• AngularJS进阶学习


    参考:http://www.hubwiz.com/class/54f3ba65e564e50cfccbad4b

    1. AJAX:Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)可以与服务器交换数据并更新部分网页,而无需重新加载整个页面。

    2. JQuery:浏览器里原生的JavaScript有点像汇编语言,不同的浏览器就像不同的CPU架构,汇编语言各有千秋,这让前端开发者很恼火。聪明人很快发现了这个痛点,于是,抹平浏览器差异的jQuery库出现了。

    3. AngularJS:jQuery看成库的话,AngularJS则是一个框架,它以一种特殊的方式向jQuery表达了敬意:内置精简版的jQuery - jqLite。如果某种原因你不愿意使用jqLite,也可以在AngularJS之前引入jQuery库。 AngularJS自动地将jqLite升级成jQuery。

    指令directive:类似ng-app,ez-clock这样的非标准HTML标签叫做指令(JavaScript文件)

    模板:包含AngularJS标记的html文件被称为模板

    编译:将指令解析成为浏览器可以理解的html元素和脚本的过程叫做编译

    视图:通过编译器解析处理模板文件,生成的结果就是视图

    一般过程:构造声明式界面模板,来提出需求,继而抽取并实现自定义指令。

    1. 作用域/Scope

    在HTML模板中,我们用了两个内置指令:

    • ng-app指令会在启动引导时创建一个$rootScope对象。
    • ng-init指令用来在作用域上初始化变量,这个指令将在$rootScope上建立sb对象。

    1.1 层级的作用域

    在默认情况下,一个DOM子元素不会创建新的作用域,也就是说,这个子元素所对应的 scope对象,其实就是它的最近一级的祖先对象对应的scope对象。

    有些指令会导致创建新的作用域,比如ng-controller。如果在一个DOM对象上创建了新 的作用域,那么这个scope对象的原型是其最近一级的组件对象的scope对象。

    1.2 监听Scope数据的变化

    编译仅仅在启动引导时执行一次,这意味着我们的link函数只会被调用一次,所以需要监听数据变化来使得界面和数据一致。

    1. $watch(watchExpression, listener, [objectEquality]);

    $watch方法要求传入三个参数:

    • watchExpression - 要监听的表达式,比如:"sb"
    • listener - 变化发生时的回调函数,AngularJS将向这个函数传入新值和旧值
    • objectEquality - 如果监听表达式的值是一个对象,应当将这个参数置为true。

    以上便建立了从 数据到界面的单向绑定

    1.3 修改Scope上的数据

    挂接监听函数(示例中使用keyup事件), 在监听函数中实现对sb变量的修改。

    以上便建立了从 界面到数据的单向绑定

    angular.module("ezstuff",[])
    .directive("ezNamecardEditor",function(){
        return {
            restrict : "E",
            template : "<ul class='nceditor'></ul>",
            replace : true,
            link : function(scope,element,attrs){
                //获得变量名称
                var model = attrs.data;
    
                //展开HTML模板,使用field属性标记对应字段
                element.append("<li>name : <input type='text' field='name'></li>")
                    .append("<li>gender : <input type='text' field='gender'></li>")
                    .append("<li>age : <input type='text' field='age'></li>");
    
                //监听DOM事件,变化时修改变量值
                element.find("input").on("keyup",function(ev){
                    var field = ev.target.getAttribute("field");
                    scope[model][field] = ev.target.value;
                    //将对scope的修改进行传播
                    scope.$apply("");
                });
            }
        };
    })
    .directive("ezLogger",function(){
        return {
            restrict : "A",
            link : function(scope,element,attrs){
                var model = attrs.data;
    
                scope.$watch(model,function(nv){
                    var cnt = JSON.stringify(nv,null,"    ");
                    element.html("<pre>"+cnt+"</pre    ");
                },true);
            }
        };
    });

    1.4 数据变化的传播

    数据绑定有两个方向:

    • 数据 → 界面:我们使用scope对象的$watch()方法监听数据的变化,来更新界面。
    • 界面 → 数据:我们在界面的DOM对象上监听变化事件,来更新数据,并通过$apply()方法传播变化。
      • $watch()

      每个scope对象都维护了一个私有的监听队列,每次当我们在scope上执行一次$watch方法,就相当于 向这个监听队列里塞入一个监听函数。

      • $apply()

      为了捕捉对数据的修改,AngularJS要求开发者使用scope对象的$apply方法对数据进行修改, $apply方法内部会自动地调用监听队列里的监听函数

      //方法1:直接修改sb对象. 不会自动触发监听函数
      scope.sb.name = 'Tonny';
       
      //方法2:使用scope的$apply方法,在数据修改后会自动触发监听函数
      scope.$apply("sb.name = 'Tonny'");
       
      //方法3:直接修改sb对象,然后调用$apply方法来传播变化。
      scope.sb.name = 'Tonny';
      scope.$apply("");

    2. 依赖注入:注入器

    在依赖注入的模式下,所有的组件必须通过容器才能相互访问,这导致了在AngularJS中, 你必须通过一个中介才能获得某个组件的实例对象:

    var injector = angular.injector(['ng']);
    injector.invoke(function($http){
        //do sth. with $http
    });

    这个中介,就是依赖注入模式中的容器,在AngularJS中,被称为:注入器

    AngularJS的组件之间不可以互相直接调用,一个组件必须通过注入器才 可以调用另一个组件。这些组件有一个统称 - 供给者/provider。

    配方其实就是:名称+类构造函数。AngularJS启动时,这些provider首先使用其配方在注入器内注册。比如,http请求服务组件封装在$httpProvider类内,它通过"$http"这个名字在注入器内注册。其他组件,比如一个用户的控制器,如果需要使用http功能,使用"$http"这个名字 向注入器请求,就可以获得一个http服务实例了。

    2.1 注册服务组件

    从injector的角度看,组件就是一个功能提供者,因此被称为供给者/Provider。 在AngularJS中,provider以JavaScript类(构造函数)的形式封装。

    Provider类要求提供一个$get函数(类工厂),injector通过调用该函数, 就可以获得服务组件的实例。

    名称和类函数的组合信息,被称为配方。injector中维护一个集中的配方库, 用来按需创建不同的组件。这个配方库,其实就是一个Hash对象,key就是服务名称,value 就是类定义。

    2.2 调用组件服务:获得注入器对象,而后通过注入器调用API

    获取注入器的方法有两个:

    • 创建一个新的注入器

    可以使用angular.injector()创建一个新的注入器:

    angular.injector(modules, [strictDi]);
    • 获取已经创建的注入器

    如果AngularJS框架已经启动,那么可以使用DOM对象的injector()方法获 得已经创建的注入器:

    var element = angular.element(dom_element);
    var injector = element.injector();

    注入器有两个方法可供进行API调用:invoke()和get()。

    • invoke()

    使用注入器的invoke()方法,可以直接调用一个用户自定义的函数体,并通过函数参数 注入所依赖的服务对象,这是AngularJS推荐和惯例的用法:

    angular.element(document).ready(function(){
        angular.injector(["ng","ezstuff"]).invoke(function(ezHello){
            //将ezHello实例对象转成字符串显示出来
            var e = document.querySelector("#logger");
            angular.element(e).text(ezHello);
        });
    });
    • get()

    也可以使用注入器的get()方法,获得指定名称的服务实例:

    angular.element(document).ready(function(){
        //直接通过注入器获取ezHello实例对象
        var myHello = angular.injector(["ng","ezstuff"]).get("ezHello");
        //将ezHello实例对象转成字符串显示出来
        var e = document.querySelector("#logger");
        angular.element(e).text(myHello);
    });

    2.3 注入的方式和原理

    有两种方法告知注入器需要注入的服务对象:参数名注入和依赖数组注入。

    • 参数名注入

    AngularJS在执行invoke()函数时,将待注入函数定义转化为字符串,通过 正则表达式检查其参数表,从而发现并注入所所依赖的服务对象:

    //myfunc通过参数表声明这个函数依赖于"$http"服务
    var myfunc = function($http){   
     //do sth. with $http
    };
    injector.invoke(myfunc);
    //myfunc的定义将被转化为字符串进行参数名检查

    example:

    //在ezstuff模块上登记一个服务ezHello
    angular.module("ezstuff",[])
    .provider("ezHello",function(){
        //$get方法是一个类工厂,返回服务的实例
        this.$get = function(){
            return "hello,world!";
        };
    });
    
    angular.element(document).ready(function(){
      var myfunc = function(ezHello){
            //将ezHello实例对象转成字符串显示出来
            var e = document.querySelector("#logger");
            angular.element(e).text(ezHello);
        };
        angular.injector(["ng","ezstuff"]).invoke(myfunc);
    });

    这样有一个问题,就是当我们对JavaScript代码进行压缩处理时,$http可能会被 变更成其他名称,这将导致注入失败。

    • 依赖数组注入

    AngularJS采用依赖项数组的方法解决代码压缩混淆产生的问题。这时传入invoke()的 是一个数组,数组的最后一项是实际要执行的函数,其他项则指明需要向该函数注入 的服务名称。注入器将按照数组中的顺序,依次向函数注入依赖对象。

    采用这种方法,待注入函数的参数表的名称就无关紧要了:

    
    
    //myfunc依赖于"$http"和"$compile"服务
    var myfunc = ["$http","$compile",function(p1,p2){
        //do sth. with p1($http),p2($compile)
    }];
    injector.invoke(myfunc);

    example:

    //在ezstuff模块上登记一个服务ezHello
    angular.module("ezstuff",[])
    .provider("ezHello",function(){
        //$get方法是一个类工厂,返回服务的实例
        this.$get = function(){
            return "hello,world!";
        };
    });
    
    angular.element(document).ready(function(){
      var myfunc = ["ezHello",function(hhh){
            //将ezHello实例对象转成字符串显示出来
            var e = document.querySelector("#logger");
            angular.element(e).text(hhh);
        }];
        angular.injector(["ng","ezstuff"]).invoke(myfunc);
    });

    3. 启动引导

    当你在HTML文件中引入angular.min.js时,AngularJS只是建立了一个全局的 angular对象,这个对象有一些方法可供开发者调用,但应用的框架还没有建立。

    在这个阶段,AngularJS还只是一个,和jQuery类似,你可以使用angular.element() 操作DOM,也可以使用angular.injector()创建注入器... 但是,你定义的指令,你 创建的控制器,你封装的服务,你开发的模板...所有这些组件,还静静地躺在那里, 没有被整合在一起。

    我们说,框架还没有运转起来,现在还是库阶段

    只有通过启动引导,AngularJS框架才开始将那些组件拼接在一起,应用才真正 开始运转。

    3.1 自动引导启动框架

    就像你看到的那样,如果HTML模板中有某个标签有ng-app属性,那么当DOM树建立成功后, AngularJS就会自动进入引导过程,启动整个框架:

    image

    3.2 手工引导启动框架

    在大多数情况下,我们都使用ng-app指令来进行自动引导启动,但是如果一个HTML文件中 有多个ng-app,AngularJS只会自动引导启动它找到的第一个ng-app应用,这是需要手工引导 的一个应用场景。

    我们可以利用angular.bootstrap()方法进行手动引导:

    1. angular.bootstrap(element, [modules], [config]);

    bootstrap方法有三个参数:

    • element : 一个DOM元素,以这个元素为Angular应用的根,等同自动引导时ng-app所在 的元素。这个参数是必须的。比如:document、document.body等。
    • modules : 引导时需要载入的模块数组。比如:[]、["ezstuff"]等。由于我们的HTML中引用 了ezstuff模块中定义的ez-duang指令,所以,我们需要指定载入ezstuff模块。
    • config :引导配置项,可选。我们先忽略。
    angular.element(document).ready(function(){
        var e = document.querySelector("#bootstrap");
        angular.element(e)
        .on("click",function(){
            angular.bootstrap(document,["ezstuff"]); 
        })
    })
    ;
    angular.module("ezstuff",[])
    .directive("ezDuang",function(){
        return {
            restrict : "E",
            template : "<img src='http://ww4.sinaimg.cn/bmiddle/757eb2ffjw1eptcr4qobjg209205dthh.gif'>"
        };
    });

    3.3 引导的一般过程

    3.3.1 引导第1步:创建注入器

    引导过程使AngularJS从转变成了一个框架AngularJS深入骨髓地使用着依赖注入,那么,在引导过程 之初,首先需要创建一个注入器就毫不奇怪了。注入器是通向AngularJS所有功能的入口,而AngularJS的功能实现,是通过模块的方式组织的。所以, 在创建注入器的时候,需要告诉AngularJS载入哪些模块(ng模块是内置载入的,不需要显式指定)。

    image

    在自动启动引导的场景下,可以给ng-app赋值以指定一个需要载入的模块,比如:

    • ng-app = "ezstuff"

    在手动启动引导的场景下,通过bootstrap方法的第二个参数指定需要载入的模块,比如:

    • angular.bootstrap(document,["ezstuff"]);

    INSIDE:无论自动启动还是手工启动,最终都是调用angular对象上的injector()方法创建了一个 注入器,然后把这个注入器存入了根对象的data里:

    1. var injector = angular.injector(["ng","ezstuff"]);
    2. angular.element(document).data("$injector",injector);

    3.3.2 引导第2步:创建根作用域

    scope对象是AngularJS实现数据绑定的重要服务,所以,在引导启动建立了注入器之后, AngularJS马上在应用的根节点上创建一个根作用域:$rootScope对象。

    image

    如果是自动引导启动,那么ng-app所在的DOM节点对应着根作用域。如果是手工引导启动, 那么在bootstrap方法中指定的第一个参数就对应着根作用域。

    无论哪一种情况,一旦$rootScope对象创建成功,AngularJS就将这个对象存储到根节点 的data中:

    • var rootScope = injector.get("$rootScope");
      angular.element(document).data("$rootScope",rootScope);

    我们可以使用如下的方法查看这个对象:

    • angular.element(approot).data("$rootScope");

    3.3.3 引导第3步:编译DOM子树

    引导过程的最后一步,是以ng-app所在DOM节点为根节点,对这棵DOM子树进行编译。

    image

    编译过程通常借助于指令,完成这几种操作:

    1. 对DOM对象进行变换。
    2. 在DOM对象上挂接事件监听。
    3. 在DOM对象对应的scope对象上挂接数据监听。

    手动引导需要如下:

    • var compile = injector.get("$compile")
      compile(document)(rootScope);

    3.4 编译器/$compile

    编译器$compile是一个AngularJS的内置服务,它负责遍历DOM树来查找匹配指令, 并调用指令的实现代码进行处理。

    HTML编译包括3个步骤:

    • 匹配指令

    $compile遍历DOM树,如果发现有元素匹配了某个指令,那么这个指令将被加入 该DOM元素的指令列表中。一个DOM元素可能匹配多个指令。

    • 执行指令的编译函数

    当一个DOM元素的所有指令都找齐后,编译器根据指令的优先级/priority指令进行排序。 每个指令的compile函数被依次执行。每个compile执行的结果产生一个link函数,这些 link函数合并成一个复合link函数。

    • 执行生成的链接函数

    $compile通过执行指令的link函数,将模板和scope链接起来。结果就是一个DOM视图和scope对象模型 之间的动态数据绑定。

    3.5 指令/directive

    指令可以放置在元素名、属性、CSS类名称及备注中。指令的实现本质上就是一个类工厂,它返回一个指令定义对象,编译器根据这个指令定义对象进行操作。

    image

    指令的规范化

    AngularJS在进行匹配检测之前,首先对HTML元素的标签和属性名转化成规范的驼峰式字符串

    1. 去除名称前缀的x-和data-
    2. 以: , - 或 _ 为分割符,将字符串切分成单词,除第一个单词外,其余单词首字母大写
    3. 重新拼接各单词

    例如,下面的写法都等效地匹配ngBind指令:

    1. <span ng-bind="name"></span> <br>
    2. <span ng:bind="name"></span> <br>
    3. <span ng_bind="name"></span> <br>
    4. <span data-ng-bind="name"></span> <br>
    5. <span x-ng-bind="name"></span> <br>

    4. 控制器

    在AngularJS中,实现数据绑定的核心是scope对象。没有控制器/controller,我们没有地方定义业务模型

    控制器让我们有机会在scope上定义我们的业务逻辑,具体说,可以使用控制器:

    1. 对scope对象进行初始化
    2. 向scope对象添加方法

    4.1 在模板中声明控制器

    在一个HTML元素上使用ng-controller指令,就可以引入一个控制器对象:

    1. <div ng-controller="myController">...</div>

    4.2 控制器的实现

    控制器实际上就是一个JavaScript的类/构造函数

    1. //控制器类定义
    2. var myControllerClass = function($scope){
    3. //模型属性定义
    4. $scope.text = "...";
    5. //模型方法定义
    6. $scope.do = function(){...};
    7. };
    8. //在模块中注册控制器
    9. angular.module('someModule',[])
    10. .controller("myController",myControllerClass);

    4.3 控制器的一次性

    控制器构造函数仅在AngularJS对HTML文档进行编译时被执行一次。从这个角度看, 就更容易理解为何将控制器称为对scope对象的增强:一旦控制器创建完毕,就意味着scope对 象上的业务模型构造完毕,此后就不再需要控制器了- scope对象接管了一切。

    4.4 控制器对scope的影响

    4.4.1 控制器总是会创建新的scope对象

    ng-controller指令总是创建一个新的scope对象:

    controller

    在图中,我们看到:

    1. ng-app指令引发$rootScope对象的创建。开始时,它是一个空对象
    2. body元素对应的scope对象还是$rootScope。ng-init指令将sb对象挂在了$rootScope上。
    3. div元素通过ng-controller指令创建了一个新的scope对象,这个对象的原型是$rootScope。
    4. 因为原型继承的关系,在do函数中对sb的引用指向$rootScope.sb

    4.4.2 初始化$scope对象

    通常在应用启动时,需要初始化scope对象上的数据模型。我们之前曾使用ng-init指令进行初始化, 而使用控制器则是更为规范的做法。

    右边的示例定义了一个ezController,利用这个控制器,我们对业务模型进行了初始化赋值:

    init viewmodel

    请注意,控制器仅仅负责在编译时在scope对象上建立视图对象vm,视图对象和模板的绑定则是由 scope负责管理的。

    4.4.2 向cope对象添加方法

    业务模型是动态的,在数据之外,我们需要给业务模型添加动作。

    在之前建立的业务模型上,我们增加一个随机挑选的方法:shuffle,这个方法负责 从一个小型的名人库中随机的选择一个名人来更新模型的sb属性:

    action added

    通过在button上使用ng-click指令,我们将模型的shuffle方法绑定到了鼠标点击 事件上。试着用鼠标点击【shuffle】按钮,我们的模型将从库里随机的选出一个 名人,显示在视图里。

    控制器的设计出发点是封装单个视图的业务逻辑,因此,不要进行以下操作:

    • DOM操作

    应当将DOM操作使用指令/directive进行封装。

    • 变换输出形式

    应当使用过滤器/filter对输出显示进行转化。

    • 跨控制器共享代码

    对于需要复用的基础代码,应当使用服务/service进行封装

    5. 封装服务

    5.1 创建服务组件

    在AngularJS中创建一个服务组件很简单,只需要定义一个具有$get方法的构造函数, 然后使用模块的provider方法进行登记:

    1. //定义构造函数
    2. var myServiceProvider = function(){
    3. this.$get = function(){
    4. return ....
    5. };
    6. };
    7. //在模块中登记
    8. angular.module("myModule",[])
    9. .provider("myService",myServiceProvider);

    5.2 可配置的服务

    有时我们希望服务在不同的场景下可以有不同的行为,这意味着服务可以进行配置

    比如,我们希望小计算器可以根据不同的本地化区域,给计算结果追加货币符号前缀, 那么需要在这个服务创建之前,首先配置本地化区域的值,然后在具体的计算中, 根据这个值选择合适的货币符号。

    AngularJS使用模块的config()方法对服务进行配置,需要将实例化的服务提供者 (而不是服务实例)注入到配置函数中:

    1. angular.module("myModule",[])
    2. .config(["myServiceProvider",function(myServiceProvider){
    3. //do some configuration.
    4. }]);

    注意:服务提供者provider对象在注入器中的登记名称是:服务名+Provider。 例如: $http的服务提供者实例名称是"$httpProvider"。

    function doCalc(){
        var injector = angular.injector(["ezstuff"]),
            mycalculator = injector.get("ezCalculator"),
            ret = mycalculator.add(3,4);
    
        document.querySelector("#result").innerText = ret;
    }
    
    angular.module("ezstuff",[])
        .provider("ezCalculator",function(){
            var currency = "$";
            this.setLocal = function(l){
                var repo = {
                    "CN":"¥",
                    "US":"$",
                    "JP":"¥",
                    "EN":"€"
                };
                if(repo[l]) currency = repo[l];
            };
            this.$get = function(){
                return {
                    add : function(a,b){return currency + (a+b);},
                    subtract : function(a,b){return currency + (a-b);},
                    multiply : function(a,b){return currency + (a*b);},
                    divide: function(a,b){return currency + (a/b);}
                }
            };
        })
        .config(function(ezCalculatorProvider){
            ezCalculatorProvider.setLocal("CN");
        });

    5.3 服务定义语法糖

    使用模块的provider方法定义服务组件,在有些场景下显得有些笨重。AngularJS友好 地提供了一些简化的定义方法,这些方法通常只是对provider方法的封装, 分别适用于不同的应用场景:

    • factory

    使用一个对象工厂函数定义服务,调用该工厂函数将返回服务实例。

    • service

    使用一个类构造函数定义服务,通过new操作符将创建服务实例。

    • value

    使用一个定义服务,这个值就是服务实例。

    • constant

    使用一个常量定义服务,这个常量就是服务实例。

    5.3.1 factory方法

    factory方法要求提供一个对象工厂,调用该类工厂将返回服务实例。

    1. var myServiceFactory = function(){
    2. return ...
    3. };
    4. angular.module("myModule",[])
    5. .factory("myService",myServiceFactory);

    INSIDE:AngularJS会将factory方法封装为provider,上面的示例 等同于:

    1. var myServiceFactory = function(){
    2. return ...
    3. };
    4. angular.module("myModule",[])
    5. .provider("myService",function(){
    6. this.$get = myServiceFactory;
    7. });

    如下:

    angular.module("ezstuff",[])
        .factory("ezCalculator",function(){
            return {
                add : function(a,b){return a+b;},
                subtract : function(a,b){return a-b;},
                multiply : function(a,b){return a*b;},
                divide: function(a,b){return a/b;}
            }
        })

    5.3.2 service方法

    service方法要求提供一个构造函数,AngularJS使用这个构造函数创建服务实例:

    1. var myServiceClass = function(){
    2. this.method1 = function(){...}
    3. };
    4. angular.module("myModule",[])
    5. .service("myService",myServiceClass);

    INSIDE:AngularJS会将service方法封装为provider,上面的示例 等同于:

    1. var myServiceClass = function(){
    2. //class definition.
    3. };
    4. angular.module("myModule",[])
    5. .provider("myService",function(){
    6. this.$get = function(){
    7. return new myServiceClass();
    8. };
    9. });

    如下:

    var ezCalculatorClass = function(){
        this.add = function(a,b){return a+b;};
        this.subtract = function(a,b){return a-b;};
        this.multiply = function(a,b){return a*b;};
        this.divide = function(a,b){return a/b;};
    };
    
    angular.module("ezstuff",[])
    .service("ezCalculator",ezCalculatorClass);

    5.3.3 value方法

    有时我们需要在不同的组件之间共享一个变量,可以将这种情况视为一种服务: provider返回的总是变量的值。

    value方法提供了对这种情况的简化封装:

    1. angular.module("myModule",[])
    2. .value("myValueService","cx129800123");

    INSIDE:AngularJS会将value方法封装为provider,上面的示例 等同于:

    1. angular.module("myModule",[])
    2. .provider("myService",function(){
    3. this.$get = function(){
    4. return "cx129800123";
    5. };
    6. });

    5.3.4 constant方法

    有时我们需要在不同的组件之间共享一个常量,可以将这种情况视为一种服务: provider返回的总是常量的值。

    constant方法提供了对这种情况的简化封装:

    1. angular.module("myModule",[])
    2. .constant("myConstantService","Great Wall");

    和value方法不同,AngularJS并没有将constant方法封装成一个provider,而仅仅 是在内部登记这个值。这使得常量在AngularJS的启动配置阶段就可以使用(创建任何 服务之前):你可以将常量注入到模块的config()方法中。

    如下,将constant换成value则不能运行:

    angular.module("ezstuff",[])
        .constant("ezCurrency","CN")
        .provider("ezCalculator",function(){
            var currency = "$";
            this.setLocal = function(l){
                var repo = {
                    "CN":"¥",
                    "US":"$",
                    "JP":"¥",
                    "EN":"€"
                };
                if(repo[l]) currency = repo[l];
            };
            this.$get = function(){
                return {
                    add : function(a,b){return currency + (a+b);},
                    subtract : function(a,b){return currency + (a-b);},
                    multiply : function(a,b){return currency + (a*b);},
                    divide: function(a,b){return currency + (a/b);}
                }
            };
        })
        .config(function(ezCurrency,ezCalculatorProvider){
            ezCalculatorProvider.setLocal(ezCurrency);
        });

    6. 封装指令

    6.1 创建指令

    指令也是一种服务,只是这种服务的定义有几个特殊要求:

    1. 必须使用模块的directive()方法注册服务
    2. 必须以对象工厂/factory()方法定义服务实现
    3. 对象工厂必须返回一个指令定义对象
    1. //定义指令的类工厂
    2. var directiveFactory = function(injectables){
    3. //指令定义对象
    4. var directiveDefinationObject = {
    5. ...
    6. };
    7. return directiveDefinationObject;
    8. };
    9. //在模块上注册指令
    10. angular.module("someModule",[])
    11. .directive("directiveName",directiveFactory);

    INSIDE:指令在注入器中的登记名称是:指令名+Directive。 例如,ng-app指令的服务名称是:"ngAppDirective"。

    6.2 指令定义对象

    每个指令定义的工厂函数,需要返回一个指令定义对象。指令定义对象就是 一个具有约定属性的JavaScript对象,编译器/$compile在编译时就根据这 个定义对象对指令进行展开。

    指令定义对象的常用属性如下:

    • template : string

    使用template指定的HTML标记替换指令内容(如果replace = true,那么用HTML片段替换指令本身。如果transclue属性为true,则为包裹指令的内容。)

    • restrict : string

    用来限定指令在HTML模板中出现的位置。restict属性可以是EACM这四个字母的任意组合,用来限定指令的应用场景。 如果不指定这个属性,默认情况下,指令将仅允许被用作元素名属性名

    • E - 指令可以作为HTML元素使用
    • A - 指令可以作为HTML属性使用
    • C - 指令可以作为CSS类使用
    • M - 指令可以在HTML注释中使用
    • replace : true|false

    使用这个属性指明template的替换方式。作图为false,有图为true

    <body>
        <div ng-controller="ezCtrl">
            <ez-customer></ez-customer>
        </div>
    </body>

    imageimage

    当然,因为replace为true时,要求模板有 一个根节点,所以定义如下:

    .directive("ezCustomer", function() {
      return {
          restrict:"E",
          replace:true,
          template: "<div>Name: {{customer.name}} Address: {{customer.address}}</div>"
      };
    });
    • scope : true|false|{...}

    scope属性为指令创建私有的作用域,这在创建可复用的Widget时非常有用。通过设置scope属性,指令的每个实例都将获得一个隔离的本地作用域

    scope上有两种约定符号:@表示DOM值变则Scope值变,=表示DOM值和Scope值双向影响。如下图的理解:

    image

    angular.module("ezstuff",[])
    .controller("ezCtrl", ["$scope", function($scope) {
      $scope.Emmy = {
        name: "Emmy",
        address: "1600 Amphitheatre"
      };
      $scope.Edison = {
        name: "Edison",
        address: "2500 Amphitheatre"
      };
    }])
    .directive("ezCustomer", function() {
      return {
          restrict:"E",
          replace:true,
          scope:{
            name:"@name",
            address: "=address"
          },
          template: "<div>Name: <input field='name' value={{name}}></input>  Address: <input field='address' value={{address}}></input></div>",
          link : function(scope,element,attrs){
                   element.find("input").on("keyup",function(ev){
                    var field = ev.target.getAttribute("field");
                    scope[field] = ev.target.value;
                    //将对scope的修改进行传播
                    scope.$apply("");
                });
        }
      };
    })
      .directive("ezLogger",function(){
        return {
            restrict : "A",
            link : function(scope,element,attrs){
                var model = attrs.data;
    
                scope.$watch(model,function(nv){
                    var cnt = JSON.stringify(nv,null,"    ");
                    element.html("<pre>"+cnt+"</pre    ");
                },true);
            }
        };
    });
    <html ng-app="ezstuff">
    <head>
        <script src="angular.min.js"></script>
    </head>
    <body>
        <div ng-controller="ezCtrl">
            <ez-customer name="{{Emmy.name}}" address="Emmy.address"></ez-customer>
            <ez-customer name="{{Edison.name}}" address="Edison.address"></ez-customer>
          <div ez-logger data="Emmy"></div>
          <div ez-logger data="Edison"></div>
          
        </div>
    </body>
    </html>
    • link : function(..){...}

    link属性是一个函数,用来在指令中操作DOM树、实现数据绑定。

    1. scope:指令对应的scope对象。如果指令没有定义自己的本地作用域,那么传入的就是外部的 作用域对象。
    2. iElement:指令所在DOM对象的jqLite封装。如果使用了template属性,那么iElement对应 变换后的DOM对象的jqLite封装。
    3. iAttrs:指令所在DOM对象的属性集。这是一个Hash对象,每个键是驼峰规范化后的属性名。
      transclude : true|false|'element'

    允许指令包含其他HTML元素,这通常用于实现一个容器类型的Widget。

    有些指令需要能够包含其他未知的元素。比如我们定义一个指令ez-dialog,用来 封装对话框的样式和行为,它应当允许在使用期(也就是在界面模板文件里)才指 定其内容:

    1. <ez-dialog>
    2. <p>对话框的内容在我们开发ez-dialog指令的时候是无法预计的。这部分内容需要
    3. 被转移到展开的DOM树中适当的位置。</p>
    4. </ez-dialog>

    transclude属性可以告诉编译器,利用所在DOM元素的内容,替换template中包含 ng-transclude指令的元素的内容:

    transclude

    从上图中可以看到,使用transclude有两个要点:

    1. 需要首先声明transclude属性值为true,这将告诉编译器,使用我们这个指令的 DOM元素,其内容需要被复制插入到编译后的DOM树的某个点。
    2. 需要在template属性值中使用ng-transclude指明插入点

    7. 过滤器

    7.1 在视图模板中使用过滤器

    过滤器也是一种服务,负责对输入的内容进行处理转换,以便更好地向用户显示。

    过滤器可以在模板中的{{}}标记中使用:

    1. {{ expression | filter:arg1:arg2}}
    • 预置的过滤器

    AngularJS的ng模块实现了一些预置的过滤器,如:currency、number等等,可以直接 使用。例如下面的示例将对数字12使用currency过滤器,结果将是"$12.00":

    1. {{12|currency}}
    • 带参数的过滤器

    过滤器也可以有参数,例如下面的示例将数字格式化为"1,234.00":

    1. {{1234|number:2}}
    • 过滤器流水线

    过滤器可以应用于另一个过滤器的输出,就像流水线,语法如下:

    1. {{expression|filter1|filter2|...}}

    7.2 在代码中使用过滤器

    别忘了过滤器也是一种服务,所以你可以将它注入你的代码中。

    和普通的服务不同,过滤器在注入器中注册时,名称被加了一个后缀:Filter。 例如,number过滤器的服务名称是:numberFilter,而currency过滤器的服务名称是: currencyFilter。

    通常我们的代码被封装在三个地方:控制器、服务、指令。这些地方都支持服务的直接 注入,例如:

    1. angular.module('myModule',[])
    2. .controller(function($scope,numberFilter){
    3. //...
    4. })

    有时你需要显式的通过注入器调用过滤器,那么使用注入器的invoke()方法:

    1. angular.injector(['ng'])
    2. .invoke(function(numberFilter){
    3. //...
    4. })

    总之,记住过滤器是一种服务,除了名字需要追加Filter后缀,和其他服务的调用方法没 什么区别。

    右边的示例在控制器中注入了number和currency过滤器,实现对total的格式化。

    7.3 创建过滤器

    和指令类似,过滤器也是一种特殊的服务,与创建一个普通的服务相比较:

    1. 必须使用模块的filter()接口注册服务
    2. 必须提供对象工厂/factory方法
    3. 对象工程必须返回一个过滤器函数,其第一个参数为输入变量
    1. //定义过滤器类工厂
    2. var filterFactory = function(){
    3. //定义过滤器函数
    4. var filter = function(input,extra_args){
    5. //process input and generate output
    6. return output
    7. }
    8. };
    9. //在模块上注册过滤器
    10. angular.module("someModule",[])
    11. .filter("filterName",filterFactory);

    右边的示例定义了一个将字符串格式化为大写的过滤器。

    7.4 为过滤器增加参数

    过滤器的行为可以通过额外的参数来调整。比如,我们希望改进上一节的示例,使其可以 支持仅大写每个单词的首字母。

    • 实现

    通过在过滤器类工厂返回的过滤器函数中传入额外的参数,就可以实现这个功能。

    1. var filter = function(input,argument1,argument2){...}
    • 使用

    在使用过滤器时,额外的参数通过前缀:引入,比如

    1. {{expression|filter:argument1:argument2}}

    右边的示例实现了支持参数的过滤器ezUC,试着去掉HTML模板中过滤器ezUC的参数, 看看显示输出的区别!

  • 相关阅读:
    《文献管理与信息分析》速看提问
    《构建之法(第三版)》速读提问
    《深入理解计算机系统》速读提问
    2017-2018-1 20179205《Linux内核原理与设计》第八周作业
    《从问题到程序》第一、二章学习
    2017-2018-1 20179205 第三周测试 汇编混合编程
    2017-2018-1 20179205《Linux内核原理与设计》第七周作业
    第三周main参数传递-1 课堂测试
    2017-2018-1 20179205《Linux内核原理与设计》第六周作业
    2017-2018-1 20179203 《Linux内核原理与分析》第八周作业
  • 原文地址:https://www.cnblogs.com/dorothychai/p/4686625.html
Copyright © 2020-2023  润新知