Tutorial

How to Use Angular 1.5's Component Method

Draft updated on Invalid Date
Default avatar

By Nyambati Thomas

How to Use Angular 1.5's Component Method

This tutorial is out of date and no longer maintained.

Introduction

Web Components are a set of features that allow the creation of reusable widgets in web documents and web applications.

The components model in Angular 1.5.x allows for encapsulation and interoperability of individual HTML elements. Previously in angular 1.4.x you could build components using .directive() method, with this method you could build custom HTML elements and attributes giving your application the modularity and encapsulation of DOM elements.

If that got the job done why should I use the new .component() approach?

Good question buddy, let’s answer that, shall we?

Prerequisites

This article doesn’t cover Angular basics and requires a basic understanding of Angular 1.

You should have prior knowledge of how to build applications with Angular 1, and preferably be familiar with building custom directives with .directive() method helper.

Angular’s 1.5.x .component() Up Close

The .component() method helper is just syntactic sugar of the good old .directive() method we all hate and love. There is practically nothing you can do with .component() that you can’t do with .directive().

.component() simplifies the way we create “components” - which roughly means UI directives. The component approach pushes the community to use defaults that have become best practices. Here are some defaults that components are shipped with:

  • Components have isolated scopes by default.
  • They automatically use controllerAs syntax therefore we can use $ctrl to access component data.
  • They use controllers instead of link functions.
  • The bindToController option is on by default.

Angular Components: Before and After

Below is an example of how we used to write components using the directive approach vs the component approach:

// before
app.directive(name, fn)

// after
app.component(name, options)

Sample Code

app.directive('listDirective', function () {
    return {
        // Isolated scope binding
        scope: {
            message: '='
        },
        // Inline template which is binded to message variable in the component controller
        template: '<div>Hello {{$ctrl.message}}</div>',
        // The controller that handles our component logic
        controller: function () {
                this.message = "Thomas directive"
        },
        //defaults, automatically set when using .component()
        controllerAs: '$ctrl',
        bindToController: true
    };
});

It’s a simple component directive, with isolated scope, binding, and controller. Now let’s look at the component approach.

app.component('listComponent', {
    // isolated scope binding
    bindings: {
        message: '='
    },

    // Inline template which is binded to message variable
    // in the component controller
    template:'<div>Hello {{$ctrl.message}}</div>',

    // The controller that handles our component logic
    controller: function () {
        this.message = "Thomas component"
    }
});

Elegant right? You can see not much has changed, It is simpler and straightforward. Also, we get to enjoy some default settings. bindToController is set to true and controllerAs set to $ctrl all of this is done by the component itself.

Passing External Data to Components

When building an Angular application, data is always loaded by services and passed to controllers then to the template. We could inject that particular service and get the data we need, but if another component or controller has loaded this data already we would be repeating ourselves.

So what do we do?

This is where bindings come in, we can achieve this by adding a parameter to pass data to our component, which would be used as follows:

// the data attribute will hold the data from other components or services.
<our-component data="'this is the data we need'"></our-component>

In our component definition, we basically define the name of the attribute that will be added to our component along with the type binding we want to use. There are four different types of bindings:

  1. = Two-way data binding. This means that if you update that variable in your component scope, the change will be reflected on the parent scope;
  2. < One-way bindings when we just want to read a value from a parent scope and not update it;
  3. @ This is for string parameters;
  4. & This is for callbacks in case your component needs to output something to its parent scope.

For more details read this docs

In our own case, we need to pass a string, so our component will look like this:

var app = angular.module('app',[]);

app.component('ourComponent', {
  // Binds the attibute data to the component controller.
  bindings: {
    data: '@'
  },

  // We can now access the data from the data attribute with `$ctrl`
  template:'<p>{{ $ctrl.data }}</p>'
});

Note that bindings are added to the local scope of your component, which is bound to a controller called $ctrl by default.

Inter-Component Comunication

When building an application using components, communication between these components is key in making the application as seamless as possible.

Let’s look at a scenario where you need an accordion to display some data to your users, you can use the default Bootstrap to implement this feature but it will be so hard to reuse the accordion even if you wanted to.

The best approach is to use two components accordion and accordion-panel, these components will communicate together to build an accordion we can use anywhere in our application. Below is code snippets of how this can be achieved.

var app = angular.module('app', [])

function AccordionController () {
  var self = this;
  // add panel
  self.addPanel = function(panel) {
    // code to add panel
  }

  self.selectPanel = function() {
    //code to select panel
  }
}

// register the accordion component
app.component('accordion', {
  template: '<!-- -accordion-template -->',
  controller: AccordionController
}

function AccordionPanelController () {
  // use parents methods here
  var self = this;

  // add panel
  self.parent.addPanel(self);

  // select panel
  self.parent.selectPanel(self);
}

// register the accordion-panel component
app.component('accordionPanel', {
  // require the parent component
  // In this case parent is an instance of accordion component
  require: {
    'parent': '^accordion',
  template: '<!-- -accrodion-panel-template -->',
  controller: AccordionController
}

In the code above, using property require we have required the parent component which in this is accordion, and used all its methods within our child accordion-panel component. Therefore whenever there are any changes within these two components, they will respond accordingly.

Implementation

Now that we have established what components are, the structure, and how to build them. Let’s actually make a component.

http://codepen.io/thomasnyambati/pen/gMaReM?editors=1010

Environment setup

Below are the tools we need to complete this exercise:

Create a folder called scotch-angular-components and in it, create the folders and files as follows.

├── bower.json
├── index.html
├── js
│   ├── app.js
│   └── components/
├── lib/
└── package.json

Create a new file in the root directory .bowerrc and add the following lines of code.

.bowerrc

{
    "directory":"lib"
}

The above file specifies the directory where our front-end libraries will be installed. To understand how .bowerrc works in detail visit bower.io.

bower.json

{
  "name": "getting-started",
  "description": "Getting started with angular components",
  "main": "",
  "license": "MIT",
  "homepage": "",
  "dependencies": {
    "angular": "^1.5.6",
    "bootstrap": "^3.3.6"
  }
}

Bower depends on bower.json to keep track of all the front-end packages your application needs. Looking at it closely you will notice that bootstrap and angular are listed as dependencies.

These are the libraries we need in this exercise. Visit bower/spec to get more information regarding this file.

To get our setup ready:

  1. Install http-server, npm install http-server -g
  2. Install Angular and Bootstrap, bower install

If you do not have bower installed on your machine, run npm install bower -g. Now that we are done with the environment setup, let the fun begin.

Create an Angular App

In index.html write the code below.

index.html

<!DOCTYPE html>
<html en="us" ng-app="app">

<head>
  <title>Getting started with Angular 1.5.x components</title>
  <!-- bootsrap css -->
  <link rel="stylesheet" type="text/css" href="lib/bootstrap/dist/css/bootstrap.min.css">
  <!-- angularjs -->
  <script src="lib/angular/angular.min.js"></script>
  <!-- The main app script -->
  <script src="js/app.js"></script>
  <!-- components -->
</head>

<body>
    <div class="container" ng-controller="HomeCtrl as $ctrl">
        <h1>{{$ctrl.message}} </h1>
    </div>
</body>

</html>

and in js/app.js.

js/app.js


(function() {
 var app =  angular.module('app', []);
 // A controller that displays hello world
  app.controller('HomeCtrl', function() {
     this.message = "Hello, world";
  });
})();

So far we have created an angular application called app; we have also included bootstrap CSS for styling and our main application file app.js.

In the HomeCtrl we have initialized the message to hello, world.

In index.html we have required our controller with ng-controller directive and a div that is going to display our message.

Run http-server and visit http://localhost:8080. There we go, we got hello world printed out. This is a sign that angular is well set and working.

Adding Components

For this demo, we are going to create four components:

  • one that creates a navigation bar
  • another one that displays a list of users with names, country, and city they reside in
  • and the last ones are the accordion and the accordion-panel that we discussed above.

We will start with the navigation bar.

In js/components create two files appComponent.js and appComponent.hmtl respectively.

js/components/appComponent.js

(function(){
  'use strict';
  var app = angular.module('app');

  app.component('menuBar', {
    // defines a two way binding in and out of the component
    bindings: {
      brand:'<'
     },
    // Load the template
    templateUrl: '/js/components/appComponent.html',
    controller: function () {
      // A list of menus
      this.menu = [{
        name: "Home",
        component: "home"
      }, {
        name: "About",
        component: "about"
      }, {
        name: "Contact",
        component: "contact"
      }];
    }
  });
})();

In the above file we are telling Angular to create a component called menuBar, use template appComponent.html to render a list of menus this.menu.

js/components/appComponent.html

<nav class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">{{$ctrl.brand}}</a>
    </div>
    <!-- Generated navbar -->
    <div>
      <ul class="nav navbar-nav">
        <li ng-repeat="menu in $ctrl.menu">
        <a href="{{menu.component}}">{{menu.name}}
            <span class="sr-only">(current)</span>
        </a>
    </li>
      </ul>
    </div>
    <!-- generated navbar -->
  </div>
  <!-- /.container-fluid -->
</nav>

In this template, you can see we are using ng-repeat directive to dynamically generate the navigation bar. We can access data from the component controller by using $ctrl, in this case, $ctrl.menu will contain the array of menus, and $ctrl.brand will contain the data passed by the brand attribute.

If you visit the browser and refresh, you will still see Hello, world on the screen. This is because we have not added the component to our app. Let’s do that. Include the component in the head tag and replace the div element in index.html with the following code.

index.html

<!-- index.html -->

<head>
    <!-- Components -->
     <script src="js/components/appComponent.js"></script>
 </head>

<body>
    <menu-bar brand="Brand"></menu-bar>
</body>

Refresh the page again and poof! We have our menu bar laid out beautifully.

When Angular bootstraps the app, it will read the DOM element <menu-bar> and map it to our component menuBar. Therefore, whenever angular encounters this element it will render out component for us.

Okay! Now that we have created our navigation component, let’s add one more. We need a table that has names of users, their country of origin, and the city they reside in. We could use a controller and populate the data in our view, but if we ever require this data in a different view we would have to repeat the same code again. To fix this we create another component.

In js/components create new files userComponent.js and userComponent.html. In userComponent.js write the following code.

js/components/userComponent.js

(function () {
  'use strict';

  var app = angular.module('app');

  app.component('userInfo', {
    // Binds caption to the parent scope
    bindings: {
      caption: '<'
    },
    // Loads the component template
    templateUrl: '/js/components/userComponent.html',
    controller: function () {
      // The list of users we will be displaying
      this.records = [{
        name: "Alfreds Futterkiste",
        city: "Berlin",
        Country: "Germany"
      }, {
        name: "Ana Trujillo Emparedados y helados",
        city: "México D.F.",
        country: "Mexico"
      }, {
        name: "Blondel père et fils",
        city: "Strasbourg",
        country: "France"
      }, {
        name: "Bólido Comidas preparadas",
        city: "Madrid",
        country: "Spain"
      }];
    }
  });
})();

In this file, we have created a component userInfo which contains an array of users in the records variable. To render this data we need a template that will iterate through the records array and display it to the browser. In js/components/userComponent.html write the following code.

js/components/userComponent.html

<div class="container">
  <table class="table table-bordered">
    <!-- table caption will be displayed here -->
    <tr>
      <td id="caption" colspan="4">{{$ctrl.name}}</td>
    </tr>
    <th>
      <td>Name</td>
      <td>City</td>
      <td>Country</td>
    </th>
    <!-- users will be displayed here -->
    <tr ng-repeat="user in $ctrl.records">
      <td></td>
      <td>{{user.name}}</td>
      <td>{{user.city}}</td>
      <td>{{user.country}}</td>
    </tr>
  </table>
</div>
<!-- component styling -->
<style type="text/css">
#caption {
  text-align: center;
}
</style>

In this template, we have created a table that accesses the component data through the use of $ctrl, loops through it, and displays it to us. That’s it? Hold on we are not done yet, right now we have just defined the component but our app has no way of locating this component, to register this component we need to link our component script to the app and add user-info tag to index.html. This way when angular sees this user-info tag it will render our component. Let’s do that.

index.html

<!-- index.html -->
<!-- in the head tag -->
<script src="js/components/userComponent.js"></script>

<!-- below <menu-bar> tag -->
  <user-info name="'Users Table'"></user-info>

When you refresh your browser, you should see the users displayed in a table as below.

Ok, we have got our users table, and navigation now let’s build our accordion.

This section will introduce you to how you can build components that are tightly coupled, working together to provide a seamless user experience. Let’s get down to it.

js.components/accordionComponent.js

(function() {
  'use strict'
  var app = angular.module('app');
  // Accordion controler
  function AccordionController () {
    var self = this;
    var panels = [];
    // here we take the panel and add to our list of panels
    // to preselect the first panel we call turnOn function on the first panel
    self.addPanel = function (panel) {
      panels.push(panel);
      if (panels.length > 0) {
        panels[0].turnOn();
      }
    };
    // when a panel is selected we would want to open the contets
    // here we take the panel find it in our array and turn if on if not selected
    // and off it it is.
    self.selectPanel = function (panel) {
      for (var i in panels) {
        if (panel === panels[i]) {
          panels[i].turnOn();
        } else {
          panels[i].turnOff();
        }
      }
    };
  }
// Register the component to the app
  app.component('accordion', {
    transclude: true,
    template:'<div class="panel-group" ng-transclude></div>',
    controller: AccordionController
  });

})();

In the above file, we have two methods, addPanel and selectpanel. The addPanel method takes a panel and adds it into the list of panels existing in the component and preselects the first panel in our list.

As for the selectPanel this method will respond to the click event when the panel is selected, it will take that respective panel toggle between on and off.

js/components/accordionPanel.js


(function() {
  'use strict';
  // accordion-panel controller
  function AccordionPanelController() {
    var self = this;
    // register the panel in init
    self.$onInit = function () {
      self.parent.addPanel(self);
    };
    // Turns on the panel by changing selected to true
    self.turnOn = function () {
      self.selected = true;
    };
    // Turns off the panel by changing selected to false
    self.turnOff = function () {
      self.selected = false;
    };
    // Selects a the current selected panel
    self.select = function () {
      self.parent.selectPanel(self);
    };
  }
// Register the accordion-panel component
  app.component('accordionPanel', {
    transclude: true,
    // require the parent component, in this case accordion component
    require: {
      'parent': '^accordion'
    },
    // specify attribute binding
    // https://docs.angularjs.org/api/ng/service/$compile#-scope-
    bindings: {
      heading: '@'
    },
    // Accordion-panel template
    template: `
      <div class="panel panel-default">
        <div class="panel-heading" ng-click="$ctrl.select()">
          <h3 class="panel-title">{{$ctrl.heading}}</h3>
        </div>
        <div class="panel-body" ng-transclude ng-if="$ctrl.selected">
        </div>
      </div>
     `,
    controller: AccordionPanelController,
  });

})();

After building the accordion component, we need an accordion panel to hold any data we may have. This is part that will be turned on and off to provide the accordion effect.

In this file we have $onInit a lifecycle hook, turnOn, turnOff and select methods. When our component is constructed, we need to register our panel to the parent component. Using $onInit hook, we have called the addPanel method to inform the parent component that we have added one more panel.

We then went ahead and defined turnOff and turnOn methods, these methods provide us with the ability to turn on and off the panels when selected. The select method works with the turnOn and turnOff to provide the toggle effect.

In the template we have created a panel, using bootstrap panels with a panel heading, panel title, and panel body. The panel heading has been bound to $ctrl.select() method through a click event, this means that when we click this heading, the select method will be called and will either turn on or off the panel. The panel body has been bound to the $ctrl.selected, when the header is clicked this value will toggle between true and false giving our component the toggle effect.

Now that we have built our components let’s intergrate them together to form our accordion. In your index.html add the two components in your head tag

*index.html

...
<script src="js/components/accordionComponent.js"></script>
<script src="js/components/accordionPanelComponent.js"></script>
...

and below the user-info component add the following

...
 <div class="container">
   <!-- The main accordion component -->
    <accordion>
      <!-- The first panel with heading 1 -->
      <accordion-panel heading="heading 1">
        This a panel
      </accordion-panel>
            <!-- The secoind panel with heading 2 -->
      <accordion-panel heading="Heading 2">
        this is another panel
      </accordion-panel>
    </accordion>
  </div>
...

Let’s save our changes and refresh your browser! Your page should look similar to this.

The accordion we have built works the same way as a native Bootstrap accordion. The beauty here is that we can reuse these components to build an accordion anywhere in our application.

There you have it! We have built an angular application using components - simple and easy.

Conclusion

Building an angular application using .controller() and .directive() can become tedious and dirty. When your application scales your codebase can get messier. With the component approach, you can build your application in small feature-specific blocks that are highly scalable, maintainable, and elegant.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Nyambati Thomas

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel