Exploring the Angular 1.5 .component() method
Angular 1.5 introduced the .component()
helper method, which is much simpler than the.directive()
definition and advocates best practices and common default behaviours. Using .component()
will allow developers to write in an Angular 2 style as well, which will in turn make upgrading to Angular 2 an easier feat.
Let’s compare the differences in syntax and the super neat abstraction that .component()
gives us over using the .directive()
method.
Update: use component() now in Angular 1.3+
I’ve back-ported the Angular 1.5 .component()
functionality to Angular 1.3 and above! Read the article and grab the code on GitHub.
.directive() to .component()
The syntax change is very simple:
// before
module.directive(name, fn);
// after
module.component(name, options);
The name
argument is what we want to define our Component as, the options
argument is a definition Object passed into the component, rather than a function that we know so well in versions 1.4 and below.
I’ve prebuilt a simple counter
component for the purposes of this exercise in Angular1.4.x
which we’ll refactor into a version v1.5.0
build to use .component()
.
.directive('counter', function counter() {
return {
scope: {},
bindToController: {
count: '='
},
controller: function () {
function increment() {
this.count++;
}
function decrement() {
this.count--;
}
this.increment = increment;
this.decrement = decrement;
},
controllerAs: 'counter',
template: [
'<div class="todo">',
'<input type="text" ng-model="counter.count">',
'<button type="button" ng-click="counter.decrement();">-</button>',
'<button type="button" ng-click="counter.increment();">+</button>',
'</div>'
].join('')
};
});
A live embed of the 1.4.x
Directive:
We’ll continue building this alongside how we’d build the Angular 1.4 version to compare differences.
Function to Object, method name change
Let’s start from the top and refactor the function
argument to become an Object
, and change the name from .directive()
to .component()
:
// before
.directive('counter', function counter() {
return {
};
});
// after
.component('counter', {
});
Nice and simple. Essentially the return {};
statement inside the .directive()
becomes the Object definition inside .component()
- easy!
“scope” and “bindToController”, to “bindings”
In a .directive()
, the scope
property allows us to define whether we want to isolate the$scope
or inherit it, this has now become a sensible default to (usually) always make our Directives have isolate scope. So repeating ourselves each time just creates excess boilerplate. With the introduction of bindToController
, we can explicitly define which properties we want to pass into our isolate scope and bind directly to the Controller.
With the bindings
property on .component()
we can remove this boilerplate and simply define what we want to pass down to the component, under the assumption that the component will have isolate scope.
// before
.directive('counter', function counter() {
return {
scope: {},
bindToController: {
count: '='
}
};
});
// after
.component('counter', {
bindings: {
count: '='
}
});
Controller and controllerAs changes
Nothing has changed in the way we declare controller
, however it’s now a little smarter and has a default controllerAs
value of $ctrl
.
If we’re using a controller local to the component, we’ll do this:
// 1.4
{
...
controller: function () {}
...
}
If we’re using another Controller defined elsewhere, we’ll do this:
// 1.4
{
...
controller: 'SomeCtrl'
...
}
If we want to define controllerAs
at this stage (which will over-ride the default $ctrl
value), we’ll need to create a new property and define the instance alias:
// 1.4
{
...
controller: 'SomeCtrl',
controllerAs: 'something'
...
}
This then allows us to use something.prop
inside our template
to talk to the instance of the Controller.
Now, there are some changes in .component()
that make sensible assumptions and automatically create a controllerAs
property under the hood for us, and automatically assign a name based on three possibilities:
// inside angular.js
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
Possibility one uses this aptly named identifierForController
function that looks like so:
// inside angular.js
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()
:
// 1.5
{
...
controller: 'SomeCtrl as something'
...
}
This saves adding the controllerAs
property… however…
We can add the controllerAs
property to maintain backwards compatibility or keep it if that’s within your style for writing Directives/Components.
The third option, and better yet, completely removes all need to think aboutcontrollerAs
, and Angular automatically uses the name $ctrl
. For instance:
.component('test', {
controller: function () {
this.testing = 123;
}
});
The would-be controllerAs
definition automatically defaults to $ctrl
, so we can use$ctrl.testing
in our template
which would give us the value of 123
.
Based on this information, we add our controller
, and refactor our Directive into a Component by dropping the controllerAs
property:
// before
.directive('counter', function counter() {
return {
scope: {},
bindToController: {
count: '='
},
controller: function () {
function increment() {
this.count++;
}
function decrement() {
this.count--;
}
this.increment = increment;
this.decrement = decrement;
},
controllerAs: 'counter'
};
});
// after
.component('counter', {
bindings: {
count: '='
},
controller: function () {
function increment() {
this.count++;
}
function decrement() {
this.count--;
}
this.increment = increment;
this.decrement = decrement;
}
});
Things are becoming much simpler to use and define with this change.
Template
There’s a subtle difference in the template
property worth noting. Let’s add the template
property to finish off our rework and then take a look.
.component('counter', {
bindings: {
count: '='
},
controller: function () {
function increment() {
this.count++;
}
function decrement() {
this.count--;
}
this.increment = increment;
this.decrement = decrement;
},
template: [
'<div class="todo">',
'<input type="text" ng-model="$ctrl.count">',
'<button type="button" ng-click="$ctrl.decrement();">-</button>',
'<button type="button" ng-click="$ctrl.increment();">+</button>',
'</div>'
].join('')
});
The template
property can be defined as a function that is now injected with $element
and $attrs
locals. If the template
property is a function then it needs to return an String representing the HTML to compile:
{
...
template: function ($element, $attrs) {
// access to $element and $attrs
return [
'<div class="todo">',
'<input type="text" ng-model="$ctrl.count">',
'<button type="button" ng-click="$ctrl.decrement();">-</button>',
'<button type="button" ng-click="$ctrl.increment();">+</button>',
'</div>'
].join('')
}
...
}
That’s it for our Directive to Component refactor, however there are a few other changes worth exploring before we finish.
Inheriting behaviour using “require”
If you’re not familiar with “require”, check my article on using require.
{
...
require: {
parent: '^parentComponent'
},
controller: function () {
// use this.parent to access required Objects
this.parent.foo();
}
...
}
Inherited Directive or Component methods will be bound to the this.parent
property in the Controller.
One-way bindings
A new syntax expression for isolate scope values, for example:
{
...
bindings: {
oneWay: '<',
twoWay: '='
},
...
}
Read my full write-up about one-way bindings.
Disabling isolate scope
Components are always created with isolate scope. Here’s the relevant part from the source code:
{
...
scope: {},
...
}
Stateless components
There’s now the ability to create “stateless” components, read my in-depth article onstateless components in the .component()
method.
Essentially we can just use a template
and bindings
:
var NameComponent = {
bindings: {
name: '=',
age: '='
},
template: [
'<div>',
'<p>Name: </p>',
'<p>Age: </p>',
'</div>'
].join('')
};
angular
.module('app', [])
.component('nameComponent', NameComponent);
Sourcecode for comparison
Throughout the article I’ve referred to some Angular source code snippets to cross reference against. Here’s the source code below:
this.component = function registerComponent(name, options) {
var controller = options.controller || function() {};
function factory($injector) {
function makeInjectable(fn) {
if (isFunction(fn) || isArray(fn)) {
return function(tElement, tAttrs) {
return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
};
} else {
return fn;
}
}
var template = (!options.template && !options.templateUrl ? '' : options.template);
return {
controller: controller,
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
template: makeInjectable(template),
templateUrl: makeInjectable(options.templateUrl),
transclude: options.transclude,
scope: {},
bindToController: options.bindings || {},
restrict: 'E',
require: options.require
};
}
// Copy any annotation properties (starting with $) over to the factory function
// These could be used by libraries such as the new component router
forEach(options, function(val, key) {
if (key.charAt(0) === '$') {
factory[key] = val;
}
});
factory.$inject = ['$injector'];
return this.directive(name, factory);
};
Again, please note that Angular 1.5 isn’t released just yet, so this article uses an API thatmay be subject to slight change.
Upgrading to Angular 2
Writing components in this style will allow you to upgrade your Components using.component()
into Angular 2 very easily, it’d look something like this in ECMAScript 5 and new template syntax:
var Counter = ng
.Component({
selector: 'counter',
template: [
'<div class="todo">',
'<input type="text" [(ng-model)]="count">',
'<button type="button" (click)="decrement();">-</button>',
'<button type="button" (click)="increment();">+</button>',
'</div>'
].join('')
})
.Class({
constructor: function () {
this.count = 0;
},
increment: function () {
this.count++;
},
decrement: function () {
this.count--;
}
});
Exploring the Angular 1.5 .component() method的更多相关文章
- [AngularJS] Exploring the Angular 1.5 .component() method
Angualr 1.4: .directive('counter', function counter() { return { scope: {}, restrict: 'EA', transclu ...
- [Angular] Test Container component with async provider
The main idea for testing contianer component is to make sure it setup everythings correctlly. Call ...
- 【Angular】No component factory found for ×××.
报错现象: 用modal打开某个组件页面时报错 报错:No component factory found for UpdateAuthWindowComponent. Did you add it ...
- Angular(二) - 组件Component
1. 组件Component示例 2. Component常用的几个选项 3. Component全部的选项 3.1 继承自@Directive装饰器的选项 3.2 @Component自己特有的选项 ...
- [Angular 2] Exposing component properties to the template
Showing you how you can expose properties on your Controller to access them using #refs inside of yo ...
- [Angular 2] Component relative paths
Oingial aritial --> Link Take away: import { Component, OnInit } from '@angular/core'; @Component ...
- [Angular & Unit Testing] Testing a RouterOutlet component
The way to test router componet needs a little bit setup, first we need to create a "router-stu ...
- [Angular Tutorial] 13 -REST and Custom Services
在这一步中,我们将会改变我们获取数据的方式. ·我们定义一个代表RESTful客户端的自定义服务.使用这个客户端,我们可以用一种更简单的方法向服务端请求数据,而不用处理更底层的$httpAPI,HTT ...
- Angular 2 to Angular 4 with Angular Material UI Components
Download Source - 955.2 KB Content Part 1: Angular2 Setup in Visual Studio 2017, Basic CRUD applicat ...
随机推荐
- HDU3952-几何
题意:给n个水果,每个水果包括m个点(即m条边),判断一刀能切的最多的水果数目: 思路:数据比较小,n <= 10,m <= 10;可以暴力枚举,枚举两个水果的任意两个点,连成一条直线,然 ...
- 关于我和Github不得不说的一些小事
你好,我叫黄雅婷,学号是1413042031,网络工程142班.因为小时候家里有很多课外书,有关神话和科学方面的杂志和书籍等,所以从小就喜欢看书,现在比较不挑,什么书都喜欢看,就是给我本字典,我也能看 ...
- 怎样将某一类型标识为适合绑定到 System.Web.UI.WebControls.ObjectDataSource 对象的对象
1.页面的代码如下: body> <form id="form1" runat="server"> <div> </div& ...
- linxu c语言 fcntl函数和flock函数区别 【转】
flock和fcntl都有锁的功能,但他们还有一点小小的区别: 1.flock只能加全局锁,fcntl可以加全局锁也可以加局部锁. 2.当一个进程用flock给一个文件加锁时,用另一个进程再给这个文件 ...
- Day06_面向对象第一天
1.JAVA中的参数传递问题(掌握) 基本类型 形式参数的改变对实际参数没有影响 基本类型的实际参数和形式参数可以看作两个变量,这两个变量分别操作各自的数据,所以互不影响 引用类 ...
- dedecms内容页 上下篇 添加文章描述方法
dedecms5.7修改后 运行正常! 在根目录include文件夹中修改arc.archives.class.php文件,812行左右:为$query添加查询字段arc.description,分别 ...
- 解决f.lux总是弹框定位
解决f.lux总是弹框定位,直接导入成功定位的注册表文件即可. 以下保存为f.lux.reg 双击导入即可. Windows Registry Editor Version 5.00 [HKEY_CU ...
- Java-->把txt中的所有字符按照码表值排序
--> List 列表中的自动添加的多余空间长度该怎么去除呢?...(已解决,是char 数组中的空字符) package com.dragon.java.filesort; import ja ...
- JS控制文本框textarea输入字数限制
<html> <head> <title>JS限制Textarea文本域字符个数</title> <meta http-equiv="C ...
- 在web.xml中配置error-page
在web.xml中配置error-page 在web.xml中有两种配置error-page的方法,一是通过错误码来配置,而是通过异常的类型来配置,分别举例如下: 一. 通过错误码来配置error ...