From ng-controller to Components With Angular 1.5+
For those who still haven't migrated their Angular 1 apps past Angular 1.5, it's time to play catch-up. Review the syntax and functionality changes and improvements to upgrade your app towards Angular 2.
Join the DZone community and get the full member experience.
Join For Freethe web has moved forward and so should you. learn how to upgrade your angular 1 app from a more mv* architecture to a cleaner, more component oriented approach. we will learn about how to refactor your code properly and about the new features introduced in angular 1.5+ that will help you succeed along this path.
the app
i’ve created a series of plunks which you can use to play around with the code by yourself. so here’s our initial app, a very simple one, just enough to showcase some concepts we’re going to explore.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>angularjs plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
<script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
<script src="app.js"></script>
<script src="home/home.controller.js"></script>
<script src="about/about.controller.js"></script>
</head>
<body ng-app="plunker">
<div class="navbar">
<a href="#home">home</a>
<a href="#about">about</a>
</div>
<div ui-view></div>
</body>
</html>
step 1: remove/avoid
$scope
ok,
$scope
was a central element in angular since the beginning. it’s the glue between your angular controller and the html template. unfortunately scope is going away in angular 2. there won’t be anything similar there. that’s why the “controller as” syntax was introduced a while back in angular 1. so let’s use it. here’s what
john papa proposes in his popular angular 1 styleguide
.
// before
angular.module('plunker')
.controller('homecontroller', function($scope) {
$scope.message = 'hi from home';
});
// after
angular.module('plunker')
.controller('homecontroller', function() {
var vm = this;
vm.message = 'hi from home';
});
the “controller as” syntax also extends to the html. in the router configuration of our angular app, we add the
controlleras
property and set it to
vm
.
// before
$stateprovider
.state('home', {
url: '/',
templateurl: 'home/home.html',
controller: 'homecontroller'
})
...
// after
$stateprovider
.state('home', {
url: '/',
templateurl: 'home/home.html',
controller: 'homecontroller',
controlleras: 'vm'
})
...
and consequently we also have to update our html code:
<!-- before -->
<h1>home</h1>
<p>{{ this.message }}</p>
<!-- after -->
<h1>home</h1>
<p>{{ vm.message }}</p>
great, so here’s our app after the change:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>angularjs plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
<script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
<script src="app.js"></script>
<script src="home/home.controller.js"></script>
<script src="about/about.controller.js"></script>
</head>
<body ng-app="plunker">
<div class="navbar">
<a href="#home">home</a>
<a href="#about">about</a>
</div>
<div ui-view></div>
</body>
</html>
step 2: convert your controller to a directive
how is our controller bound to the view? there are different possibilities, one is through the
ng-controller
tag in the html directly, like
<div ng-controller="homecontroller as vm">
...
</div>
in our app it’s the router, however:
$stateprovider
.state('home', {
url: '/',
templateurl: 'home/home.html',
controller: 'homecontroller',
controlleras: 'vm'
})
...
notice how we define a template and the corresponding controller. this is a very loose coupling. theoretically we could use one controller for multiple html templates easily. that’s considered bad practice, though. also, this hinders reusability, because one has to know which template and which controller belong together in order to be able to reuse them in another situation. so let’s change that and stick them together. how? by writing a directive and converting our controller into a directive controller. we have a few different options:
.directive('home', function() {
return {
restrict: 'e',
scope: {},
template: 'home/home.html',
controller: homecontroller,
controlleras: 'vm'
}
});
function homecontroller() {
...
}
heads up: we’re using an isolated scope (
scope: {}
) for our “components”, because we want them to be fully isolated.
our routing gets simplified, in that it doesn’t have to know the template location and/or the controller, but simply the html tag
<home>
.
$stateprovider
.state('home', {
url: '/',
template: '<home></home>'
})
that was easy, right?
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>angularjs plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
<script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
<script src="app.js"></script>
<script src="home/home.controller.js"></script>
<script src="about/about.controller.js"></script>
</head>
<body ng-app="plunker">
<div class="navbar">
<a href="#home">home</a>
<a href="#about">about</a>
</div>
<div ui-view></div>
</body>
</html>
step 3: go further. use components!
angular 1.5+ is the best angular ever so far. since
v1.5 they introduced the new
component
syntax
which makes creating components super easy. check out todd motto’s article on the topic:
exploring the angular 1.5 .component() method
.
so what does that mean for our app? let’s see.
// before
.directive('home', function() {
return {
restrict: 'e',
scope: {},
template: 'home/home.html',
controller: homecontroller,
controlleras: 'vm'
}
});
// after
.component('home', {
restrict: 'e',
scope: {},
templateurl: 'home/home.html',
controller: homecontroller,
controlleras: 'vm'
});
we can also remove the
controlleras
property. this is an optional one. angular 1.5 components expose the controller to the view through the
$ctrl
property.
// after
.component('home', {
restrict: 'e',
scope: {},
templateurl: 'home/home.html',
controller: homecontroller
});
…and then in the html
<h1>home</h1>
<p>{{ $ctrl.message }}</p>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>angularjs plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
<script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
<script src="app.js"></script>
<script src="home/home.controller.js"></script>
<script src="about/about.controller.js"></script>
</head>
<body ng-app="plunker">
<div class="navbar">
<a href="#home">home</a>
<a href="#about">about</a>
</div>
<div ui-view></div>
</body>
</html>
was that everything?
not at all, angular 1.5 components have to offer a lot more.
bindtocontroller
and
bindings
when you create directives and pass data into them, you have to define a scope property, right?
.directive('mydirective', {
...
scope: {
message: '='
}
...
})
the problem is that at that point, within your directive controller, you’d have to access them through the
$scope
variable again, which we previously said should be avoided. thus, the
bindtocontroller
has been introduced which allows us to write the above like this:
.directive('mydirective', {
...
scope: {},
bindtocontroller: {
message: '='
}
...
})
much better.
message
will now be attached directly to our controller instance. still, we have to create the
isolate scope
with
scope: {}
which isn’t the most elegant way of doing it. it gets better . the new component syntax simplifies this into a single
bindings
property, which also creates an isolate scope behind the scenes.
.component('mydirective', {
...
bindings: {
message: '='
}
...
})
you can even get one way bindings
...
bindings: {
message: '<'
}
...
for more information, check out one-way data-binding in angular 1.5 .
lifecycle hooks
most remarkably a new set of lifecycle hooks have been introduced. remember the following pattern suggested by john papa’s famous styleguide ?
.component('home', {
...
controller: homecontroller
});
function homecontroller() {
var vm = this;
vm.message = '';
activate();
/////////////////////////
function activate() {
vm.message = 'hi from home';
}
}
the
activate
method can be seen like the constructor, a place where to group your controller’s initialization code. well with the new component syntax, you can make use of the
$oninit
hook function.
function homecontroller() {
...
vm.$oninit = activate;
/////////////////////////
function activate() {
vm.message = 'hi from home';
}
}
there are other hooks, like
$onchange
,
$ondestroy
and so on. rather than going into those details by myself, check out todd motto’s awesome article on the matter:
comprehensive dive into angular 1.5 lifecycle hooks
.
component architecture
what we’ve seen so far are the technical details about how you implement components. it’s however important to also understand the concept behind a component oriented development approach.
the main concept is to define isolated and autonomous components, with a given responsibility and clearly defined contracts in terms of which data flows in and out. generally speaking, there are two main types of components you usually create, and different people give name them differently:
- smart components / stateful components - these are components that coordinate a set of “dumb component”. they connect with angular services, fetch data or get invoked through routings.
- dumb components / stateless components - these are responsible for the immediate visual feedback. they define input bindings and callbacks and render the data they receive. usually they’re not necessarily coupled to the application and are highly reusable.
what you end with, is a so-called component tree , a set of nested components, starting from a top-level app component or root component.

angular.module('plunker')
.component('message', {
bindings: {
from: '<',
msg: '<'
},
controller: messagecontroller,
template: [
'<p><strong>a message from :</strong></p>',
'<p></p>'
].join('')
});
function messagecontroller() {}
and be used within our “smart components”
<home>
and
<about>
(big words here ) like
<message from="'home'" msg="$ctrl.message"></message>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>angularjs plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.5.6/angular.min.js" data-semver="1.5.6"></script>
<script data-require="ui-router@*" data-semver="1.0.0-alpha.5" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.5/angular-ui-router.js"></script>
<script src="app.js"></script>
<script src="home/home.controller.js"></script>
<script src="about/about.controller.js"></script>
<script src="shared/message.component.js"></script>
</head>
<body ng-app="plunker">
<div class="navbar">
<a href="#home">home</a>
<a href="#about">about</a>
</div>
<div ui-view></div>
</body>
</html>
for reference:
interested what role components play in angular 2?
then check out my video course with packtpub which focuses on “learning angular 2 directives”.
also, for a quick, 20 minute intro to angular 2 (especially for beginners), you may want to check out my other article: angular 2 - a getting started guide for beginners .
conclusion
what we’ve seen in this article:
-
how to refactor
$scope
to thecontrolleras
syntax -
how to convert an
ng-controller
stepwise towards directives and ultimately to components -
we learned about the new
.component
syntax introduced in angular 1.5 and all the benefits we get from it - how to convert a mv* pattern like approach into a more component oriented approach
summarizing, try to migrate your angular frontend architecture towards a more component oriented approach. regardless whether you plan to upgrade to angular 2 at some point or not, it’ll help you anyway create much cleaner applications.
while this article didn’t deep dive into this topic, but is rather intended to give you a first overview, tero parviainen has written an in-depth version some time ago: refactoring angular apps to component style
thanks to gerard sans for reviewing this article!
if you enjoyed this post you might want to follow me on twitter for more news around javascript and angular 2. :)
Published at DZone with permission of Juri Strumpflohner. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments