2

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事实上是绑定到了controllerthis对象上面。但是在平时我们书写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属性发生变化并不会立即反应到内部的controllerthis对象上。在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版本bindToControllerboolen值,在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时,取决于自身的compilelink参数定义的规则:
当定义directive的时候同时定义了complie,pre-link,post-link3个参数的时候

    <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,默认进行了compilepre-link的阶段,在post-link里面就可以直接使用绑定在实例上的scope,ele,attrs
如果在你的程序里面不需要使用scope,不需要$watch其他的值,仅仅提供模板实例的话,可以直接使用compile。这个时候你是不能对DOM有任何操作的。
除此之外,如果你不需要实例元iElem,那么也可以不用link函数。

但当你同时书写compilelink函数(pre-link或者post-link)的时候,一定要在compile函数里面返回link函数,因为如果compile被定义的时候link属性被忽略了。

参考资料


苹果小萝卜
5.1k 声望356 粉丝

Github: [链接]