wiki
wiki copied to clipboard
[译] 探索Angular 1.5 之component() 方法
Angular 1.5 引入了 .component()
辅助方法, 它的定义比 .directive()
更简单。 .component()
允许开发者以更接近Angular2的方式写Angular1的代码,方便以后无痛地升级到Angular2。
.component()
比 .directive()
使用了更加简洁抽象的语法。
从 .directive()
到 .component()
以下是它们的语法差异:
// before
module.directive(name, fn);
// after
module.component(name, options);
先用Angular 1.4.x 写一个简单的计数器,下边再用 v1.5.0 重构它。
.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('')
};
});
jsfiddle : https://jsfiddle.net/toddmotto/avdezer7/
方法名改变,并且Function 参数变为 Object
开始自上而下重构这个示例:
// before
.directive('counter', function counter() {
return {
};
});
// after
.component('counter', {
});
.directive
中本质上需要返回一个函数,而 .component
只需要传一个对象了
scope
和 bindToController
变为 bindings
// before
.directive('counter', function counter() {
return {
// scope 用于创建独立作用域或继承父级作用域
// 由于这个选项用来创建独立作用域基本上是不可或缺的,所以每次都要写就很繁琐了
scope: {},
// bindToController 可以直接定义那些想传入独立作用域的属性,并把它们绑定到controller上
bindToController: {
count: '='
}
};
});
// after
.component('counter', {
// 用bindings可以简单地定义要传递哪些属性到component中,且并component拥有独立作用域
bindings: {
count: '='
}
});
Controller
和 controllerAs
的变化
在定义controller的方式上倒没有什么变化,唯一一点不同就是 controllerAs
多了一个默认值: $ctrl
在1.4中直接定义 controller
{
...
controller: function () {}
...
}
或者在别的地方定义,此处引用
{
...
controller: 'otherCtrl'
...
}
又或者用 controllerAs
起别名
{
...
controller: 'otherCtrl'
controllerAs: 'other'
...
}
然后就可以在模板中使用 other.prop
之类来访问Controller实例。
在 .component()
中就不会那么麻烦了,在我们没明确指定Controller实例别名时,它会自动用3种方式创建 controllerAs
属性,Angular中相关源码如下:
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
其中第一个 identifierForController
会中controller属性为字符串时(controller: 'SomeCtrl as something'
), 提取 as
后边的名字,源码如下:
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];
}
}
第二个 controllerAs
用于当controller属性为function时的情况。
第三个 '$ctrl' 默认值让我们可以忽略掉 controllerAs
了
.component('test', {
controller: function () {
// 所以可以直接在模板中通过 $ctrl.testing访问
this.testing = 123;
}
});
说了那么多,终于可以在重构中把 controllerAs
干掉了:
// before
.directive('counter', function counter() {
return {
scope: {},
bindToController: {
count: '='
},
controller: function () {
...
},
controllerAs: 'counter'
};
});
// after
.component('counter', {
bindings: {
count: '='
},
controller: function () {
...
}
});
用 require
继承
{
...
require: {
parent: '^^parentComponent'
},
controller: function () {
// 用 this.parent 访问依赖对象(在controller的parent属性上绑定)
this.parent.foo();
}
...
}
单向绑定
用新的表达式语法创建独立作用域:
{
...
bindings: {
oneWay: '<',
twoWay: '='
},
...
}
参见:One-way data-binding in Angular 1.5
Lifecycle hooks
Each component has a well-defined set of lifecycle hooks, read the full article here. 每个组件都预定义了一组 Lifecycle hooks(生命周期钩子):
- $onInit
- $postLink
- $onChanges
- $onDestroy
参见:Comprehensive dive into Angular 1.5 lifecycle hooks
关闭独立作用域
Component 始终都会创建独立作用域,相关源码部分如下:
{
...
scope: {},
...
}
无状态的components
参见:Stateless Angular components
基本上我们可以只用 template
和 bindings
:
var NameComponent = {
bindings: {
name: '<',
age: '<'
},
template: [
'<div>',
'<p>Name: </p>',
'<p>Age: </p>',
'</div>'
].join('')
};
angular
.module('app', [])
.component('nameComponent', NameComponent);
源码比较
https://github.com/angular/angular.js/blob/v1.5.7/src/ng/compile.js#L1112-L1165
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);
var ddo = {
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 annotations (starting with $) over to the DDO
forEach(options, function(val, key) {
if (key.charAt(0) === '$') ddo[key] = val;
});
return ddo;
}
// TODO(pete) remove the following `forEach` before we release 1.6.0
// The [email protected] looks for the annotations on the controller constructor
// Nothing in Angular looks for annotations on the factory function but we can't remove
// it from 1.5.x yet.
// Copy any annotation properties (starting with $) over to the factory and controller constructor functions
// These could be used by libraries such as the new component router
forEach(options, function(val, key) {
if (key.charAt(0) === '$') {
factory[key] = val;
// Don't try to copy over annotations to named controller
if (isFunction(controller)) controller[key] = val;
}
});
factory.$inject = ['$injector'];
return this.directive(name, factory);
};
升级到Angular 2
用这种方式写组件很容易地升级到Angular 2。用 ECMAScript 5 和新模板语法写的示例:
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--;
}
});
原文 https://toddmotto.com/exploring-the-angular-1-5-component-method/
不错不错 写得不错!
在标题上写明是“翻译”可能会更好一些