This tutorial is out of date and no longer maintained.
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?
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.
.component()
Up CloseThe .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:
controllerAs
syntax therefore we can use $ctrl
to access component data.link
functions.bindToController
option is on by default.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)
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.
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:
=
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;<
One-way bindings when we just want to read a value from a parent scope and not update it;@
This is for string parameters;&
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.
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.
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
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:
npm install http-server -g
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.
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.
For this demo, we are going to create four components:
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.
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.
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!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.