AngularJS进阶学习

参考:http://***/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: Name: {{customer.name}} Address: {{customer.address}}
  };
});
  • 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的参数, 看看显示输出的区别!

转载于:https://www.cnblogs.com/dorothychai/p/4686625.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值