AngularJS 1.5 introduce .component()
helper method which is much simpler than the .directive()
definitions and advocates best practices and common default behavior.
Using .component()
will allow to write in Angular 2 style as well, which will turn transition to Angular 2 much easier.
Let’s compare the difference in syntax and the possibility of new abstraction.
From “directrive()” to “component()”
The syntax change is simple:
// before
module.directive(name, fn);
// after
module.component(name, options);
I’ve rebuild a simple counter
directive which which we’ll refactor to component()
:
module
.directive('counter', function counter() {
return {
scope: {},
bindToController: {
count: '='
},
contorllerAs: '$ctlr',
controller: function() {
this.increment = () => this.count++;
this.decrement = () => this.count--;
},
template: `
<div>
<input type="number" ng-model="$ctlr.count">
<button ng-click="$ctrl.increment()">+</button>
<button ng-click="$ctrl.decrement()">-</button>
</div>
`
}
})
Function to Object
Let’s start from the top and refactor the function argument to object:
// before
module
.directive('counter', function counter() {
return {
}
})
// after
module
.component('counter', {
})
Nice and simple.
“scope” and “bindToController” become “bindings”
In directive()
the scope
property allows us to define whether we want to isolate the $scope
or inherit it. So repeating every time just create an extra boilerplate. With bindToController we can explicitly define binding directly to instance of controller via this
.
With bindings
we can remove this boilerplate and simple define what we want to pass down to the component.
And component()
will always have an isolated scope.
// before
module
.directive('counter', function counter() {
return {
scope: {
count: '='
},
bindToController: true
}
})
// after
module
.component('counter', {
binding: {
count: '='
}
})
Controller and “contorllerAs” changes
Nothing has changed in the way we declare controller
, however it’s now smarter and has a default contorllerAs
value of $ctrl
.
Under the hood it looks like:
// inside angular.js
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl'
//...
var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
function identifierForController(controller, ident) {
if (ident && isString(ident)) return ident;
if (isString(controller)) {
var match = CNTRL_REG.exec(controller);
if (match) return match[3];
}
}
This allows us to do the following inside .component()
:
{
...
controller: 'FooController as foo'
}
Based on the information we refactor our Directive
to Component
by dropping controllerAs property:
// before
.directive('counter', function counter() {
return {
scope: {
count: '='
},
bindToController: true,
controller: function () {
this.increment = () => this.count++;
this.decrement = () => this.count--;
},
controllerAs: 'counter'
};
});
// after
.component('counter', {
bindings: {
count: '='
},
controller: function () {
this.increment = () => this.count++;
this.decrement = () => this.count--;
}
});
Things now are becoming much simpler and funny.
Template
The template
property can be defined as a function property with injected $elem
and $attrs
values:
{
...
template: function ($element, $attrs) {
// access to $element and $attrs
return `
<div>
<input type="text" ng-model="$ctrl.count">
<button type="button" ng-click="$ctrl.decrement();">-</button>
<button type="button" ng-click="$ctrl.increment();">+</button>
</div>
`
}
...
}
Inheriting behavior with “require”
Inherited Directive or Component methods will be bound to this.parent
property in the Controller:
{
...
require: {
parent: '^^parentComponet'
},
controller: function() {
// use this object to access to required Object
this.parent.foo();
}
}
One-way binding
A new syntax expression for isolate scope values:
{
...
bindings: {
oneWay: '<',
twoWay: '='
},
...
}
But still remember that object are passed by reference, and Angular doesn’t make a clone of the object when it passed via
one-way binding
, it actually sets the same value, which means that objects have stilltwo-way binding
somehow.
Upgrading to Angular 2
Writing in this style using .component()
allows you easily transit to Angular 2, it’d look something like this:
import { Component } from '@angular/core'
@Component({
selector: 'counter',
template: `
<div>
<input type="number" [(ng-model)]="count">
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>
</div>
`,
})
export default class CounterComponent {
constructor() {}
increment() {
this.count++;
}
decrement() {
this.count++;
}
}
Save my day: