参考: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就会自动进入引导过程,启动整个框架:

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模块是内置载入的,不需要显式指定)。

在自动启动引导的场景下,可以给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对象。

如果是自动引导启动,那么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子树进行编译。

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

  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类名称及备注中。指令的实现本质上就是一个类工厂,它返回一个指令定义对象,编译器根据这个指令定义对象进行操作。

指令的规范化

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对象:

在图中,我们看到:

  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,利用这个控制器,我们对业务模型进行了初始化赋值:

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

4.4.2 向cope对象添加方法

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

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

通过在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>

当然,因为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值双向影响。如下图的理解:

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有两个要点:

  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的参数, 看看显示输出的区别!

AngularJS进阶学习的更多相关文章

  1. AngularJS进阶(四十)创建模块、服务

    AngularJS进阶(四十)创建模块.服务 学习要点 使用模块构架应用 创建和使用服务 为什么要使用和创建服务与模块? 服务允许你打包可重用的功能,使之能在此应用中使用. 模块允许你打包可重用的功能 ...

  2. AngularJS 整理学习

    参考博客:  https://blog.csdn.net/weixin_33178524/article/details/79179597 https://blog.csdn.net/qq_42128 ...

  3. PHP程序员进阶学习书籍参考指南

    PHP程序员进阶学习书籍参考指南 @heiyeluren lastmodify: 2016/2/18     [初阶](基础知识及入门)   01. <PHP与MySQL程序设计(第4版)> ...

  4. Matlab 进阶学习记录

    最近在看 Faster RCNN的Matlab code,发现很多matlab技巧,在此记录: 1. conf_proposal  =  proposal_config('image_means', ...

  5. zuul进阶学习(二)

    1. zuul进阶学习(二) 1.1. zuul对接apollo 1.1.1. Netflix Archaius 1.1.2. 定期拉 1.2. zuul生产管理实践 1.2.1. zuul网关参考部 ...

  6. ROS进阶学习笔记(11)- Turtlebot Navigation and SLAM - ROSMapModify - ROS地图修改

    ROS进阶学习笔记(11)- Turtlebot Navigation and SLAM - 2 - MapModify地图修改 We can use gmapping model to genera ...

  7. Struts2进阶学习4

    Struts2进阶学习4 自定义拦截器的使用 核心配置文件 <?xml version="1.0" encoding="UTF-8"?> <! ...

  8. Struts2进阶学习3

    Struts2进阶学习3 OGNL表达式与Struts2的整合 核心配置文件与页面 <?xml version="1.0" encoding="UTF-8" ...

  9. Java进阶学习:将文件上传到七牛云中

    Java进阶学习:将文件上传到七牛云中 通过本文,我们将讲述如何利用七牛云官方SDK,将我们的本地文件传输到其存储空间中去. JavaSDK:https://developer.qiniu.com/k ...

随机推荐

  1. DB2 SQL Mixed data in character strings

    Mixed character data and graphic data are always allowed for Unicode, but for EBCDIC and ASCII, the ...

  2. hadoop namenode ha--手动切换(转)

    1.hadoop的dfs.nameservices如何配置?2.集群配置中hdfs://mycluster的作用是什么?3.如何将namenode2切换为active状态? 在参考本手册前请确保Had ...

  3. AngularJS 控制器 ng-controller

    AngularJS 控制器 控制 AngularJS 应用程序的数据. AngularJS 控制器是常规的 JavaScript 对象. AngularJS 应用程序被控制器控制. ng-contro ...

  4. Jmeter 中通过(_time函数)获取10位时间戳的方法

    meter的__time函数作用是取当前时间的时间戳,默认取的时间精确到了毫秒级别,所以获取的时间戳默认是13位的.  下图为取10位的时间戳的函数表达式(时间精确到秒) 

  5. C#回顾 – 4.IEnumerable 集合

         

  6. 关于Java反射机制的几个问题

    >>如何在运行时确定对象类型 运行时类型识别(Run-time Type Identification, RTTI)主要有两种方式, 一种是在编译时和运行时已经知道了所有的类型,另外一种是 ...

  7. .net学习之母版页执行顺序、jsonp跨域请求原理、IsPostBack原理、服务器端控件按钮Button点击时的过程、缓存、IHttpModule 过滤器

    1.WebForm使用母版页后执行的顺序是先执行子页面中的Page_Load,再执行母版页中的Page_Load,请求是先生成母版页的控件树,然后将子页面生成的控件树填充到母版页中,最后输出 2.We ...

  8. 咱就入个门之NHibernate映射文件配置(一)

    之前写了数据库连接配置,这次说说映射文件的配置,即表映射[ORM的核心就是此啦!]. 下面我们使用最原始的手动配置hbm.xml文件. 步骤: 1.添加People类 namespace NHiber ...

  9. 浅学JSON——Json.NET之首次试手

    首次遭遇Json格式,缘由项目中用到Json数据,需要进行解析,为此,将Json数据转为了自己较为熟悉的DataTable格式,以此展示至DataGridView中,验证是否成功. 直接上代码: // ...

  10. HTML5头部标签备忘

    DOCTYPE DOCTYPE(Document Type),该声明位于文档中最前面的位置,处于html 标签之前,此标签告知浏览器文档使用哪种HTML 或者XHTML 规范. 推荐使用HTML5 推 ...