分类目录归档:angularjs

angularJS之$http:与服务器交互

在angularJS中与远程HTTP服务器交互时会用一个非常关键的服务-$http。

    •  $http是angular中的一个核心服务,利用浏览器的xmlhttprequest或者via JSONP对象与远程HTTP服务器进行交互。
    •  $http的使用方式和jquery提供的$.ajax操作比较相同,均支持多种method的请求,get、post、put、delete等。
    •  $http的各种方式的请求更趋近于rest风格。
    •    在controller中可通过与$scope同样的方式获取$http对象,e.g. function controller($scope,$http){}

  下面进行$http服务的使用说明,调用如下:

$http(config).success(function(data,status,headers,config){}).error(function(data,status,headers,config){});
  1. config为一个JSON对象,其中主要包含该请求的url、data、method等,如{url:”login.do”,method:”post”,data:{name:”12346″,pwd:”123″}}。
    • method  {String} 请求方式e.g. “GET”.”POST”
    • url {String} 请求的URL地址
    • params {key,value} 请求参数,将在URL上被拼接成?key=value
    • data {key,value} 数据,将被放入请求内发送至服务器
    • cache {boolean} 若为true,在http GET请求时采用默认的$http cache,否则使用$cacheFactory的实例
    • timeout {number} 设置超时时间

  2、success为请求成功后的回调函数,error为请求失败后的回调函数,这里主要是对返回的四个参数进行说明。

    • data 响应体
    • status 相应的状态值
    • headers 获取getter的函数
    • config 请求中的config对象,同上第1点

  为了方便大家与HTTP服务器进行交互,angularJS提供了各个请求方式下方法。

      $http.put/post(url,data,config) url、name必填,config可选

      $http.get/delete/jsonp/head(url,confid) url必填,config可选

  url、data、config与$http的参数一致,

  下面有一个simple demo用于展示如何使用$http()及$http.post()。

复制代码
<!DOCTYPE HTML>
<html lang="zh-cn" >
<head>
    <meta charset="UTF-8">
    <title>CSSClasses</title>
    <script src="angular.min.js" type="text/javascript"></script>
<script type="text/javascript">
    function ctrl($http,$scope){
        $scope.login = function(user){
            $http.post("login.do",user).success(function(data, status, headers, config){
                alert("success");
            }).error(function(data, status, headers, config){
                alert("error");
            })
        }
        $scope.login1 = function(user){
            $http({url:"login.do",data:user}).success(function(data, status, headers, config){
                alert("success");
            }).error(function(data, status, headers, config){
                alert("error");
            })
        }
    }
</script>
</head>
<body ng-app>
    <div ng-controller="ctrl">
        <form name="loginFm">
            Name:<input ng-model="user.name" />
            pwd: <input ng-model="user.pwd" />
            <input type="button" value="login" ng-click="login(user)" />
            <input type="button" value="login1" ng-click="login1(user)" />
        </form>
    </div>

</body>
</html>

Angularjs开发一些经验总结

在去年到今年参与了2个使用Angularjs作为客户端开发框架的项目开发。主要利用asp.net web api作为restfull服务提供框架和angularjs结合。Angularjs作为html的扩展,旨在建立一个丰富的动态web应用,通过 Directive建立一套html扩展的DSL模型,利用PM模式变形MVVM(在网上很多称MVC模式,本人认为在angular0.8是属于经典 MVC模式,但在1.0把scope独立注入过后,更倾向于MVVM模式,这将会后续随笔中写道)简化前端开发和使得前端业务逻辑得以分离,view和表 现逻辑的分离,更便于维护,扩展。Angularjs本来就是采用TDD开发的,提供了一套单元测试组件和End 2 End的测试框架。Angularjs的的强大之处在于提供了一套内似WPF,Silverlight的强大数据绑定和格式化,过滤组件,这也是MVVM 模式所必备的条件;再加之IOC的注入机制,使得不能业务逻辑的分离,服务代码的更大程度抽象重用。

在这节随便中将讨论的angularjs开发的一些基本准则,为什么会有这篇随便呢,因为看见一些项目对于angularjs的乱用。

1:不要一个page一个God似无所不能的controller包含所有页面逻辑。

Angularjs ng-controller旨在将业务逻辑的区分,更推荐按照业务逻辑的划分controller,做到业务功能的高内聚,controller的单一原则SRP。

2:View中包含尽量少的逻辑。

就像jsp,asp这类服务端模板引擎一样,我们应该把尽量少的逻辑放在view中,因为这样会导致view和逻辑的紧耦合性,view在软件开发中是最 易变化的,而表现层逻辑却相对于view是相对稳定的行为。同时也导致的view中的逻辑不能被自动化测试,持续集成所覆盖,这将导致以后修改重构和模块 的集成的痛苦。很明显的就是太多的angularjs的ng-switch,ng-when和页面计算表达式等等。

3:注意一些特殊的节点式的angularjs directive,因为在IE7上这是不被认识的,因为IE的严格XML模式。如果你想make ie7 happy,

1:请注意导入json2或者json3的js

2:xmlns:ng命令空间和节点element式directive。

<html xmlns:ng=”http://angularjs.org”>

<head>

<!–[if lte IE 8]>

<script>

document.createElement(‘ng-include’);

document.createElement(‘ng-pluralize’);

document.createElement(‘ng-view’);

 

// Optionally these for CSS

document.createElement(‘ng:include’);

document.createElement(‘ng:pluralize’);

document.createElement(‘ng:view’);

</script>

<![endif]–>

</head>

3:除官网介绍的几个注意点之外 需要将


<span class="tag"><span class="tag"><span class="tag"><span class="tag">&lt;div </span></span></span></span><span class="atn"><span class="atn"><span class="atn"><span class="atn">ng-app</span></span></span></span><span class="pun"><span class="pun"><span class="pun"><span class="pun">=</span></span></span></span><span class="atv"><span class="atv"><span class="atv"><span class="atv">"xxx"</span></span></span></span><span class="tag"><span class="tag"><span class="tag"><span class="tag">&gt;

</span></span></span></span>
改为

<span class="tag"><span class="tag"><span class="tag"><span class="tag">&lt;div </span></span></span></span><span class="atn"><span class="atn"><span class="atn"><span class="atn">id</span></span></span></span><span class="pun"><span class="pun"><span class="pun"><span class="pun">=</span></span></span></span><span class="atv"><span class="atv"><span class="atv"><span class="atv">"ng-app" </span></span></span></span><span class="atn"><span class="atn"><span class="atn"><span class="atn">ng-app</span></span></span></span><span class="pun"><span class="pun"><span class="pun"><span class="pun">=</span></span></span></span><span class="atv"><span class="atv"><span class="atv"><span class="atv">"xxx"</span></span></span></span><span class="tag"><span class="tag"><span class="tag"><span class="tag">&gt;</span></span></span></span>

另外注意html 头部要引入(否则会进入坑爹的quirk模式)


<span class="dec"><span class="dec"><span class="dec"><span class="dec">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;</span></span></span></span>

 

4:在controller和service中绝对不能出现html的DOM和CSS代码。

这会导致逻辑的混杂耦合,对于angularjs自身的绑定对html操作,很多时候你会分不清是view的影响源,导致修复bug,和新增功能,重构的 艰难,常常出现很多的诡异行为。最好的实践模式则是把必须的dom,css操作移向angular的Directive,或者view中。在 angularjs模式中只有directive和view才能出现dom和css的逻辑操作。

5:controller中公用的逻辑推向service(factory,value,config),采用IOC的注入,提高代码的重用度,修改的单一点,开闭原则。

6:controller应该只包含业务逻辑,对于数据模型的格式化过滤尽量交给angular框架filter等处理。

7:viewmodel中最好建立一个通用属性比如vm,它承载view渲染的最小量化model,对于model的变形事件则在vm之外 scope之上。这才是MVVM推荐方式。事件相当于WPF中的command,负责模型事件的传递修改模型,从而从模型的改变通知view的强制更新 (WPF中model必须实现INotifyPropertyChange接口)。同时这样vm属性也便于数据的填充和收集回发服务端。

8:IOC注入优先,有助于良好的设计,逻辑的可重用和单元模块的可测试性,面向对象的“开闭原则”,修改的单一点。

9:良好的分层设计,对于view的交互采用controller通过viewmode(scope)的推送,与服务器的交互推向service层 次,利用angularjs的$resource或者$http获取更新数据model,以及与服务端交互。层次划分属于纵向分割,将相同功能逻辑的接口 放在一起,架构层次,而model则从业务的逻辑横向分离。

10:服务端的服务的接口需要考虑表现层客户端的应用提供,这是一个良好的SOA服务设计的准则,这里不用多余的描述,具体请移步架构篇

11:如果你的公司应用了敏捷开发则,TDD的开发是必备的,angularjs本也是解决javascript测试驱动开发项目。

12:scope的纯净性,scope上的每一个函数和属性必须为view所用(事件传递或者属性绑定),不用的可以作为工具函数或者service处置.

13:对controller之间如果不是强依赖,只是弱引用则最好用事件$emit,$on,$broadcast,是的controller之间低耦合(Angularjs Controller 间通信机制)。

14:angularjs的的模块管理参见如何组织大型JavaScript应用中的代码?.

 

最后想说说angularjs也不是银弹,并不是万能的,不是所有的项目都适合应用,它适用于CRUD的应用系统,内置了一些默认规则(惯例优先),对于 表现层频繁交互的项目不适用,对于一些特殊的项目比如spring hdiv的项目也不是那么友好,或者就是你希望兼容更多的IE8一下的版本的应用系统,同样也不实用。

引用:http://www.cnblogs.com/whitewolf/archive/2013/03/24/2979344.html

 

深入理解 AngularJS 的 Scope

一、遇到的问题

问题发生在使用 AngularJS 嵌套 Controller 的时候。因为每个 Controller 都有它对应的 Scope(相当于作用域、控制范围),所以 Controller 的嵌套,也就意味着 Scope 的嵌套。这个时候如果两个 Scope 内都有同名的 Model 会发生什么呢?从子 Scope 怎样更新父 Scope 里的 Model 呢?

这个问题很典型,比方说当前页面是一个产品列表,那么就需要定义一个 ProductListController

function ProductListController($scope, $http) {
    $http.get('/api/products.json')
        .success(function(data){
            $scope.productList = data;
        });
    $scope.selectedProduct = {};
}

你大概看到了在 Scope 里还定义了一个 selectedProduct 的 Model,表示选中了某一个产品。这时会获取该产品详情,而页面通过 AngularJS 中的

$routeProvider

自动更新,拉取新的详情页模板,模板中有一个 ProductDetailController

function ProductDetailController($scope, $http, $routeParams) {
    $http.get('/api/products/'+$routeParams.productId+'.json')
        .success(function(data){
            $scope.selectedProduct = data;
        });
}

有趣的事情发生了,在这里也有一个 selectedProduct ,它会怎样影响 ProductListController 中的 selectedProduct 呢?

答案是没有影响。在 AnuglarJS 里子 Scope 确实会继承父 Scope 中的对象,但当你试下对基本数据类型(string, number, boolean)的 双向数据绑定 时,就会发现一些奇怪的行为,继承并不像你想象的那样工作。子 Scope 的属性隐藏(覆盖)了父 Scope 中的同名属性,对子 Scope 属性(表单元素)的更改并不更新父 Scope 属性的值。这个行为实际上不是 AngularJS 特有的,JavaScript 本身的原型链就是这样工作的。开发者通常都没有意识到 ng-repeat, ng-switch, ng-view 和 ng-include 统统都创建了他们新的子 scopes,所以在用到这些 directive 时也经常出问题。

二、解决的办法

解决的办法就是不使用基本数据类型,而在 Model 里永远多加一个点

.
使用
<input type="text" ng-model="someObj.prop1">
来替代
<input type="text" ng-model="prop1">

是不是很坑爹?下面这个例子很明确地表达了我所想表达的奇葩现象

app.controller('ParentController',function($scope){
    $scope.parentPrimitive = "some primitive"
    $scope.parentObj = {};
    $scope.parentObj.parentProperty = "some value";
});
app.controller('ChildController',function($scope){
    $scope.parentPrimitive = "this will NOT modify the parent"
    $scope.parentObj.parentProperty = "this WILL modify the parent";
});

查看 在线演示 DEMO

但是我真的确实十分很非常需要使用 string number 等原始数据类型怎么办呢?2 个方法——

  1. 在子 Scope 中使用
    $parent.parentPrimitive

    。 这将阻止子 Scope 创建它自己的属性。

  2. 在父 Scope 中定义一个函数,让子 Scope 调用,传递原始数据类型的参数给父亲,从而更新父 Scope 中的属性。(并不总是可行)

三、JavaScript 的原型链继承

吐槽完毕,我们来深入了解一下 JavaScript 的原型链。这很重要,特别是当你从服务器端开发转到前端,你应该会很熟悉经典的 Class 类继承,我们来回顾一下。

假设父类 parentScope 有如下成员属性 aString, aNumber, anArray, anObject, 以及 aFunction。子类 childScope 原型继承父类 parentScope,于是我们有:

angularjs-inheritance1

如果子 Scope 尝试去访问 parentScope 中定义的属性,JavaScript 会先在子 Scope 中查找,如果没有该属性,则找它继承的 scope 去获取属性,如果继承的原型对象 parentScope 中都没有该属性,那么继续在它的原型中寻找,从原型链一直往上直到到达 rootScope。所以,下面的表达式结果都是 ture:

childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'

假设我们执行下面的语句

childScope.aString = 'child string'

原型链并没有被查询,反而是在 childScope 中增加了一个新属性 aString。这个新属性隐藏(覆盖)了 parentScope 中的同名属性。在下面我们讨论 ng-repeat 和 ng-include 时这个概念很重要。

angularjs-inheritance2

假设我们执行这个操作:

childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'

原型链被查询了,因为对象 anArray 和 anObject 在 childScope 中没有找到。它们在 parentScope 中被找到了,并且值被更新。childScope 中没有增加新的属性,也没有任何新的对象被创建。(注:在 JavaScript 中,array 和 function 都是对象)

angularjs-inheritance3

假设我们执行这个操作:

childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }

原型链没有被查询,并且子 Scope 新加入了两个新的对象属性,它们隐藏(覆盖)了 parentScope 中的同名对象属性。

angularjs-inheritance4

应该可以总结

  • 如果读取 childScope.propertyX,并且 childScope 有属性 propertyX,那么原型链没有被查询。
  • 如果设置 childScope.propertyX,原型链不会被查询。

最后一种情况,

delete childScope.anArray
childScope.anArray[1] === 22  // true

我们从 childScope 删除了属性,则当我们再次访问该属性时,原型链会被查询。删除对象的属性会让来自原型链中的属性浮现出来。

angularjs-inheritance5

四、AngularJS 的 Scope 继承

  • 创建新的 Scope,并且原型继承:ng-repeat, ng-include, ng-switch, ng-view, ng-controller, directive with
    scope: true

    , directive with

    transclude: true
  • 创建新的 Scope,但不继承:directive with
    scope: { ... }

    。它会创建一个独立 Scope。

注:默认情况下 directive 不创建新 Scope,即默认参数是

scope: false

ng-include

假设在我们的 controller 中,

$scope.myPrimitive = 50;
$scope.myObject    = {aNumber: 11};

HTML 为:

<script type="text/ng-template" id="/tpl1.html">
    <input ng-model="myPrimitive">
</script>
<div ng-include src="'/tpl1.html'"></div>
 
<script type="text/ng-template" id="/tpl2.html">
    <input ng-model="myObject.aNumber">
</script>
<div ng-include src="'/tpl2.html'"></div>

每一个 ng-include 会生成一个子 Scope,每个子 Scope 都继承父 Scope。

angularjs-inheritance6

输入(比如”77″)到第一个 input 文本框,则子 Scope 将获得一个新的 myPrimitive 属性,覆盖掉父 Scope 的同名属性。这可能和你预想的不一样。

angularjs-inheritance7

输入(比如”99″)到第二个 input 文本框,并不会在子 Scope 创建新的属性,因为 tpl2.html 将 model 绑定到了一个对象属性(an object property),原型继承在这时发挥了作用,ngModel 寻找对象 myObject 并且在它的父 Scope 中找到了。

angularjs-inheritance8

如果我们不想把 model 从 number 基础类型改为对象,我们可以用 $parent 改写第一个模板:

<input ng-model="$parent.myPrimitive">

输入(比如”22″)到这个文本框也不会创建新属性了。model 被绑定到了父 scope 的属性上(因为 $parent 是子 Scope 指向它的父 Scope 的一个属性)。

angularjs-inheritance9

对于所有的 scope (原型继承的或者非继承的),Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系(也就是继承关系),图中为简化而未画出这些属性。

在没有表单元素的情况下,另一种方法是在父 Scope 中定义一个函数来修改基本数据类型。因为有原型继承,子 Scope 确保能够调用这个函数。例如,

// 父 Scope 中
$scope.setMyPrimitive = function(value) {
    $scope.myPrimitive = value;
}

查看 DEMO。参考 StackOverflow

ng-switch

ng-switch 的原型继承和 ng-include 一样。所以如果你需要对基本类型数据进行双向绑定,使用 $parent,或者将其改为 object 对象并绑定到对象的属性,防止子 Scope 覆盖父 Scope 的属性。

参考 AngularJS, bind scope of a switch-case?

ng-repeat

ng-repeat 有一点不一样。假设在我们的 controller 里:

$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]

还有 HTML:

<ul>
    <li ng-repeat="num in myArrayOfPrimitives">
       <input ng-model="num">
    </li>
<ul>
<ul>
    <li ng-repeat="obj in myArrayOfObjects">
       <input ng-model="obj.num">
    </li>
<ul>

对于每一个 Item,ng-repeat 创建新的 Scope,每一个 Scope 都继承父 Scope,但同时 item 的值也被赋给了新 Scope 的新属性(新属性的名字为循环的变量名)。Angular ng-repeat 的源码实际上是这样的:

childScope = scope.$new(); // 子 scope 原型继承父 scope ...     
childScope[valueIdent] = value; // 创建新的 childScope 属性

如果 item 是一个基础数据类型(就像 myArrayOfPrimitives),本质上它的值被复制了一份赋给了新的子 scope 属性。改变这个子 scope 属性值(比如用 ng-model,即

num

)不会改变父 scope 引用的 array。所以上面第一个 ng-repeat 里每一个子 scope 获得的

num

属性独立于 myArrayOfPrimitives 数组:

angularjs-inheritance10

这样的 ng-repeat 和你预想中的不一样。在 Angular 1.0.2 及更早的版本,向文本框中输入会改变灰色格子的值,它们只在子 Scope 中可见。Angular 1.0.3+ 以后,输入文本不会再有任何作用了。(参考 StackOverflow 上的解释)我们希望的是输入能改变 myArrayOfPrimitives 数组,而不是子 Scope 里的属性。为此我们必须将 model 改为一个关于对象的数组(array of objects)。

所以如果 item 是一个对象,则对于原对象的一个引用(而非拷贝)被赋给了新的子 Scope 属性。改变子 Scope 属性的值(使用 ng-model,即 obj.num)也就改变了父 Scope 所引用的对象。所以上面第二个 ng-repeat 可表示为:

angularjs-inheritance11

这才是我们想要的。输入到文本框即会改变灰色格子的值,该值在父 Scope 和子 Scope 均可见。

参考 Difficulty with ng-model, ng-repeat, and inputs 以及 ng-repeat and databinding

ng-controller

使用 ng-controller 进行嵌套,结果和 ng-include 和 ng-switch 一样是正常的原型继承。所以做法也一样不再赘述。然而“两个 controller 使用 $scope 继承来共享信息被认为是不好的做法”(来自 这里),应该使用 service 在 controller 间共享数据。

如果你确实要通过继承来共享数据,那么也没什么特殊要做的,子 Scope 可以直接访问所有父 Scope 的属性。参考 Controller load order differs when loading or navigating

directives

这个要分情况来讨论。

  1. 默认
    scope: false

    – directive 不会创建新的 Scope,所以没有原型继承。这看上去很简单,但也很危险,因为你会以为 directive 在 Scope 中创建了一个新的属性,而实际上它只是用到了一个已存在的属性。这对编写可复用的模块和组件来说并不好。

  2. scope: true

    – 这时 directive 会创建一个新的子 scope 并继承父 scope。如果在同一个 DOM 节点上有多个 directive 都要创建新 scope,则只有一个新 Scope 会创建。因为有正常的原型继承,所以和 ng-include, ng-switch 一样要注意基础类型数据的双向绑定,子 Scope 属性会覆盖父 Scope 同名属性。

  3. scope: { ... }

    – 这时 directive 创建一个独立的 scope,没有原型继承。这在编写可复用的模块和组件时是比较好的选择,因为 directive 不会不小心读写父 scope。然而,有时候这类 directives 又经常需要访问父 scope 的属性。对象散列(object hash)被用来建立这个独立 Scope 与父 Scope 间的双向绑定(使用 ‘=’)或单向绑定(使用 ‘@’)。还有一个 ‘&’ 用来绑定父 Scope 的表达式。这些统统从父 Scope 派生创建出本地的 Scope 属性。注意,HTML 属性被用来建立绑定,你无法在对象散列中引用父 Scope 的属性名,你必须使用一个 HTML 属性。例如,

    &lt;div my-directive&gt;

    scope: { localProp: '@parentProp' }

    是无法绑定父属性 parentProp 到独立 scope的,你必须这样指定:

    &lt;div my-directive the-Parent-Prop=parentProp&gt;

    以及

    scope: { localProp: '@theParentProp' }

    。独立的 scope 中

    __proto__

    引用了一个 Scope 对象(下图中的桔黄色 Object),独立 scope 的 $parent 指向父 scope,所以尽管它是独立的而且没有从父 Scope 原型继承,它仍然是一个子 scope。下面的图中,我们有

    &lt;my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2"&gt;

    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }


    同时,假设 directive 在它的 link 函数里做了

    scope.someIsolateProp = "I'm isolated"

    angularjs-inheritance12

    注意:在 link 函数中使用

    attrs.$observe('attr_name', function(value) { ... }

    来获取独立 Scope 用 ‘@’ 符号替换的属性值。例如,在 link 函数中有

    attrs.$observe('interpolated', function(value) { ... }

    值将被设为 11. (

    scope.interpolatedProp

    在 link 函数中是 undefined,相反

    scope.twowayBindingProp

    在 link 函数中定义了,因为用了 ‘=’ 符号)
    更多参考 http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/

  4. transclude: true

    – 这时 directive 创建了一个新的 “transcluded” 子 scope,同时继承父 scope。所以如果模板片段中的内容(例如那些将要替代 ng-transclude 的内容)要求对父 Scope 的基本类型数据进行双向绑定,使用 $parent,或者将 model 一个对象的属性,防止子 Scope 属性覆盖父 Scope 属性。transcluded 和独立 scope (如果有)是兄弟关系,每个 Scope 的 $parent 指向同一个父 Scope。当模板中的 scope 和独立 Scope 同时存在,独立 Scope 属性 $$nextSibling 将会指向模板中的 Scope。
    更多关于 transcluded scope 的信息,参考 AngularJS two way binding not working in directive with transcluded scope

    在下图中,假设 directive 和上个图一样,只是多了

    transclude: true

    angularjs-inheritance13
    查看 在线 DEMO,例子里有一个 showScope() 函数可以用来检查独立 Scope 和它关联的 transcluded scope。

总结

一共有四种 Scope:

  1. 普通进行原型继承的 Scope —— ng-include, ng-switch, ng-controller, directive with
    scope: true
  2. 普通原型继承的 Scope 但拷贝赋值 —— ng-repeat。 每个 ng-repeat 的循环都创建新的子 Scope,并且子 Scope 总是获得新的属性。
  3. 独立的 isolate scope —— directive with
    scope: {...}

    。它不是原型继承,但 ‘=’, ‘@’ 和 ‘&’ 提供了访问父 Scope 属性的机制。

  4. transcluded scope —— directive with
    transclude: true

    。它也遵循原型继承,但它同时是任何 isolate scope 的兄弟。

对于所有的 Scope,Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系。

参考链接
http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs

引用:http://www.lovelucy.info/understanding-scopes-in-angularjs.html 

AngularJS 指令实践指南(二)

这个系列教程的第一部分给出了AngularJS指令的基本概述,在文章的最后我们介绍了如何隔离一个指令的scope。第二部分将承接上一篇继续介绍。首先,我们会看到在使用隔离scope的情况下,如何从指令内部访问到父scope的属性。接着,我们会基于对 controller 函数和 transclusions 讨论如何为指令选择正确的scope。这篇文章的最后会以通过一个完整的记事本应用来实践指令的使用。

隔离scope和父scope之间的数据绑定

通常,隔离指令的scope会带来很多的便利,尤其是在你要操作多个scope模型的时候。但有时为了使代码能够正确工作,你也需要从指令内部访问父scope的属性。好消息是Angular给了你足够的灵活性让你能够有选择性的通过绑定的方式传入父scope的属性。让我们重温一下我们的 helloWorld 指令,它的背景色会随着用户在输入框中输入的颜色名称而变化。还记得当我们对这个指令使用隔离scope的之后,它不能工作了吗?现在,我们来让它恢复正常。

假设我们已经初始化完成app这个变量所指向的Angular模块。那么我们的 helloWorld 指令如下面代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
app.directive(
'helloWorld'
,
function
() {
  
return
{
    
scope: {},
    
restrict:
'AE'
,
    
replace:
true
,
    
template:
'&lt;p&gt;Hello World&lt;/p&gt;'
,
    
link:
function
(scope, elem, attrs) {
      
elem.bind(
'click'
,
function
() {
        
elem.css(
'background-color'
,
'white'
);
        
scope.$apply(
function
() {
          
scope.color =
"white"
;
        
});
      
});
      
elem.bind(
'mouseover'
,
function
() {
        
elem.css(
'cursor'
,
'pointer'
);
      
});
    
}
  
};
});

使用这个指令的HTML标签如下:

1
2
3
4
&lt;
body
ng-controller
=
"MainCtrl"
&gt;
  
&lt;
input
type
=
"text"
ng-model
=
"color"
placeholder
=
"Enter a color"
/&gt;
  
&lt;
hello-world
/&gt;
&lt;/
body
&gt;

上面的代码现在是不能工作的。因为我们用了一个隔离的scope,指令内部的 {{color}} 表达式被隔离在指令内部的scope中(不是父scope)。但是外面的输入框元素中的 ng-model 指令是指向父scope中的 color 属性的。所以,我们需要一种方式来绑定隔离scope和父scope中的这两个参数。在Angular中,这种数据绑定可以通过为指令所在的HTML元素添加属性和并指令定义对象中配置相应的 scope 属性来实现。让我们来细究一下建立数据绑定的几种方式。

选择一:使用 @ 实现单向文本绑定

在下面的指令定义中,我们指定了隔离scope中的属性 color 绑定到指令所在HTML元素上的参数 colorAttr。在HTML标记中,你可以看到 {{color}}表达式被指定给了 color-attr 参数。当表达式的值发生改变时,color-attr 参数也跟着改变。隔离scope中的 color 属性的值也相应地被改变。

1
2
3
4
5
6
7
8
9
app.directive(
'helloWorld'
,
function
() {
  
return
{
    
scope: {
      
color:
'@colorAttr'
    
},
    
....
    
// the rest of the configurations
  
};
});

更新后的HTML标记代码如下:

1
2
3
4
&lt;
body
ng-controller
=
"MainCtrl"
&gt;
  
&lt;
input
type
=
"text"
ng-model
=
"color"
placeholder
=
"Enter a color"
/&gt;
  
&lt;
hello-world
color-attr
=
"{{color}}"
/&gt;
&lt;/
body
&gt;

我们称这种方式为单项绑定,是因为在这种方式下,你只能将字符串(使用表达式{{}})传递给参数。当父scope的属性变化时,你的隔离scope模型中的属性值跟着变化。你甚至可以在指令内部监控这个scope属性的变化,并且触发一些任务。然而,反向的传递并不工作。你不能通过对隔离scope属性的操作来改变父scope的值。

注意点:
当隔离scope属性和指令元素参数的名字一样是,你可以更简单的方式设置scope绑定:

1
2
3
4
5
6
7
8
9
app.directive(
'helloWorld'
,
function
() {
  
return
{
    
scope: {
      
color:
'@'
    
},
    
....
    
// the rest of the configurations
  
};
});

相应使用指令的HTML代码如下:

1
&lt;
hello-world
color
=
"{{color}}"
/&gt;

选择二:使用 = 实现双向绑定

让我们将指令的定义改变成下面的样子:

1
2
3
4
5
6
7
8
9
app.directive(
'helloWorld'
,
function
() {
  
return
{
    
scope: {
      
color:
'='
    
},
    
....
    
// the rest of the configurations
  
};
});

相应的HTML修改如下:

1
2
3
4
&lt;
body
ng-controller
=
"MainCtrl"
&gt;
  
&lt;
input
type
=
"text"
ng-model
=
"color"
placeholder
=
"Enter a color"
/&gt;
  
&lt;
hello-world
color
=
"color"
/&gt;
&lt;/
body
&gt;

与 @ 不同,这种方式让你能够给属性指定一个真实的scope数据模型,而不是简单的字符串。这样你就可以传递简单的字符串、数组、甚至复杂的对象给隔离scope。同时,还支持双向的绑定。每当父scope属性变化时,相对应的隔离scope中的属性也跟着改变,反之亦然。和之前的一样,你也可以监视这个scope属性的变化。

选择三:使用 & 在父scope中执行函数

有时候从隔离scope中调用父scope中定义的函数是非常有必要的。为了能够访问外部scope中定义的函数,我们使用 &。比如我们想要从指令内部调用 sayHello() 方法。下面的代码告诉我们该怎么做:

1
2
3
4
5
6
7
8
9
app.directive(
'sayHello'
,
function
() {
  
return
{
    
scope: {
      
sayHelloIsolated:
'&amp;amp;'
    
},
    
....
    
// the rest of the configurations
  
};
});

相应的HTML代码如下:

1
2
3
4
&lt;
body
ng-controller
=
"MainCtrl"
&gt;
  
&lt;
input
type
=
"text"
ng-model
=
"color"
placeholder
=
"Enter a color"
/&gt;
  
&lt;
say-hello
sayHelloIsolated
=
"sayHello()"
/&gt;
&lt;/
body
&gt;

这个 Plunker 例子对上面的概念做了很好的诠释。

父scope、子scope以及隔离scope的区别

作为一个Angular的新手,你可能会在选择正确的指令scope的时候感到困惑。默认情况下,指令不会创建一个新的scope,而是沿用父scope。但是在很多情况下,这并不是我们想要的。如果你的指令重度地使用父scope的属性、甚至创建新的属性,会污染父scope。让所有的指令都使用同一个父scope不会是一个好主意,因为任何人都可能修改这个scope中的属性。因此,下面的这个原则也许可以帮助你为你的指令选择正确的scope。

1.父scope(scope: false) – 这是默认情况。如果你的指令不操作父scoe的属性,你就不需要一个新的scope。这种情况下是可以使用父scope的。

2.子scope(scope: true) – 这会为指令创建一个新的scope,并且原型继承自父scope。如果你的指令scope中的属性和方法与其他的指令以及父scope都没有关系的时候,你应该创建一个新scope。在这种方式下,你同样拥有父scope中所定义的属性和方法。

3.隔离scope(scope:{}) – 这就像一个沙箱!当你创建的指令是自包含的并且可重用的,你就需要使用这种scope。你在指令中会创建很多scope属性和方法,它们仅在指令内部使用,永远不会被外部的世界所知晓。如果是这样的话,隔离的scope是更好的选择。隔离的scope不会继承父scope。

Transclusion(嵌入)

Transclusion是让我们的指令包含任意内容的方法。我们可以延时提取并在正确的scope下编译这些嵌入的内容,最终将它们放入指令模板中指定的位置。 如果你在指令定义中设置 transclude:true,一个新的嵌入的scope会被创建,它原型继承子父scope。 如果你想要你的指令使用隔离的scope,但是它所包含的内容能够在父scope中执行,transclusion也可以帮忙。

假设我们注册一个如下的指令:

1
2
3
4
5
6
7
app.directive(
'outputText'
,
function
() {
  
return
{
    
transclude:
true
,
    
scope: {},
    
template:
'&lt;div ng-transclude&gt;&lt;/div&gt;'
  
};
});

它使用如下:

1
2
3
&lt;
div
output-text&gt;
  
&lt;
p
&gt;Hello {{name}}&lt;/
p
&gt;
&lt;/
div
&gt;

ng-transclude 指明在哪里放置被嵌入的内容。在这个例子中DOM内容 <p>Hello {{name}}</p> 被提取和放置到 <div ng-transclude></div> 内部。有一个很重要的点需要注意的是,表达式{{name}}所对应的属性是在父scope中被定义的,而非子scope。你可以在这个Plunker例子中做一些实验。如果你想要学习更多关于scope的知识,可以阅读这篇文章

transclude:’element’ 和 transclude:true的区别

有时候我我们要嵌入指令元素本身,而不仅仅是它的内容。在这种情况下,我们需要使用 transclude:’element’。它和 transclude:true 不同,它将标记了 ng-transclude 指令的元素一起包含到了指令模板中。使用transclusion,你的link函数会获得一个名叫 transclude 的链接函数,这个函数绑定了正确的指令scope,并且传入了另一个拥有被嵌入DOM元素拷贝的函数。你可以在这个 transclude 函数中执行比如修改元素拷贝或者将它添加到DOM上等操作。 类似 ng-repeat 这样的指令使用这种方式来重复DOM元素。仔细研究一下这个Plunker,它使用这种方式复制了DOM元素,并且改变了第二个实例的背景色。

同样需要注意的是,在使用 transclude:’element’的时候,指令所在的元素会被转换成HTML注释。所以,如果你结合使用 transclude:’element’ 和 replace:false,那么指令模板本质上是被添加到了注释的innerHTML中——也就是说其实什么都没有发生!相反,如果你选择使用 replace:true,指令模板会替换HTML注释,那么一切就会如果所愿的工作。使用 replade:false 和 transclue:’element’有时候也是有用的,比如当你需要重复DOM元素但是并不想保留第一个元素实例(它会被转换成注释)的情况下。对这块还有疑惑的同学可以阅读stackoverflow上的这篇讨论,介绍的比较清晰。

controller 函数和 require

如果你想要允许其他的指令和你的指令发生交互时,你需要使用 controller 函数。比如有些情况下,你需要通过组合两个指令来实现一个UI组件。那么你可以通过如下的方式来给指令添加一个 controller 函数。

1
2
3
4
5
6
7
8
9
10
11
12
app.directive(
'outerDirective'
,
function
() {
  
return
{
    
scope: {},
    
restrict:
'AE'
,
    
controller:
function
($scope, $compile, $http) {
      
// $scope is the appropriate scope for the directive
      
this
.addChild =
function
(nestedDirective) {
// this refers to the controller
        
console.log(
'Got the message from nested directive:'
+ nestedDirective.message);
      
};
    
}
  
};
});

这个代码为指令添加了一个名叫 outerDirective 的controller。当另一个指令想要交互时,它需要声明它对你的指令 controller 实例的引用(require)。可以通过如下的方式实现:

1
2
3
4
5
6
7
8
9
10
11
12
app.directive(
'innerDirective'
,
function
() {
  
return
{
    
scope: {},
    
restrict:
'AE'
,
    
require:
'^outerDirective'
,
    
link:
function
(scope, elem, attrs, controllerInstance) {
      
//the fourth argument is the controller instance you require
      
scope.message =
"Hi, Parent directive"
;
      
controllerInstance.addChild(scope);
    
}
  
};
});

相应的HTML代码如下:

1
2
3
&lt;
outer-directive
&gt;
  
&lt;
inner-directive
&gt;&lt;/
inner-directive
&gt;
&lt;/
outer-directive
&gt;

require: ‘^outerDirective’ 告诉Angular在元素以及它的父元素中搜索controller。这样被找到的 controller 实例会作为第四个参数被传入到 link 函数中。在我们的例子中,我们将嵌入的指令的scope发送给父亲指令。如果你想尝试这个代码的话,请在开启浏览器控制台的情况下打开这个Plunker。同时,这篇Angular官方文档上的最后部分给了一个非常好的关于指令交互的例子,是非常值得一读的。

 

一个记事本应用

这一部分,我们使用Angular指令创建一个简单的记事本应用。我们会使用HTML5的 localStorage 来存储笔记。最终的产品在这里,你可以先睹为快。
我们会创建一个展现记事本的指令。用户可以查看他/她创建过的笔记记录。当他点击 add new 按钮的时候,记事本会进入可编辑状态,并且允许创建新的笔记。当点击 back 按钮的时候,新的笔记会被自动保存。笔记的保存使用了一个名叫 noteFactory 的工厂类,它使用了 localStorage。工厂类中的代码是非常直接和可理解的。所以我们就集中讨论指令的代码。

第一步

我们从注册 notepad 指令开始。

1
2
3
4
5
6
7
8
9
app.directive(
'notepad'
,
function
(notesFactory) {
  
return
{
    
restrict:
'AE'
,
    
scope: {},
    
link:
function
(scope, elem, attrs) {
    
},
    
templateUrl:
'templateurl.html'
  
};
});

这里有几点需要注意的:

  • 因为我们想让指令可重用,所以选择使用隔离的scope。这个指令可以拥有很多与外界没有关联的属性和方法。
  • 这个指令可以以属性或者元素的方式被使用,这个被定义在 restrict 属性中。
  • 现在的link函数是空的
  • 这个指令从 templateurl.html 中获取指令模板

第二步

下面的HTML组成了指令的模板。

1
2
3
4
5
6
7
8
9
10
&lt;
div
class
=
"note-area"
ng-show
=
"!editMode"
&gt;
  
&lt;
ul
&gt;
    
&lt;
li
ng-repeat
=
"note in notes|orderBy:'id'"
&gt;
      
&lt;
a
href
=
"#"
ng-click
=
"openEditor(note.id)"
&gt;{{note.title}}&lt;/
a
&gt;
    
&lt;/
li
&gt;
  
&lt;/
ul
&gt;
&lt;/
div
&gt;
&lt;
div
id
=
"editor"
ng-show
=
"editMode"
class
=
"note-area"
contenteditable
=
"true"
ng-bind
=
"noteText"
&gt;&lt;/
div
&gt;
&lt;
span
&gt;&lt;
a
href
=
"#"
ng-click
=
"save()"
ng-show
=
"editMode"
&gt;Back&lt;/
a
&gt;&lt;/
span
&gt;
&lt;
span
&gt;&lt;
a
href
=
"#"
ng-click
=
"openEditor()"
ng-show
=
"!editMode"
&gt;Add Note&lt;/
a
&gt;&lt;/
span
&gt;

几个重要的注意点:

  • note 对象中封装了 title,id 和 content。
  • ng-repeat 用来遍历 notes 中所有的笔记,并且按照自动生成的 id 属性进行升序排序。
  • 我们使用一个叫 editMode 的属性来指明我们现在在哪种模式下。在编辑模式下,这个属性的值为 true 并且可编辑的 div 节点会显示。用户在这里输入自己的笔记。
  • 如果 editMode 为 false,我们就在查看模式,显示所有的 notes。
  • 两个按钮也是基于 editMode 的值而显示和隐藏。
  • ng-click 指令用来响应按钮的点击事件。这些方法将和 editMode 一起添加到scope中。
  • 可编辑的 div 框与 noteText 相绑定,存放了用户输入的文本。如果你想编辑一个已存在的笔记,那么这个模型会用它的文本内容初始化这个 div 框。

第三步

我们在scope中创建一个名叫 restore() 的新函数,它用来初始化我们应用中的各种控制器。 它会在 link 函数执行的时候被调用,也会在 save 按钮被点击的时候调用。

1
2
3
4
5
scope.restore =
function
() {
  
scope.editMode =
false
;
  
scope.index = -1;
  
scope.noteText =
''
;
};

我们在 link 函数的内部创建这个函数。 editMode 和 noteText 之前已经解释过了。 index 用来跟踪当前正在编辑的笔记。 当我们在创建一个新的笔记的时候,index 的值会设成 -1. 我们在编辑一个已存在的笔记的时候,它包含了那个 note 对象的 id 值。

第四步

现在我们要创建两个scope函数来处理编辑和保存操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
scope.openEditor =
function
(index) {
  
scope.editMode =
true
;
  
if
(index !== undefined) {
    
scope.noteText = notesFactory.get(index).content;
    
scope.index = index;
  
}
else
{
    
scope.noteText = undefined;
  
}
};
scope.save =
function
() {
  
if
(scope.noteText !==
''
) {
    
var
note = {};
    
note.title = scope.noteText.length &gt; 10 ? scope.noteText.substring(0, 10) +
'. . .'
: scope.noteText;
    
note.content = scope.noteText;
    
note.id = scope.index != -1 ? scope.index : localStorage.length;
    
scope.notes = notesFactory.put(note);
  
}
  
scope.restore();
};

这两个函数有几点需要注意:

  • openEditor 为编辑器做准备工作。如果我们在编辑一个笔记,它会获取当前笔记的内容并且通过使用 ng-bind 将内容更新到可编辑的 div 中。
  • 如果我们在创建一个新的笔记,我们会将 noteText 设置成 undefined,以便当我们在保存笔记的时候,触发相应的监听器。
  • 如果 index 参数是 undefined,它表明用户正在创建一个新的笔记。
  • save 函数通过使用 notesFactory 来存储笔记。在保存完成后,它会刷新 notes 数组,从而监听器能够监测到笔记列表的变化,来及时更新。
  • save 函数调用在重置 controllers 之后调用restore(),从而可以从编辑模式进入查看模式。

第五步

在 link 函数执行时,我们初始化 notes 数组,并且为可编辑的 div 框绑定一个 keydown 事件,从而保证我们的 nodeText 模型与 div 中的内容保持同步。我们使用这个 noteText 来保存我们的笔记内容。

1
2
3
4
5
6
7
8
var
editor = elem.find(
'#editor'
);
scope.restore(); 
// initialize our app controls
scope.notes = notesFactory.getAll();
// load notes
editor.bind(
'keyup keydown'
,
function
() {
  
scope.noteText = editor.text().trim();
});

第六步

最后,我们在HTML如同使用其他的HTML元素一样使用我们的指令,然后开始做笔记吧。

1
2
&lt;
h1
class
=
"title"
&gt;The Note Making App&lt;/
h1
&gt;
&lt;
notepad
/&gt;

总结

一个很重要的点需要注意的是,任何使用jQuery能做的事情,我们都能用Angular指令来做到,并且使用更少的代码。所以,在使用jQuery之前,请考虑一下我们能否在不进行DOM操作的情况下以更好的方式来完成任务。试着使用Angular来最小化jQuery的使用吧。
再来看一下我们的笔记本应用,删除笔记的功能被故意漏掉了。鼓励读者们自己实验和实现这个功能。 你可以从GitHub上下到这个Demo的源代码。

引用:http://blog.jobbole.com/62999/

AngularJS 指令实践指南(一)

指令(Directives)是所有AngularJS应用最重要的部分。尽管AngularJS已经提供了非常丰富的指令,但还是经常需要创建应用特定的指令。这篇教程会为你讲述如何自定义指令,以及介绍如何在实际项目中使用。在这篇文章的最后(第二部分),我会指导你如何使用Angular指令来创建一个简单的记事本应用。

概述

一个指令用来引入新的HTML语法。指令是DOM元素上的标记,使元素拥有特定的行为。举例来说,静态的HTML不知道如何来创建和展现一个日期选择器控件。让HTML能识别这个语法,我们需要使用指令。指令通过某种方法来创建一个能够支持日期选择的元素。我们会循序渐进地介绍这是如何实现的。 如果你写过AngularJS的应用,那么你一定已经使用过指令,不管你有没有意识到。你肯定已经用过简单的指令,比如 ng-mode, ng-repeat, ng-show等。这些指令都赋予DOM元素特定的行为。例如,ng-repeat 重复特定的元素,ng-show 有条件地显示一个元素。如果你想让一个元素支持拖拽,你也需要创建一个指令来实现它。指令背后基本的想法很简单。它通过对元素绑定事件监听或者改变DOM而使HTML拥有真实的交互性。

jQuery视角

想象一下使用jQuery如何创建一个日期选择器。首先,我们在HTML中添加一个普通的输入框,然后通过jQuery调用 $(element).dataPicker() 来将它转变成一个日期选择器。但是,仔细想一下。当一个设计人员过来检查HTML标记的时候,他/她能否立刻猜到这个字段实际上表示的内容?这只是一个简单的输入框,或者一个日期选择器?你需要查看jQuery代码来确定这些。而Angular的方法是使用一个指令来扩展HTML。所以,一个日期选择器的指令可以是下面的形式:

 
1
&lt;
input
type
=
"text"
/&gt;

或者是这样:

1
&lt;
input
type
=
"text"
/&gt;

这种创建UI组建的方式更加直接和清晰。你可以轻易地通过查看元素就明白这到底是什么。

创建自定义指令:

一个Angular指令可以有以下的四种表现形式: 1. 一个新的HTML元素(<data-picker></data-picker>) 2. 元素的属性(<input type=”text” data-picker/>) 3. CSS class(<input type=”text” class=”data-picker”/>) 4. 注释(<!–directive:data-picker –>) 当然,我们可以控制我们的指令在HTML中的表现形式。下面我们来看一下AngularJS中的一个典型的指令的写法。指令注册的方式与 controller 一样,但是它返回的是一个拥有指令配置属性的简单对象(指令定义对象) 。下面的代码是一个简单的 Hello World 指令。

1
2
3
4
5
6
7
8
9
var
app = angular.module(
'myapp'
, []);
app.directive(
'helloWorld'
,
function
() {
  
return
{
      
restrict:
'AE'
,
      
replace:
'true'
,
      
template:
'&lt;h3&gt;Hello World!!&lt;/h3&gt;'
  
};
});
在上面的代码中,app.directive()方法在模块中注册了一个新的指令。这个方法的第一个参数是这个指令的名字。第二个参数是一个返回指令定义对象的函数。如果你的指令依赖于其他的对象或者服务,比如 $rootScope, $http, 或者$compile,他们可以在这个时间被注入。这个指令在HTML中以一个元素使用,如下:
1
2
3
&lt;
hello-world
/&gt;
//OR
&lt;
hello:world
/&gt;

或者,以一个属性的方式使用:

1
2
3
&lt;
div
hello-world&gt;&lt;/
div
&gt;
//OR
&lt;
div
hello:world/&gt;

如果你想要符合HTML5的规范,你可以在元素前面添加 x- 或者 data-的前缀。所以下面的标记也会匹配 helloWorld 指令:

1
2
3
&lt;div data-hello-world&gt;&lt;/div&gt;
//OR
&lt;div x-hello-world&gt;&lt;/div&gt;

注意: 在匹配指令的时候,Angular会在元素或者属性的名字中剔除 x- 或者 data- 前缀。 然后将 – 或者 : 连接的字符串转换成驼峰(camelCase)表现形式,然后再与注册过的指令进行匹配。这是为什么,我们在HTML中以 hello-world 的方式使用 helloWorld 指令。其实,这跟HTML对标签和属性不区分大小写有关。 尽管上面的指令仅仅实现了静态文字的显示,但是这里还是有一些有趣的点值得我们去挖掘。我们在指令定义过程中使用了三个属性来配置指令。我们来一一介绍他们的作用。

  • restrict – 这个属性用来指定指令在HTML中如何使用(还记得之前说的,指令的四种表示方式吗)。在上面的例子中,我们使用了 ‘AE’。所以这个指令可以被当作新的HTML元素或者属性来使用。如果要允许指令被当作class来使用,我们将 restrict 设置成 ‘AEC’。
  • template – 这个属性规定了指令被Angular编译和链接(link)后生成的HTML标记。这个属性值不一定要是简单的字符串。template 可以非常复杂,而且经常包含其他的指令,以及表达式({{ }})等。更多的情况下你可能会见到 templateUrl, 而不是 template。所以,理想情况下,你应该将模板放到一个特定的HTML文件中,然后将 templateUrl 属性指向它。
  • replace – 这个属性指明生成的HTML内容是否会替换掉定义此指令的HTML元素。在我们的例子中,我们用 <hello-world></hello-world>的方式使用我们的指令,并且将 replace 设置成 true。所以,在指令被编译之后,生成的模板内容替换掉了 <hello-world></hello-world>。最终的输出是 <h3>Hello World!!</h3>。如果你将 replace 设置成 false,也就是默认值,那么生成的模板会被插入到定义指令的元素中。

打开这个 plunker,在”Hello World!!”右键检查元素内容,来更形象地明白这些。

Link函数和Scope

指令生成出的模板其实没有太多意义,除非它在特定的scope下编译。默认情况下,指令并不会创建新的子scope。更多的,它使用父scope。也就是说,如果指令存在于一个controller下,它就会使用这个controller的scope。 如何运用scope,我们要用到一个叫做 link 的函数。它由指令定义对象中的link属性配置。让我们来改变一下我们的 helloWorld 指令,当用户在一个输入框中输入一种颜色的名称时,Hello World 文字的背景色自动发生变化。同时,当用户在 Hello World 文字上点击时,背景色变回白色。 相应的HTML标记如下:

1
2
3
4
&lt;
body
ng-controller
=
"MainCtrl"
&gt;
  
&lt;
input
type
=
"text"
ng-model
=
"color"
placeholder
=
"Enter a color"
/&gt;
  
&lt;
hello-world
/&gt;
&lt;/
body
&gt;

修改后的 helloWorld 指令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.directive(
'helloWorld'
,
function
() {
  
return
{
    
restrict:
'AE'
,
    
replace:
true
,
    
template:
'&lt;p&gt;Hello World'
,
    
link:
function
(scope, elem, attrs) {
      
elem.bind(
'click'
,
function
() {
        
elem.css(
'background-color'
,
'white'
);
        
scope.$apply(
function
() {
          
scope.color =
"white"
;
        
});
      
});
      
elem.bind(
'mouseover'
,
function
() {
        
elem.css(
'cursor'
,
'pointer'
);
      
});
    
}
  
};
});

我们注意到指令定义中的 link 函数。 它有三个参数:

  • scope – 指令的scope。在我们的例子中,指令的scope就是父controller的scope。
  • elem – 指令的jQLite(jQuery的子集)包装DOM元素。如果你在引入AngularJS之前引入了jQuery,那么这个元素就是jQuery元素,而不是jQLite元素。由于这个元素已经被jQuery/jQLite包装了,所以我们就在进行DOM操作的时候就不需要再使用 $()来进行包装。
  • attr – 一个包含了指令所在元素的属性的标准化的参数对象。举个例子,你给一个HTML元素添加了一些属性:,那么可以在 link 函数中通过 attrs.someAttribute 来使用它。

link函数主要用来为DOM元素添加事件监听、监视模型属性变化、以及更新DOM。在上面的指令代码片段中,我们添加了两个事件, click,和 mouseover。click 处理函数用来重置 <p> 的背景色,而 mouseover 处理函数改变鼠标为 pointer。在模板中有一个表达式 {{color}},当父scope中的 color 发生变化时,它用来改变 Hello World 文字的背景色。 这个 plunker 演示了这些概念。

compile函数

compile 函数在 link 函数被执行之前用来做一些DOM改造。它接收下面的参数:

  • tElement – 指令所在的元素
  • attrs – 元素上赋予的参数的标准化列表

要注意的是 compile 函数不能访问 scope,并且必须返回一个 link 函数。但是如果没有设置 compile 函数,你可以正常地配置 link 函数,(有了compile,就不能用link,link函数由compile返回)。compile函数可以写成如下的形式:

1
2
3
4
5
6
7
8
9
10
app.directive(
'test'
,
function
() {
  
return
{
    
compile:
function
(tElem,attrs) {
      
//do optional DOM transformation here
      
return
function
(scope,elem,attrs) {
        
//linking function here
      
};
    
}
  
};
});

大多数的情况下,你只需要使用 link 函数。这是因为大部分的指令只需要考虑注册事件监听、监视模型、以及更新DOM等,这些都可以在 link 函数中完成。 但是对于像 ng-repeat 之类的指令,需要克隆和重复 DOM 元素多次,在 link 函数执行之前由 compile 函数来完成。这就带来了一个问题,为什么我们需要两个分开的函数来完成生成过程,为什么不能只使用一个?要回答好这个问题,我们需要理解指令在Angular中是如何被编译的!

指令是如何被编译的

当应用引导启动的时候,Angular开始使用 $compile 服务遍历DOM元素。这个服务基于注册过的指令在标记文本中搜索指令。一旦所有的指令都被识别后,Angular执行他们的 compile 方法。如前面所讲的,compile 方法返回一个 link 函数,被添加到稍后执行的 link 函数列表中。这被称为编译阶段。如果一个指令需要被克隆很多次(比如 ng-repeat),compile函数只在编译阶段被执行一次,复制这些模板,但是link 函数会针对每个被复制的实例被执行。所以分开处理,让我们在性能上有一定的提高。这也说明了为什么在 compile 函数中不能访问到scope对象。 在编译阶段之后,就开始了链接(linking)阶段。在这个阶段,所有收集的 link 函数将被一一执行。指令创造出来的模板会在正确的scope下被解析和处理,然后返回具有事件响应的真实的DOM节点。

改变指令的Scope

默认情况下,指令获取它父节点的controller的scope。但这并不适用于所有情况。如果将父controller的scope暴露给指令,那么他们可以随意地修改 scope 的属性。在某些情况下,你的指令希望能够添加一些仅限内部使用的属性和方法。如果我们在父的scope中添加,会污染父scope。 其实我们还有两种选择:

  • 一个子scope – 这个scope原型继承子父scope。
  • 一个隔离的scope – 一个孤立存在不继承自父scope的scope。

这样的scope可以通过指令定义对象中 scope 属性来配置。下面的代码片段是一个例子:

1
2
3
4
5
6
7
8
app.directive(
'helloWorld'
,
function
() {
  
return
{
    
scope:
true
// use a child scope that inherits from parent
    
restrict:
'AE'
,
    
replace:
'true'
,
    
template:
'&lt;h3&gt;Hello World!!&lt;/h3&gt;'
  
};
});

上面的代码,让Angular给指令创建一个继承自父socpe的新的子scope。 另外一个选择,隔离的scope:

1
2
3
4
5
6
7
8
app.directive(
'helloWorld'
,
function
() {
  
return
{
    
scope: {}, 
// use a new isolated scope
    
restrict:
'AE'
,
    
replace:
'true'
,
    
template:
'&lt;h3&gt;Hello World!!&lt;/h3&gt;'
  
};
});

这个指令使用了一个隔离的scope。隔离的scope在我们想要创建可重用的指令的时候是非常有好处的。通过使用隔离的scope,我们能够保证我们的指令是自包含的,可以被很容易的插入到HTML应用中。 它内部不能访问父的scope,所保证了父scope不被污染。 在我们的 helloWorld 指令例子中,如果我们将 scope 设置成 {},那么上面的代码将不会工作。 它会创建一个新的隔离的scope,那么相应的表达式 {{color}} 会指向到这个新的scope中,它的值将是 undefined. 使用隔离的scope并不意味着我们完全不能访问父scope的属性。其实有一些技术可以允许我们访问父scope的属性,甚至监视他们的变化。我们会在指令这个系列的第二部分中讨论这些技术,以及一些更高级的概念,比如 Controller 函数。 第二部分也会和你一起使用Angular指令创建一个较为丰富的记事本应用。 所以,请保持关注。

引用:http://blog.jobbole.com/62249/ 

AngularJs中的Modules详解

一、什么是Module?
  很多应用都有一个用于初始化、加载(wires是这个意思吗?)和启动应用的main方法。angular应用不需要main方法,作为替代,module提供有指定目的的声明式,描述应用如何启动。这样做有几项优点:
  1. 这过程是声明描述的,更加容易读懂。
  2. 在单元测试中,不需要加载所有module,这对写单元测试很有帮助。
  3. 额外的module可以被加载到情景测试中,可以覆盖一些设置,帮助进行应用的端对端测试(end-to-end test)。
  4. 第三方代码可以作为可复用的module打包到angular中。
  5. module可以通过任意顺序或并行加载(取决于模块执行的延迟性,due to delayed nature of module execution)。
二、The Basics(基础)
  我们很迫切想知道如何让Hello World module能够工作。下面有几个关键的事情要注意:
module API(http://code.angularjs.org/1.0.2/docs/api/angular.Module)
注意的提及的在<html ng-app=”myApp”>中的myApp module,它让启动器启动我们定义的myApp module。
<span>&lt;!DOCTYPE HTML&gt;</span>
<span>&lt;html lang="zh-cn" ng-app="myApp"&gt;</span>
<span>&lt;head&gt;</span>
<span>    &lt;meta charset="UTF-8"&gt;</span>
<span>    &lt;title&gt;basics&lt;/title&gt;</span>
<span>    &lt;style type="text/css"&gt;</span>
<span>        .ng-cloak {</span>
<span>            display: none;</span>
<span>        }</span>
<span>    &lt;/style&gt;</span>
<span>&lt;/head&gt;</span>
<span>&lt;body&gt;</span>
<span>&lt;div&gt;</span>
<span>    {{'Kitty' | greet}}</span>
<span>&lt;/div&gt;</span>
<span>&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;</span>
<span>&lt;script type="text/javascript"&gt;</span>
<span>    var simpleModule = angular.module("myApp", []);</span>
<span>    simpleModule.filter("greet", function () {</span>
<span>        return function(name) {</span>
<span>            return "Hello " + name + " !";</span>
<span>        }</span>
<span>    });</span>
<span>&lt;/script&gt;</span>
<span>&lt;/body&gt;</span>
<span>&lt;/html&gt;</span>
三、(Recommended Setup)推荐设置
  虽然上面的例子很简单,它不属于大规模的应用。我们建议将自己的应用按照如下建议,拆分为多个module:
  1. service module,用于声明service。
  2. directive module,用于声明directive。
  3. filter module,用于声明filter。
  4. 应用级别的module,依赖上述的module,并且包含初始化的代码。
  这样划分的理由是,当我们在测试的时候,往往需要忽略那些让测试变得困难的初始化代码。通过将代码分成独立的module,在测试中就可以很容易地忽略那些代码。这样,我们就可以更加专注在加载相应的module进行测试。
  上面的只是一个建议,可以随意地按照自己的需求制定。
四、Module Loading & Dependencies(模块加载和依赖)
  module是配置(configuration)的集合,执行在启动应用的进程中应用的块(blocks)。在它的最简单的形式中,由两类block组成:
  1.配置块(configuration blocks):在provider注册和配置的过程中执行的。只有provider和constant(常量?)可以被注入(injected)到configuration blocks中。这是为了避免出现在service配置完毕之前service就被执行的意外。
  2.运行块(run blocks):在injector创建完成后执行,用于启动应用。只有实例(instances)和常量(constants)可以被注入到run block中。这是为了避免进一步的系统配置在程序运行的过程中执行。
<span>angular.module('myModule', []).</span>
<span>    config(function(injectables) { // provider-injector</span>
<span>    // 这里是config block的一个例子</span>
<span>    // 我们可以根据需要,弄N个这样的东东</span>
<span>    // 我们可以在这里注入Providers (不是实例,not instances)到config block里面</span>
<span>    }).</span>
<span>    run(function(injectables) { // instance-injector</span>
<span>    // 这里是一个run block的例子</span>
<span>    // 我们可以根据需要,弄N个这样的东东</span>
<span>    // 我们只能注入实例(instances )(不是Providers)到run block里面</span>
<span>});</span>
  a) Configuration Blocks(配置块)
  有一个方便的方法在module中,它相当于config block。例如:
<span>angular.module('myModule', []).</span>
<span>  value('a', 123).</span>
<span>    factory('a', function() { return 123; }).</span>
<span>    directive('directiveName', ...).</span>
<span>    filter('filterName', ...);</span>
// 等同于
<span>angular.module('myModule', []).</span>
<span>  config(function($provide, $compileProvider, $filterProvider) {</span>
<span>    $provide.value('a', 123)</span>
<span>    $provide.factory('a', function() { return 123; })</span>
<span>    $compileProvider.directive('directiveName', ...).</span>
<span>    $filterProvider.register('filterName', ...);</span>
<span>});</span>
  configuration blocks被应用的顺序,与它们的注册的顺序一致。对于常量定义来说,是一种额外的情况,即放置在configuration blocks开头的常量定义。
  b) Run Blocks(应用块)
  run block是在angular中最接近main方法的东东。run block是必须执行,用于启动应用的代码。它将会在所有service配置、injector创建完毕后执行。run block通常包含那些比较难以进行单元测试的代码,就是因为这个原因,这些代码应该定义在一个独立的module中,让这些代码可以在单元测试中被忽略。
  c) Dependencies(依赖)
  一个module可以列出它所依赖的其他module。依赖一个module,意味着被请求(required)的module(被依赖的)必须在进行请求(requiring)module(需要依赖其他module的module,请求方)加载之前加载完成。换一种说法,被请求的module的configuration block会在请求的module的configuration block执行前执行(before the configuration blocks or the requiring module,这里的or怎么解释呢?)。对于run block也是这个道理。每一个module只能够被加载一次,即使有多个其他module需要(require)它。
  d) Asynchronous Loading(异步加载)
  module是管理$injector配置的方法之一,不用对加载脚本到VM做任何事情。现在已经有现成的项目专门用于处理脚本加载,也可以用到angular中。因为module在加载的过程中不做任何事情,它们可以按照任意的顺序被加载到VM中。脚本加载器可以利用这个特性,进行并行加载。
五、Unit Testing(单元测试)
  在单元测试的最简单的形式中,其中一个是在测试中实例化一个应用的子集,然后运行它们。重要的是,我们需要意识到对于每一个injector,每一个module只会被加载一次。通常一个应用只会有一个injector。但在测试中,每一个测试用例都有它的injector,这意味着在每一个VM中,module会被多次加载。正确地构建module,将对单元测试有帮助,正如下面的例子:
  在这个例子中,我们准备假设定义如下的module:
<span>angular.module('greetMod', []).</span>
<span>factory('alert', function($window) {</span>
<span>     return function(text) {</span>
<span>       $window.alert(text);</span>
<span>     };</span>

<span>})</span>
<span>.value('salutation', 'Hello')</span>
<span>.factory('greet', function(alert, salutation) {</span>
<span>     return function(name) {</span>
<span>       alert(salutation + ' ' + name + '!');</span>
<span>     };</span>
<span>});</span>
  让我们写一些测试用例:
<span>    beforeEach(module('greetMod', function($provide) {//这里看来是要将真正的$window替换为以下的东东</span>
<span>      $provide.value('$window', {</span>
<span>        alert: jasmine.createSpy('alert')</span>
<span>      });</span>
<span>    }));</span>

<span>    // inject()会创建一个injector,并且注入greet和$window到测试中。</span>
<span>    // 测试不需要关心如何写应用,只需要关注如何测试应用。</span>
<span>    it('should alert on $window', inject(function(greet, $window) {</span>
<span>      greet('World');</span>
<span>      expect($window.alert).toHaveBeenCalledWith('Hello World!');</span>
<span>    }));</span>

<span>    // 这里是在测试中通过行内module和inject方法来覆盖配置的方法</span>
<span>    it('should alert using the alert service', function() {</span>
<span>      var alertSpy = jasmine.createSpy('alert');</span>
<span>      module(function($provide) {</span>
<span>        $provide.value('alert', alertSpy);</span>
<span>      });</span>
<span>      inject(function(greet) {</span>
<span>        greet('World');</span>
<span>        expect(alertSpy).toHaveBeenCalledWith('Hello World!');</span>
<span>      });</span>
<span>    });</span>

$http 相关资料

1. REST 客户端部分

客户端AngularJS:Accept,Content-Type
例如AngularJS发送请求:
    $http({method: 'GET', url: '/someUrl'})
    success(function(data, status, headers, config) {
        // this callback will be called asynchronously
        // when the response is available
    }).
    error(function(data, status, headers, config) {
        // called asynchronously if an error occurs
        // or server returns response with an error status.
    });
AngularJS Setting HTTP Headers
默认的HTTP头:
The $http service will automatically add certain HTTP headers to all requests. These defaultscan be fully configured by accessing the$httpProvider.defaults.headers configurationobject, which currently contains this default configuration:
$httpProvider.defaults.headers.common (headers that are common for all requests):
Accept: application/json, text/plain, * / *
X-Requested-With: XMLHttpRequest
$httpProvider.defaults.headers.post: (header defaults for POST requests)
Content-Type: application/json
$httpProvider.defaults.headers.put (header defaults for PUT requests)
Content-Type: application/json
如何修改HTTP头:
To add or overwrite these defaults, simply add or remove a property from these configuration objects. To add headers for an HTTP method other than POST or PUT, simply add a new objectwith the lowercased HTTP method name as the key, e.g.$httpProvider.defaults.headers.get[‘My-Header’]=’value’.
Additionally, the defaults can be set at runtime via the $http.defaults object in the same fashion.
e.g. 修改$httpProvider.defaults.headers(prevent angular.js $http object from sending X-Requested-With header)
angular.module('myModule', [])
    .config(['$httpProvider', function($httpProvider) {
        delete $httpProvider.defaults.headers.common["X-Requested-With"]
    }])
e.g. 使用$http.defaults
$http({
    method: 'POST',
    url: url,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}, //headers参数可以设置Accept、Content-Type
}).success(function () {});
客户端:$httpProvider.defaults.transformRequest/Response
AngularJS Transforming Requests and Responses
Both requests and responses can be transformed using transform functions. By default, Angularapplies these transformations:
Request transformations:
If the data property of the request configuration object contains an object,serialize it intoJSON format.
Response transformations:
If XSRF prefix is detected, strip it (see Security Considerations section below).
If JSON response is detected, deserialize it using a JSON parser.
To globally augment or override the default transforms, modify the $httpProvider.defaults.transformRequest and$httpProvider.defaults.transformResponse properties. These properties are by default an array of transform functions, which allows you topush or unshift a new transformation function into the transformation chain. You can also decide to completely override any default transformations by assigning yourtransformation functions to these properties directly without the array wrapper.
Similarly, to locally override the request/response transforms, augment thetransformRequestand/ortransformResponseproperties of the configuration object passed into$http.
e.g. local配置:在发送请求时设置transformRequest:
$http({
    method: 'POST',
    url: url,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    transformRequest: function(obj) {
        var str = [];
        for(var p in obj)
        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
        return str.join("&amp;");
    },
    data: xsrf
}).success(function () {});
e.g. Global配置:$httpProvider.defaults.transformRequest
var module = angular.module('myApp');
module.config(function ($httpProvider) {
    $httpProvider.defaults.transformRequest = function(data){
        if (data === undefined) {
            return data;
        }
        return $.param(data);
    }
});

(转)AngularJs学习笔记 全部–Guide教程系列文章索引

     在很久很久以前,一位前辈向我推荐AngularJs。但当时我没有好好学习,仅仅是讲文档浏览了一次。后来觉醒了……于是下定决心好好理解这系列的文档,并意译出来(英文水平不足……不能说是翻译,有些实在是看不懂,希望大家在参观的过程中指出其中的错误)。经过1个多月断断续续的努力,终于把Guide里面的文章基本上都弄出来。Guide中的部分章节,由于重复的部分似乎有点多,而且篇幅较短,这里就没有列出来。
  文章列表如下:
    By Lcllao.

(转)AngularJs学习笔记 3–concepts(概念)

一、总括
本文主要是angular组件(components)的概览,并说明他们如何工作。列表如下:
  1. statup – 依旧是hello world…改为Hello Kitty!
  2. runtime – 介绍angular的runtime
  3. scope – view与contorller的纽带(神马glue…胶)
  4. controller – app的行为(application behavior)
  5. model – app的数据
  6. view – 用户所看到的东东
  7. directives – HTML的语法扩展
  8. filters – 根据用户的本地格式,格式化数据
  9. injector – 加载我们的app(依赖管理之类)
  10. module – 配置injector
  11. $ – angular的命名空间(namespace)
二、启动(Startup)
下面描述angular是如何启动的(参考图表与下面的例子):
1. 浏览器加载HTML,将HTML标签转换为DOM对象;
2. 浏览器加载angular.js的脚本;
3. Angular等待DOMContentLoaded事件;
4. Angular寻找ng-app这个用于指定应用边界范围的directive;
5. 如果ng-app有指定module(也许是ng-app=”SomeApp”),将被用作配置$injector;
6. $injector用于创建$compile服务(service)以及$rootScope;
7. $compile服务用作“编译”(有点像遍历,然后做一点神秘的事情)DOM,并将其与对应的$rootScope连
接。
8. ng-init 这个directive在对应的scope中创建name属性并对其赋予”Kitty”值;
9. 将“{{name}}”的值插入(interpolates)到表达式中,最终显示”Hello Kitty!”。
2012090500475480

&lt;!DOCTYPE html&gt;

&lt;html lang="zh-cn" ng-app&gt;

&lt;head&gt;

    &lt;meta charset="UTF-8"&gt;

    &lt;title&gt;Hello Kitty!&lt;/title&gt;

    &lt;style type="text/css"&gt;

        .ng-cloak {

            display: none;

        }

    &lt;/style&gt;

&lt;/head&gt;

&lt;body&gt;

&lt;div ng-init="name='Kitty'"&gt;Hello {{name}}!&lt;/div&gt;

&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

三、Runtime
2012090601163790
  这图表和后面的例子,描述了angular如何通过浏览器event-loop(所有的时间处理函数,以及timer执行的函数,会排在一个queue结构中,利用一个无限的循环,不断从queue中取出函数来执行,这个就是event-loop。来自http://wiki.nodejs.tw/nodejs_from_scratch/javascript-yunodejs/2-1-event-loop)来进行交互。
  1. 浏览器event-loop等待事件到来。事件来自于用户交互(DOM events)、timer事件(setTimeout)、network事件(服务端响应,XHR之类);
  2. 事件回调函数开始执行。这里进入javascript上下文(context)。这回调函数可以修改DOM结构。
  3. 当回调函数执行完毕后,浏览器退出javascript context,根据DOM的改变来重绘视图。
  Angular通过创建自己的事件处理循环(event processing loop),修改了一般的javascript流(flow)。这将Javascript分割成传统的和Angular的执行上下文(execution context)。只要是在Angularexecution context 里面执行的操作,都拥有angular data-binding、异常处理(exception handling)、属性监视(property watching)等能力。我们可以通过在javascript使用$apply(),进入Angularexecution context。但要记住一点,在大多数(angular的)地方(如controllers、services),处理事件的directive会为你调用$apply。手动调用$apply的场景,一般是当你实现自定义事件处理函数,或者处理第三方库的回调的时候。
  1. 通过调用scope.$apply(stimulusFn)进入angular execution context。stimulusFn就是我们想在angular execution context中执行的函数(含scope作为参数)或者angular合法的表达式。
  2. Angular执行stimulusFn,这通常会改变应用的状态(application state)。
  3. Angular进入$digest loop。这个loop由一个处理$evalAsync queue 和处理$watch list两个更小的循环组成。$digest loop会在model稳定之前保持迭代,即$evalAsync queue为空,而且$watch list没有检测到任何变化。
  4. $evalAsync queue被用作安排必须跳出当前堆栈帧(堆栈帧指的是在堆栈中为当前正在运行的函数分配的区域(或空间)。传入的参数、返回地址(当这个函数结束后必须跳转到该返回地址。译注:即主调函数的断点处)以及函数所用的内部存储单元(即函数存储在堆栈上的局部变量)都在堆栈帧中。http://book.51cto.com/art/200804/70915.htm C.1.1  堆栈帧)之外,但在浏览器视图绘制之前的工作。这通常是通过使用setTimeout(0)来实现。但setTimeout(0)这方法,会导致缓慢,或者在每个事件处理完毕后,浏览器绘制视图时,出现视图闪烁(angular有没有去解决这个问题?如何解决?)。
  5. $watch list是有可能在最近一次迭代中被修改的表达式的集合。如果(model)发生了改变,那么$watch 函数会被调用,从而达到对特定的DOM重新赋值的目标。
  6. 一旦Angular $digest loop 完成了(之前3提到的情况),离开angular和javascript的context后,浏览器紧跟着就会去重绘DOM,以响应变化。
  下面解释例子“Hello Kitty”(-_-!)是如何在用户在文本框输入文本时实现数据绑定(data-binding)效果。
  1. 编译阶段(compilation phase):
    a) ng-model和input directive在<input>中版定keydown事件监听器。
    b) {{name}}占位符(interpolation,不知道怎么翻译)(表达式)设置一个$watch以便在name发生改变时有所响应。
  2. 执行阶段(runtime phase):
    a) 在inut控件中按下”X”按钮,让浏览器触发一个keydown事件;
    b) input directive捕捉到文本框值的改变,然后调用$apply(“name = ‘X’;”),在angular execution context中更新应用的model。
    c) Angluar将 “name = ‘X’;”应用在model中。(model发生改变)
    d) $digest loop开始
    e) $watch list检测到name的值被改变了,然后再次解析{{name}}表达式,然后更新DOM。
    f) Angulart退出(angular) execution context,再依次退出keydown事件以及javascript execution context;
    g) 浏览器重绘视图,更新字符。

&lt;!DOCTYPE html&gt;

&lt;html lang="zh-cn" ng-app&gt;

&lt;head&gt;

    &lt;meta charset="UTF-8"&gt;

    &lt;title&gt;Hello Kitty!&lt;/title&gt;

    &lt;style type="text/css"&gt;

        .ng-cloak {

            display: none;

        }

    &lt;/style&gt;

&lt;/head&gt;

&lt;body&gt;

    &lt;input ng-model="name"/&gt;

    &lt;p&gt;Hello {{name}}!&lt;/p&gt;

&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

四、Scope
  scope的是负责检测model的变化,并作为表达式的执行上下文(execution context)。Scope是在一个类似于DOM结构的层次结构中嵌套的(据之前了解,划分可能跟controller有关)。(详情查看individualdirective documentation,看看哪个directive会创建新的scope)
  下面的例子展示”name”这个表达式的值是根据它依赖(所属)的scope决定的,而且还包含了值查找的方式(类似Js的作用域链,自己没有就找老爸要)。

&lt;!DOCTYPE HTML&gt;

&lt;html lang="zh-cn" ng-app&gt;

&lt;head&gt;

    &lt;meta charset="UTF-8"&gt;

    &lt;title&gt;scope&lt;/title&gt;

    &lt;style type="text/css"&gt;

        .ng-cloak {

            display: none;

        }

    &lt;/style&gt;

&lt;/head&gt;

&lt;body&gt;

&lt;div ng-controller="ControllerA"&gt;

    Hello {{name}}!;

&lt;/div&gt;

&lt;div ng-controller="ControllerB"&gt;

    Hello {{name}}!;

    &lt;div ng-controller="ControllerC"&gt;

        Hello {{name}}!;

        &lt;div ng-controller="ControllerD"&gt;

            Hello {{name}}!;

        &lt;/div&gt;

    &lt;/div&gt;

&lt;/div&gt;

&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;

    function ControllerA($scope) {

        $scope.name = 'Kitty';

    }

    function ControllerB($scope) {

        $scope.name = 'Lcllao';

    }

    function ControllerC($scope) {

        $scope.name = 'Jeffrey';

    }

    function ControllerD($scope) {

    }

&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

五、Controller
2012090701063773

&lt;!DOCTYPE HTML&gt;

&lt;html lang="zh-cn" ng-app&gt;

&lt;head&gt;

    &lt;meta charset="UTF-8"&gt;

    &lt;title&gt;Controller&lt;/title&gt;

    &lt;style type="text/css"&gt;

        .ng-cloak {

            display: none;

        }

    &lt;/style&gt;

&lt;/head&gt;

&lt;body&gt;

&lt;div ng-controller="ControllerA"&gt;

    Hello {{name}}!

    &lt;button ng-click="doIt()"&gt;DoIt!!&lt;/button&gt;

&lt;/div&gt;

&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;

    function ControllerA($scope) {

        $scope.name = 'Kitty';

        $scope.doIt = function() {

            $scope.name = "Handsome";

        };

    }

&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

  Controller是在view背后的代码(-_-!)。它的职责是构建model,并通过回调函数,将其(model)推送到view中。View是当前scope到template(HTML)的映射(翻译得有点勉强…)。Scope是指挥model到view以及向controller发送event的纽带。
  Controller与view分离是很重要的,因为:Controller是写在javascript中的。Javascript是命令式的(imperative)。命令(imperative)是描述应用程序行为的一个好方法。Controller不应该包含任何显示信息(的逻辑)(DOM引用或者HTML片段),View模版是写在HTML里的。HTML是声明式的。声明式(的HTML)是描述UI的好方法。View不应该包含任何行为。由于Controller不知道自己需要对应哪一个View,使得一个Controller可以(间接)使用多个View。这对于re-skinning(更换皮肤?)、其他设备特定的视图(例如手机与桌面)还有代码的可测性是很重要的。
六、Model
2012090716235049
  Model,可以理解为数据对象。它被用作与模版结合,以产生视图。为了将model写入到视图中,model必须被scope所引用。与很多其他框架不一样,angular对model没有任何限制与要求。不需要额外添加class,也不需要通过特殊的特权方法去访问或者改变model。Model的数据类型,可以是原始的类型(string、number……),可以是键值对象({a:1,b:2}),也可以是函数(function() {…})。简要地说,angular的model只需要是一个普通的javascript对象。
七、View
  view是用户所能看到的东西。view诞生于模版。它与model结合,最终呈现为浏览器DOM。Angular采取一个对于其他很多模版系统来说,很不一样的方式去呈现View。
2012090717415747
       其他模版引擎:很多模版引擎,是通过建立带有特殊标记的HTML字符串来实现的。通常这些模版标记破坏了HTML的语法,这意味着不能通过一般的HTML编辑器去编辑代码(这个嘛…)。模版字符串传入模版引擎,与数据合并。最终生成HTML字符串。这些字符串一般通过.innerHTML的方式写入DOM中,促使浏览器呈现模版内容。当数据发生改变时,这个过程需要一次又一次地重复。模版的粒度与DOM更新的粒度一致。这粒的关键,是模版系统处理字符串。
       Angular:Angular模版的不同之处,在于它是基于DOM的而不是基于字符串的。模版依然需要在HTML中写入一些字符串,但依旧是HTML(不是通过在里面嵌入模版)。浏览器把HTML转换为DOM,然后DOM成为了compiler(angular的模版引擎)的输入。Compiler查找directives,依次在model中设置watches。得出的结果,是一个一直更新的view,不需要重新拼接model与template。model成为了view的唯一数据来源(single source of truth)。
 八、Directives
  Directive是一个行为(例如之前文章的例子“躲猫猫”)或DOM转换(自定义标签,里面包含一组DOM),将其名称放在属性、标签名、class名里面都可以触发该directive。Directive允许你以声明的方式扩展HTML的标签。
  下面的例子,还有一些疑问。就是$render如何触发@_@

&lt;!DOCTYPE HTML&gt;

&lt;html lang="zh-cn" ng-app="myDirective"&gt;

&lt;head&gt;

    &lt;meta charset="UTF-8"&gt;

    &lt;title&gt;directive&lt;/title&gt;

    &lt;style type="text/css"&gt;

        .ng-cloak {

            display: none;

        }

    &lt;/style&gt;

&lt;/head&gt;

&lt;body ng-controller="MyCtrl"&gt;

&lt;div ng-model="content" contenteditable="true"&gt;My Little Dada&lt;/div&gt;

&lt;pre&gt;modelValue = {{content}}&lt;/pre&gt;

&lt;button ng-click="reset()"&gt;reset(change model)&lt;/button&gt;

&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;

    angular.module("myDirective",[])

            .directive("contenteditable",function() {

                return {

                    require:'ngModel',

                    link:function (scope, element, attr, ngModel) {

                        function setVal() {

                            ngModel.$setViewValue(element.text());

                        }

                        // veiw -&gt; model

                        element.bind("keyup",function() {

                            scope.$apply(setVal);

                        });

                        // model -&gt; view

                        ngModel.$render = function(val) {

                            console.log("render running");

                            element.html(val);

                        };

                        //init

                        setVal();

                    }

                }

            }

    ).controller("MyCtrl",function($scope) {

                $scope.reset = function() {

                        $scope.content = "My Little Dada";

                };

            });

&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

九、Filters
  Filters 扮演一个数据转换(格式化)的角色。通常他们是与地域有关的,不同地域也许会有不同的输出格式。他们在追随了Unix过滤器的精神与类似的语法:|  (pipe)

&lt;!DOCTYPE HTML&gt;

&lt;html lang="zh-cn" ng-app&gt;

&lt;head&gt;

    &lt;meta charset="UTF-8"&gt;

    &lt;title&gt;filter&lt;/title&gt;

    &lt;style type="text/css"&gt;

        .ng-cloak {

            display: none;

        }

    &lt;/style&gt;

&lt;/head&gt;

&lt;body&gt;

&lt;div ng-init="list = ['百度B','搜狗S','360','3SB']"&gt;

    数字格式化: 1233211234567 -&gt; {{1233211234567|number}}&lt;br/&gt;

    数组过滤,然后通过json格式输出: &lt;input ng-model="myFilterText" type="text"/&gt;&lt;br/&gt;

    {{list|filter:myFilterText|json}}&lt;br/&gt;

&lt;/div&gt;

&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

 十、Modules and the Injector
2012090812215170
  Injector是一个服务定位器。每一个Angular应用,都会有一个单独的injector。Injector提供一个通过名称查找对象实例的途径。Injector会在内部cache中保持所有对象实例,所以重复调用相同的名称时,返回的都是同一个对象实例。如果对象不存在,那么它会请求实例工厂(instance factory)去创建一个新实例。
  Module是一个配置injector的实例工厂的方法,被称为”provider”。
    <span style="color: #333333;">// Create a module</span>

    var myModule = angular.module('myModule', [])

    // Configure the injector

    myModule.factory('serviceA', function() {

    return {

    // instead of {}, put your object creation here

    };

    });

    // create an injector and configure it from 'myModule'

    var $injector = angular.injector('myModule');

    // retrieve an object from the injector by name

    var serviceA = $injector.get('serviceA');

    // always true because of instance cache

    $injector.get('serviceA') === $injector.get('serviceA');//true

  但是injector的真正牛X的地方在于它可以用于调用方法和”instantiate” type。这个美妙的特性是允许method和types请求他们所依赖的资源,而不是寻找他们。
   <span style="color: #333333;"> // You write functions such as this one.</span>

    function doSomething(serviceA, serviceB) {

    // do something here.

    }

    // Angular provides the injector for your application

    var $injector = ...;

    ///////////////////////////////////////////////

    // the old-school way of getting dependencies.

    var serviceA = $injector.get('serviceA');

    var serviceB = $injector.get('serviceB');

    // now call the function

    doSomething(serviceA, serviceB);

    //上面是传统的老方法~下面是angular说自己的牛X方法

    ///////////////////////////////////////////////

    // the cool way of getting dependencies.

    // the $injector will supply the arguments to the function automatically

    $injector.invoke(doSomething); // This is how the framework calls your functions

  注意,我们唯一需要写的,就是我们的function,在function的arguments中列出方法依赖的资源即可!当angular调用function时,他会使用”call”方法,自动填充function agruments。
  留意下面的例子中是如何在constructor中列出依赖的。当ng-controller实例化controller时,将自动提供所依赖的资源。没有必要去创建、寻找、创建injector引用来加载依赖资源。

&lt;!DOCTYPE HTML&gt;

&lt;html lang="zh-cn" ng-app="timeExample"&gt;

&lt;head&gt;

    &lt;meta charset="UTF-8"&gt;

    &lt;title&gt;injector&lt;/title&gt;

    &lt;style type="text/css"&gt;

        .ng-cloak {

            display: none;

        }

    &lt;/style&gt;

&lt;/head&gt;

&lt;body&gt;

&lt;div ng-controller="ClockCtrl"&gt;

    Current time is : {{time.now}}

&lt;/div&gt;

&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;

    angular.module("timeExample", []).factory("myClock", function ($timeout) {

        var time = {};

        (function tick() {

            time.now = new Date().toString();

            $timeout(tick, 1000);

        })();

        return time;

    });

    /**

     *

     * @param $scope

     * @param myClock 这里自动插入了依赖的myClock!!

     * @constructor

     */

    function ClockCtrl($scope,myClock) {

        $scope.time = myClock;

    }

&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

十一、Angular Namespace
为了防止名称冲突,angular会在object的名称中加入前缀$。请不要在代码中使用$前缀以避免冲突。(-_-!! )
转载目的:用于记录保存资料和学习,感谢原作者分享