Writing component based goats dating app with angular 1.5

The angular team introduced the .component() method in version 1.5, it allows us to write component based apps using angular 1, this approach makes it easier to transfer your code base to angular 2.

In this guide we will build "Goatlove"(Got-love :), which is a dating app, for goats. It will help us to see component based architecture in action, and hopefully, find some goats around the world a true love.

You can find the entire code here.

Components based architecture

The component based architecture is a rising star in the world of web development, with libraries like React, and the new direction Angular 2.0 is headed. It's clearly we will see a lot more of web apps that embrace this practice in their workflow.

Working with components means separating your apps into small chunks, usually just a few lines of code each. In such way we can define the data flow for those components and isolate them from their environment. So later, we can take one component out and place it inside other part of our application.

Angular 1.X embraced the use of directives, but defining a directive for me was really annoying, all the boilerplate, markup, link functions, transclusion, etc... we're quite intimidating. So many developers ended up with something called "Scope Soup" which basically means that your controllers, were separated from the views, and you probably used ng-controller for tying things. Really fast it becomes a mess, you cant really know how your app behaves and how the data flows.

Later, the approach of using controllerAs syntax evolved, and for many it became a best practice to always alias your controllers in the view so you can understand the scope from looking at your code.

When I started to look into React it was so easy to create a new component, that I ended up writing components for almost everything, reusing them in other parts of my code was a piece of cake. I really love writing code this way nowadays.

One of the projects I've started working on required using angular for some of it part, so I searched for a way to write component based apps, without the hassle of writing directives. After a quick search I found that in angular 1.5 a new method released by the name .component(), which is basically a directive but much easier to write and read.

Angular .component()

The new syntax is pretty straight forward, instead of passing a function that returns an object, we simply pass an object.

Let's see how a component is declared in angular 1.5:

angular  
     .module('app')
     .component('sampleComponent', {
          template: '<h1>Hello {{$ctrl.name}}!</h1>',
          bindings: {
               name: "<"
          },
          controller: function() {
               // You can access the bindings here or inside your view
               console.log(this.name) // -> World
          }
     });

than when we want to use it inside our views we can simply type:

<div ng-app="app">  
    <sample-component name="World"></sample-component>
</div>  

Let's examine the component structure:

.component('sampleComponent', { ...

Here we define the component's name camelCased, when we will want to use the component we will write it using hyphens as in our example above.

  • template - used as our component html view, you can write here other components as well! (You can also specify a templateUrl instead if you wish)
  • bindings - angular components come with isolated scope by default, and instead of writing bind-to-controller key as we used to in the directive method we can simply specify bindings. Bindings in additional to all the known angular bindings can now be used with the new < symbol, a one way binding option, this allows writing our components isolated to the outer scope.
  • controller - here we can specify a controller for our component, angular by default create a controllerAs with the name of $ctrl, we can change this default behavior by specifying controllerAs key.
  • require - We can require the controller of other components in order to communicate between the components.
  • transclude - enables transclusion, the uses are the same as with directives.

For additional configurations you can read the angular docs.

Writing simple component based webapp

Our "goatlove" app will contain a list of goats, with some details like: age, name, about info & photo (currently we will use only external pic link, after we will have some VC's interest, we can upgrade our app to use a server:)), we will allow our clients to create a new entry. For simplicity sake, and for the sake of goats with no WiFi connection, our creation page will allow creating multiple goat profiles with no login needed.

When writing component based apps it is important to visualise your different app components upfront. So here is our awesome goatlove layout.

Goatlove layout

We split our app into small reusable components based on their behavior, we can later decide that we will create more components for buttons, or other reusable parts we might find.

Note that there is a separation between smart and dumb components, it is a great concept I found from Dan Abramov post.

Shortly, our dumb controllers simply acquire their input from the parent controller, they don't know where the data is coming from or where its going when it needs to output something. This approach makes our code reusable. We can port our dumb components to some other place in our code, give them the i/o they needed and they will magically work :).

Smart controllers, are usually don't contain much html and css, their responsibility is to contact services and pass the data they acquire to the dumb components.

Boilerplate

We will start by grabbing NG6 Starter, its a great starting point for writing component based angular apps. It's come with pre configured webpack, sass compiling, es6 transpiler, auto reload, component blueprints and other cool stuff. So simply fork or clone this repo to your computer.

Enter the cloned directory and type npm install this will install all dependencies necessary for our Goatlove project.

The starter pack does not come with node-sass library, so simply type npm i node-sass -D.

Now we will added 2 loaders for our webpack.config.js file. npm install file-loader url-loader -D.

and add to the loaders array in webpack.config.js the next lines:

 {
           test   : /\.woff/,
           loader : require.resolve("url-loader") + '?prefix=font/&limit=10000&mimetype=application/font-woff&name=assets/[hash].[ext]'
       },
       {
           test   : /\.ttf/,
           loader : require.resolve("file-loader") + '?prefix=font/&name=assets/[hash].[ext]'
       },
       {
           test   : /\.eot/,
           loader : require.resolve("file-loader") + '?prefix=font/&name=assets/[hash].[ext]'
       },
       {
           test   : /\.svg/,
           loader : require.resolve("file-loader") + '?prefix=font/&name=assets/[hash].[ext]'
       },

This will handle our font loading when importing bootstrap(We used require.resolve for finding the modules from node_modules folder, you can use simply the string, but I encountered a few errors with this starter kit).

When finished, we will start our app using:

gulp serve  

Go to localhost:3000 and see your awesome app alive!

Styling

For the sake of this tutorial we will use bootstrap to make all of our styling. So let's install bootstrap as our app dependency:

npm install bootstrap --save  

Folder Structure

Inside the starters pack our code will reside inside client folder, in there we are going to create a new folder named services and containers which will hold our "smart components" and services accordingly. Also let's clear all the current html and scss content inside the components folders.

Next open app.scss file and clean its contents.

Our client folder in this point should look like:

├── app
│   ├── app.component.js
│   ├── app.html
│   ├── app.js
│   ├── app.scss
│   ├── components
│   ├── containers
│   └── services
│   └── pages
└── index.html

Building the app

Let's start by adding bootstrap styles to our project. We simply do so by adding an import statement inside our app.js file.

We will also remove the old dependencies from the file.

import angular      from 'angular';  
import uiRouter     from 'angular-ui-router';  
import AppComponent from './app.component';

// import our default styles for the whole application
import 'normalize.css';  
import 'bootstrap/dist/css/bootstrap.css';

angular.module('app', [  
    uiRouter
])
.config(($locationProvider, $stateProvider, $urlRouterProvider) => {
    "ngInject";

    // Define our app routing, we will keep our layout inside the app component
    // The layout route will be abstract and it will hold all of our app views
    $stateProvider
        .state('app', {
            url: '/app',
            abstract: true,
            template: '<app></app>'
        })

        // Dashboard page to contain our goats list page
        .state('app.home', {
            url: '/home',
            template: 'Home page'
        })

        // Create route for our goat listings creator
        .state('app.create', {
            url: '/create',
            template: 'Create Page'
        });

   // Default page for the router
   $urlRouterProvider.otherwise('/app/home');
})
.component('app', AppComponent);

And our index.html will look like this:

<!doctype html>  
<html lang="en">  
  <head>
    <meta charset="utf-8">
    <title>Find love</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="Goatlove app">
    <base href="/">
  </head>
  <body ng-app="app" ng-strict-di ng-cloak>

    <!-- Our app entry point, everything will be generated here-->
    <div ui-view></div>

  </body>
</html>  

Note that we haven't included any js files inside the index.html, webpack-dev-server will handle this for us, and when we will build our app for production it will append any script tags necessary.

We will start writing our layout, and the first component we will build is going to be the navigation bar.

To add a component, write the following line inside your terminal:

gulp component --name navigation  

This will generate a new component named navigation inside the components folder.

Navigate to the components folder and open the navigation.html file.

Add the next bootstrap snippet:

<nav class="navbar navbar-default">  
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">Goatlove</a>
    </div>

    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li ui-sref-active="active" ui-sref="app.home"><a href="">Home</a></li>
        <li ui-sref-active="active" ui-sref="app.create"><a href="">Create</a></li>
      </ul>
    </div>
  </div>
</nav>  

This will act as our navigation bar, ui-sref-active directive will add the active class based on the current app state. Next, clear the .scss file since we won't be using it for now.

Finally, open the app.js file and import the new component, after that, add it as app dependency:

import NavigationComponent from './components/navigation/navigation';  
.....
angular.module('app', [  
    uiRouter,

    // we inject the .name property of our import
    NavigationComponent.name
])
....

Next, we will open app.html and add the following html:

<div class="app">  
    <!-- This is how we add a component -->
    <navigation></navigation>

    <!-- Will contain our pages -->
    <div ui-view></div>
</div>  

Home page

Let's begin with our home page, this will host the Goats Listing component.

Open your terminal:

gulp component --name home --parent ../pages  

We added the --parent attribute to specify a different folder for the components generator, we will place this component inside the Pages folder(the parent path is relative to the components folder).

Open the home.html file and add the following html:

<div class="container">  
  <h1>Welcome to the Goatlove Home Page!</h1>
  <p>The best app for finding true love.</p>

  <goats-listing></goats-listing>
</div>  

Now, go to the app.js file import the Home component and replace the $state route template with <home></home> This will generate the Home page component for the specific route.

Goats Listing (Smart Component)

Before, we can proceed building the smart component we need to build our GoatsService.

Create a new file inside our services folder, name it GoatsService.js.

This file will contain a simple function we will export and import inside app.js (when creating larger apps we will want to create separate module for our services, for the purpose of this tutorial we will keep it simple).

function GoatsService() {  
    "ngInject";

    // private variable to store our goats entries
    const goats = [
        {
            name: "Goat 1",
            age: 23,
            about: "I'm a demo goat, and this is my bio",
            photo: "http://images.northrup.org/picture/xl/animals/billy-goat-profile1.jpg"
        }
    ];

    return {

        // Will retrieve our goats list for displaying
        getGoats() {
            return goats;
        },

        // Creating a new goat entry based on user input.
        createGoat(goat) {

            const {name, age, about, photo} = goat;

            const tempGoat = {
                about,
                photo,
                name,
                age
            };

            goats.push(tempGoat);
        }
    }
}

export default GoatsService;  

Now add the factory to our app.js file:

import GoatsService from './services/GoatsService';

... previous code ...
.factory('GoatsService', GoatsService);

Next, we will create our smart component, by saying smart I mean that it will interact with our Services, and pass the retrieved information to the other dumb components beneath.

We will place our smart components inside the containers folder, a common practice I borrowed from react.

gulp component --name goatsListing --parent ../containers  

This component will not contain any html (maybe some wrapper divs and containers) It's main purpose is for sharing information with our goats-list component.

the goatsListing.html will contain only:

<goats-list goats="vm.goatsList"></goats-list>  

We are passing an array from our component's controller.

goatsListing.controller file will contain a fetching request from the Service and placing it on the controller scope, so we can pass it to our dumb components later:

class GoatsListingController {  
  constructor(GoatsService) {
    "ngInject";

    // This will keep the service instance across our class
    this.GoatsService = GoatsService;

    // this will gold our goatsList, it will be passed to the other components.
    this.goatsList = [];
  }

  // This method will be called each time the component will be initialised,
  // In our case, it will be called for every page route change.
  $onInit(){
    this.goatsList = this.GoatsService.getGoats();
  }
}

export default GoatsListingController;  

For our container to work, we need to inject it to our home.js page file:

import angular from 'angular';  
import uiRouter from 'angular-ui-router';  
import homeComponent from './home.component';  
import GoatsListingComponent from '../../containers/goatsListing/goatsListing';

let homeModule = angular.module('home', [  
    uiRouter,

    // here we inject the container
    GoatsListingComponent.name
])

.component('home', homeComponent);

export default homeModule;  

Now, after we have the container loading our data we need to show it. Time to create some dumb components.

Goats List Component

gulp component --name goatsList  

import the new component module to our goatsListing.js file as we did before with previous modules.

for the goatsList.html we will add the following html:

<div class="row">  
  <div class="jumbotron">
    <p>This page will list all of our goats registered, you can view the listings, or create your own profile!</p>
    <p><a class="btn btn-primary btn-lg" href="#/app/create" role="button">Create Entry</a></p>
  </div>


  <goat-list-item ng-repeat="goat in vm.goats" goat="goat"></goat-list-item>
</div>  

Remember that we passed to goatsList array from our container? Since we all components are scope isolated we need to open the input for the outside world, we do that from the goatsList.component.js file.

import template from './goatsList.html';  
import controller from './goatsList.controller';  
import './goatsList.scss';

const goatsListComponent = {  
  restrict: 'E',
  bindings: {
    goats: '<'
  },
  template,
  controller,
  controllerAs: 'vm'
};

export default goatsListComponent;  

Goats List Item

This will represent each goat entry, let's create it:

gulp component --name goatListItem  

Let's define a goat property binding to our component, since we passing it from the component above:

goatListItem.component.js

import template from './goatListItem.html';  
import controller from './goatListItem.controller';  
import './goatListItem.scss';

const goatListItemComponent = {  
  restrict: 'E',
  bindings: {
    goat: '<'
  },
  template,
  controller,
  controllerAs: 'vm'
};

export default goatListItemComponent;  

The html file will contain the markup for each entry:

<div class="col-sm-6 col-md-4">  
  <div class="thumbnail goat-item">
    <div class="img-wrapper">
      <img ng-src="{{vm.goat.photo}}" alt="{{vm.goat.name}}">
    </div>

    <div class="caption">
      <h3>{{vm.goat.name}}</h3>
      <p>{{vm.goat.about}}</p>
    </div>
  </div>
</div>  

Inject the ItemListItem in the goatsList.js file, so we can use it inside our ng-repeat loop.

Cool, our viewer seems to work, except one problem, our goat photo is too big. we can fix this inside our goatListItem.scss:

.goat-item {
  .img-wrapper {
    height: 300px;
    overflow: hidden;

    img {
      width: 100%;
    }
  }
}

Awesome, good looking goat we have here! After finishing displaying our goats entries, we can make the <create></create> page.

Creating Goat Profile

We will start with a creator page for the ui-router:

gulp component --name create --parent ../pages  

Our html file is going to be:

<div class="container">  
  <h1>Welcome to Goat's creation page!</h1>

  <goat-creator-form></goat-creator-form>
</div>  

Import the page module to the app.js file as we did before, and change the app.creator $state route with the <create></create> template.

Goat Creator Form Component

We are going to create our creation form as smart component. It will recieve use input from a form, and then submit the entry. After the entry will be saved to our service, we'll transfer to the home page.

gulp component --name goatCreatorForm --parent ../containers  

inject the module to the create page module as before.

The html markup for the form:

<form class="form-horizontal" role="form" ng-submit="vm.addGoat()">  
  <div class="form-group">
    <label class="control-label col-sm-2">Name:</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" placeholder="Enter name" ng-model="vm.goat.name">
    </div>
  </div>
  <div class="form-group">
    <label class="control-label col-sm-2">Age:</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" placeholder="Enter your age" ng-model="vm.goat.age">
    </div>
  </div>
  <div class="form-group">
    <label class="control-label col-sm-2">Photo:</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" placeholder="Enter photo URL:" ng-model="vm.goat.photo">
    </div>
  </div>
  <div class="form-group">
    <label class="control-label col-sm-2">About:</label>
    <div class="col-sm-10">
      <textarea name="" class="form-control" cols="30" rows="10" ng-model="vm.goat.about"></textarea>
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
      <button type="submit" class="btn btn-default">Submit</button>
    </div>
  </div>
</form>  

And the form controller:

class GotCreatorFormController {  
  constructor($state, GoatsService) {
    "ngInject";

    this.$state       = $state;
    this.GoatsService = GoatsService;

    this.goat = {};
  }

  // will handle the form submission,
  // validates the required field and then adds the goat to the service.
  // once added, we will go to the next page.
  addGoat() {
    if(!this.goat.name) return alert('Goat Name is Required');
    if(!this.goat.photo) return alert('Goat Profile photo URL is required');

    this.GoatsService.createGoat(this.goat);

    // reset the form
    this.goat = {};

    // go to home page, to see our entry
    this.$state.go('app.home');
  }
}

export default GotCreatorFormController;  

Summary

In this blog post we have built a simple app entirely using the new .component method from angular 1.5.

This approach allows us to write component based apps that allow us reuse our components between different pages easily, since each component is isolated from the outer app. Our dumb components expose their "API" as bindings, so we can change their context without problem.

Writing your apps this way, will help with the future migration of your code base to Angular 2.0, since it is built entirely using components.

You can find the entire app in my github repo here.

Dima Grossman

Read more posts by this author.

Subscribe to Dima's code blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!
comments powered by Disqus