Preparing for Angular 2
I’m sure you heard about Angular 2 and that it will be totally different. Forget everything you know and start from scratch :open_mouth: . Jokes apart, if you have taken a closer look you already know that, yes, it will be new, things will be different (as it’s mostly the case with new stuff), but many concepts will still be there. Well, as Angular 2 starts getting more and more concrete, articles, videos and podcasts get published that contains lots of useful information on how to get prepared an eventual migration scenario. I use this article to collect such best practices for myself and and obviously to share them with you!
Totally feel free to submit new practices in my comments. That’d be awesome! You can also directly submit a PR as this blog is Open Source. Simply click the “Contribute” link above.
Keep an eye on this post here as I will update it from now and then as I stumble upon new things. Following is easy, simply subscribe to the RSS feed or follow me on Twitter.
Get rid of $scope: controller-as syntax
Definitely switch over to the controller-as syntax and get rid of $scope
in your controller files as well as in the templates.
<div ng-controller="PersonController as personCtrl">
<div ng-repeat="person in personCtrl.people">
{% raw %}{{ person.name }}{% endraw %}
</div>
<button ng-click="personCtrl.add()">Add</button>
</div>
angular
.module('demo', [])
.controller('PersonController', PersonController);
function PersonController(){
this.people = [
{
name: 'Juri'
},
{
name: 'Steffi'
}
];
};
First of all, Angular 2 won’t have any concept of $scope
and then - especially in the HTML - using the controller-as syntax avoids a lot of headache when it comes to nested controllers.
Links
Use directive controllers where possible
When you create new Angular directives you actually have different possibilities where to define your logic:
angular
.module('demo', [])
.directive('personDisplay', PersonDisplayDirective);
function PersonDisplayDirective(){
return {
compile: function(element, attrs){
// implement logic
},
link: function($scope, iElement, iAttrs){
// implement logic
},
controller: function(){
// implement logic
}
}
}
When should we use which?? That’s a common question among Angular devs. You can find some suggestions on the Angular docs and in various online communities, but it’s not always that clear.
Angular 2 removes all of these, you’ll have a component (directive) and a controller class associated to it. As an obvious result, people suggest to use “directive controllers” wherever possible.
angular
.module('demo', [])
.directive('personDisplay', PersonDisplayDirective);
function PersonDisplayDirective(){
return {
controller: function(){
// implement logic
},
controllerAs: 'vm'
}
}
I asked on Twitter:
True to say we should avoid the compile/link fns and use directive controllers with bindToController etc. ? #angular //cc @PascalPrecht
— Juri Strumpflohner (@juristr) July 3, 2015
Pascal Precht - Angular expert @ Thoughtram - answered:
@juristr In addition, it's much more aligned with the philosophy behind Angular 2 components. I recommend using controllers where possible
— Pascal Precht ʕ•̫͡•ʔ (@PascalPrecht) July 3, 2015
From his perspective:
- No you shouldn’t avoid them per se. The point is, as long as you do stuff that hasn’t to be in compile/pre/postlink, put it in ctrl
- In other words, in most of the cases you actually don’t need to deal with compile/link. Controller gives you everything you need
- Except for cases like ngModelController, which registers itself at ngFormController during preLink
- However, when controller is enough, use that. You don’t have to deal with rather complicated compile/link things.
- In addition, it’s much more aligned with the philosophy behind Angular 2 components. I recommend using controllers where possible
- Also easier to test!
Note that one issue when you use directive controllers is often on how to reference the directive scope properties from within the controller - since we should possibly avoid $scope
. Since Angular v1.3 there is the boolean bindToController
property and recently in v1.4 they’ve even improved it s.t. you can write things like
return {
restrict: '...',
bindToController: {
val1: '=',
val2: '@'
...
},
controller: function($log){
// access them with
$log.debug(this.val1);
}
}
There’s a nice article on Thoughtram about that. Follow the link below.
Links
Get rid of ng-controller; Write Directives
Angular 2 follows the current trend of web components. Thus, it won’t have autonomous controllers any more, but just in conjunction with so-called components. An Angular 2 app is a tree of nested components, with a top-level component being the app.
So rather than having controllers, start to create such widgets or components that are autonomous. In Angular 1.x we know them as directives. Search for template dependent controllers and try to move them into separate directives. For example, refactoring the previous example we could get something like this:
<div ng-controller="PersonController as personCtrl">
<!-- PersonController scope -->
<people-list people="personCtrl.people">
<!-- PersonListController scope here -->
<div ng-repeat="person in personListCtrl.people">
{% raw %}{{ person.name }}{% endraw %}
</div>
<button ng-click="personListCtrl.add()">Add</button>
</people-list>
</div>
Note that PersonController
passes in the data required by our list component. Thus it remains fairly re-usable. All it does is creating the UI functionality.
angular
.module('demo', [])
...
.directive('peopleList', peopleListDirective);
function peopleListDirective(){
return {
controllerAs: 'personListCtrl',
controller: function($scope, $attrs){
this.peopleList = $scope.eval($attrs.people);
this.add = function(){
// implementation
}.bind(this);
}
}
}
Start writing Components
Start writing components:
restrict: 'E'
- restrict to elements as they will be a lot more common in Angular 2. (there are attribute directives as well, though)bindToController: {}
- makes it a lot easier to use the controllerAs syntax (Thoughtram article)controllerAs
- well…see the sections before about why
Here’s an example.
function myAngular1ComponentDirective() {
return {
restrict: 'E',
scope: {},
bindToController: {
user: '='
},
controller: function() {},
controllerAs: 'vm',
template: [
'<p>',
'Hi, {{ vm.user.name }}!',
'</p>'
].join(' ')
}
}
Try it yourself.
Note, Angular v1.5 will introduce
.component()
which are even more similar to Angular 2 components. See Todd Motto’s article on that.
Use Observables instead of $watch
David East recently spoke at Angular U on how to prepare for upgrading to NG 2. What’s particularly interesting is his usage of Rx.js to avoid $watch
.
Here’s an example of a peopleService
in the form of an Angular factory. Note, in the subscribe
method, external listeners can register themselves on the Rx ReplaySubject
for getting updates. The actual broadcasting happens in the add
method which first performs the actual “business” logic (obviously quite simple in this demo service) and then broadcasts the changes.
angular.module('demo', [])
.service('peopleService', PeopleService);
function PeopleService() {
var peopleSubject = new Rx.ReplaySubject();
var service = {
subscribe: function(subscription) {
peopleSubject.subscribe(subscription);
},
add: function(people) {
people.push({
name: 'Name ' + (people.length + 1)
});
// broadcast
peopleSubject.next(people);
}
};
return service;
}
At this point, on the PeopleController
(or any other interested party) we can subscribe to those changes:
function PersonController(peopleService) {
var vm = this;
vm.people = [];
peopleService.subscribe(function(people) {
vm.people = people;
});
}
In the people-list
directive, when the button is clicked and the add
method invoked, we forward the action to our service, which - as we’ve seen - will broadcast the changes.
function peopleListDirective() {
return {
controllerAs: 'peopleListCtrl',
controller: function($scope, $attrs, peopleService) {
this.people = $scope.$eval($attrs.people);
this.add = function() {
peopleService.add(this.people);
}.bind(this);
}
}
}
Links
- Presentation by David East on preparing for Angular 2
- Reactive Extensions - Rx.js