Controller As
angular
.module('app', [])
.controller('DemoController', DemoController);
function DemoController() {
this.name = 'XL';
this.sayName = function() {
return this.name;
}
}
<div ng-controller='DemoController as vm'>
{{vm.name}}
</div>
在js部分书写控制器的时候,更像是在写构造函数,然后在view层
实例化一个ViewModule
。那么在view
里面可以直接调用挂载在这个vm上的属性和方法。
这样的写法的好处就是避免在嵌套的controller
里面使用$parent
去获取父controller
里面的方法或值。因为在angular里面scope是基于原型进行继承的。
<div ng-controller='parentController'>
{{name}}
<div ng-controller='childController'>
{{name}}
</div>
</div>
angular
.module('app')
.controller('parentController', parentController)
.controller('childController', childController);
//都使用的推断式注入
function parentController($scope) {
$scope.name = 'XL';
}
function childController($scope) {
$scope.name = 'xl';
}
最后在视图里面输出'XL', 'xl'。如果我要获取的是父控制器里面的属性值,那么我只能$scope.$parent
去获取,嵌套的controller
多了怎么办- -,使用controller as
可以有效的避免这个问题.
如果使用这种controller as
的写法的话,尽量避免在controller里面使用$scope,除非遇到$emit,$broadcase,$on, $watch
。
<input ng-model='vm.title'>
function SomeController($scope, $log) {
var vm = this;
vm.title = 'Some Title';
$scope.$watch('vm.title', function(current, original) {
$log('vm.title was %s', original);
$log('vm.title is now %s', current);
});
}
this vs $scope
angular
.module('app', [])
.controller('parentController', parentController);
function parentController($scope) {
this.sayName = function() {
console.log('this');
}
}
<div ng-controller='parentController as vm'>
<button ng-click='vm.sayName()'>BtnA</button>
<button ng-click='sayName()'>BtnB</button>
</div>
结果:
点击
BtnA
后能输出'this'
,点击
BtnB
后不输出任何东西。
事实上:
当书写Controller
的时候,即在写controller constructor
,使用controller as
语法,当在view
里面实例化controller
后(vm),this
便指向这个实例化vm
,然后就可以在view
里面调用vm
所拥有的属性和方法。在本例中即vm
所拥有的sayName()
方法
如果
function parentController($scope) {
this.sayName = function() {
console.log('this');
}
$scope.sayName = function() {
console.log('$scope');
}
}
在controller constructor
里面定义$scope.sayName
方法,那么点击BtnB
的时候可以输出$scope
。
事实上:
注入$scope
后,即提供了一个model
,可以在这个model
上面绑定属性和方法。那么在view
里面声明的controller
里面可以访问到。
综上,在this
上面挂载的方法其实是在controller constructor
上面挂载的方法,必须要通过controller
实例去访问。在$scope上面挂载的方法是在模型上面挂载的,因为在directive的pre-link阶段(见下面的compile vs link)是将$scope绑定到DOM上,因此可以直接在view层
访问绑定在$scope
的方法。
除此之外,this
和$scope
另外一个区别就是指向的问题:
<div ng-controller='parentController' id='parentBox'>
<p ng-click='logThisAndScope()'>log 'this' and $scope</p> - parent scope
<div ng-controller='childController' id='childBox'>
<p ng-click='logThisAndScope()'>log 'this' and $scope</p> - child scope
</div>
</div>
然后仅仅在parentController
上面挂载方法:
$scope.logThisAndScope = function() {
console.log(this, $scope);
}
首先因为controller
嵌套同时是在$scope
上面挂载方法,因此父元素和子元素点击都会输出this
和$scope
的内容,在这个实例当中this
的指向是不同的,一个是parentController
另外一个是childController
,$scope
的指向是一样的,同时指向的是绑定在id为parentBox的DOM内的$scope。
The problem with controllerAs in Directives
当使用controllerAs
语法的时候, controller scope
事实上是绑定到了controller
的this对象
上面。但是在平时我们书写directive
的时候会创建独立的作用域。
app.directive('someDirective', function() {
return {
scope: {
oneWay: '@',
twoWay: '=',
expr: '&'
}
}
})
接下来我们创建一个拥有独立作用域,有自己的控制器的directive
。
app.directive('someDirective', function() {
return {
scope: {},
controller: function() {
this.name = 'Pascal';
},
controllerAs: 'ctrl',
template: '<div>{{ctrl.name}}</div>'
}
})
但是,如果这个name
属性是一个可以和父作用域共享的呢?当然我们立马想到的是
app.directive('someDirective', function() {
return {
scope: {
name: '='
},
....
}
})
如果外部的name
属性发生变化并不会立即反应到内部的controller
的this对象
上。在1.2版本里面处理这种情况就是使用$scope服务
上挂载的$watch
方法去监听name
属性的变化。
app.directive('someDirective', function() {
return {
scope: {
name: '='
},
controller: function($scope) {
this.name = 'Pascal';
$scope.$watch('name', function(newValue){
this.name = newValue;
}.bind(this));
//这个地方要注意this的指向
}
}
})
bindToController
1.3版本里面directive
出现了一个新的配置对象bindToController
,顾名思义绑定到controller
上面,当directive
使用独立作用域以及controllerAs
语法的时候,而且bindToController
这个值被设置为true
的时候,这个组件的属性都被绑定到controller上了而不是scope上面。
这意味着,当controller被实例化后,独立作用域上绑定属性的初始值都可以通过this对象来访问到,未来这个属性的值发生变化后都能被检测的到。
app.directive('someDirective', function () {
return {
scope: {
name: '='
},
controller: function () {
this.name = 'Pascal';
},
controllerAs: 'ctrl',
bindToController: true,
template: '<div>{{ctrl.name}}</div>'
};
});
1.4版本的语法升级:
在1.3版本bindToController
为boolen
值,在1.4版本中为一个对象,即如果想将独立作用域上的值绑定到controller
上面,可以直接在bindToController
这个对象上进行配置。
app.directive('someDirective', function () {
return {
scope: {},
bindToController: {
someObject: '=',
someString: '@',
someExpr: '&'
},
controller: function () {
this.name = 'Pascal';
},
controllerAs: 'ctrl',
template: '<div>{{ctrl.name}}{{ctrl.someObject}}</div>'
};
});
//如果父作用域里面的someObject属性发生变化,会随时反应到这个directive的template里面。
complie vs link
Angularjs在处理directive
时,取决于自身的compile
和link
参数定义的规则:
当定义directive
的时候同时定义了complie
,pre-link
,post-link
3个参数的时候
<level-one>
<level-two>
Hello {{XL}}
<level-three>
Hello {{Sugar}}
<level-three>
<level-two>
</level-one>
var app = angular.module('app', []);
function createDirective(name) {
return function() {
return {
restrict: 'E',
compile: function(tElem, tAttrs) {
console.log(name + ': complie');
},
return {
pre: function(scope, tElem, iAttrs) {
console.log(name + ': pre link');
},
post: function(scope, tElem, iAttrs) {
console.log(name + ': post link');
}
}
}
}
}
app.directive('levelOne', createDirective('levelOne'));
app.directive('levelTwo', createDirective('levelTwo'));
app.directive('levelThree', createDirective('levelThree'))
结果:
angularjs一开始compile所有原生指令和自定义指令,complile阶段还没有绑定scope.link阶段分为pre-link和post-link阶段。
从结果看来compile阶段
和pre-link阶段
的顺序一样,但是post-link执行顺序
正好相反。
修改代码:
function createDirective(name) {
return function() {
return {
restrict: 'E',
compile: function(tElem, tAttrs) {
console.log(name + ': complie =>' + tElem.html());
},
return {
pre: function(scope, iElem, iAttrs) {
console.log(name + ': pre link =>' + iElem.html());
},
post: function(scope, iElem, iAttrs) {
console.log(name + ': post link =>' + iElem.html());
}
}
}
}
}
结果:
现在再看下输出的信息,特别是在pre-link阶
段,虽然和compile
一样输出元素的顺序是一样的,但是元素中出现了属性class='ng-binding'
,事实上在compile阶段DOM元素仍然是最初html标记所创建的DOM元素,它是模板元素(template element)
的实例元素(instance element)
.pre-link阶段提供一个scope
给这个实体,这个实体可以是全新的scope
,继承的scope
或者是孤立的scope
,取决于directive定义的scope属性。
post-link阶段
:当实例元素初始化完成(compile阶段)和绑定scope(pre-link阶段)完成后就可以进行post-link(DOM)操作。注意这个地方执行顺序是从子元素开始再到父元素的。即在level-one
执行post-link
阶段前确保level-two.level-three
执行完毕。
compile阶段:
/*
* @param tElem - 模板元素
* @param tAttr - 模板元素的属性
*/
compile: function(tElem, tAttrs) {
}
pre-link阶段
/*
* @param scope - 连接于此的实例的scope
* @param iElem - 实例元素
* @param iAttr - 实例元素的属性
*/
function (scope, iElem, iAttr) {
}
post-link阶段
/*
* @param scope - 连接于此实例的scope
* @param iElem - 实例元素
* @param iAttr - 实例元素的属性
*/
function (scope, iElem, iAttr) {
}
where to use compile or link?
之前的写法都是直接用link
,默认进行了compile
和pre-link
的阶段,在post-link
里面就可以直接使用绑定在实例上的scope,ele,attrs
。
如果在你的程序里面不需要使用scope
,不需要$watch
其他的值,仅仅提供模板实例的话,可以直接使用compile
。这个时候你是不能对DOM有任何操作的。
除此之外,如果你不需要实例元iElem
,那么也可以不用link函数。
但当你同时书写compile
和link
函数(pre-link或者post-link)
的时候,一定要在compile
函数里面返回link
函数,因为如果compile
被定义的时候link
属性被忽略了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。