AngularJS入门

准备


https://docs.angularjs.org/tutorial

http://www.apjs.net/

AngularJS特性

AngularJS核心特性1 ——MVC

    Q: 现在的很多后端框架都实现了MVC,比如Spring MVC ,前端再做MVC有什么意义?

    A: 在客户端使用类似AngularJS这样的MVC框架,并不是与使用服务端MVC框架互斥的,但是你会发现AngularJS客户端分担了一部分本应出现在服务器端的复杂度。一般来说这是一件好事,因为它将服务器端的工作卸载到了客户端上,允许在更少的服务器负载下提供对更多的客户端的支持。


AngularJS核心特性2 ——模块化Module

AngularJS核心特性3 ——指令系统

AngularJS核心特性4 ——双向数据绑定

双向数据绑定3个问题:


(1)为什么其他所有前端框架都不实现双向数据绑定?

双向数据绑定的核心问题是“脏值检测”。

难点是“震荡问题” 或者说 “循环依赖问题”

(2)如果让你来实现双向数据绑定,你会怎么去实现?

(3)双向数据绑定机制有什么潜在的缺点吗?


AngularJS中的数据绑定


(1)ng-app属性声明所有被其包含的内容都属于这个AngularJS应用,这也是我们可以在Web应用程序中嵌套AngularJS应用的原因。只有被具有ng-app属性的DOM元素包含的元素才会受AngularJS影响。

(2)自动数据绑定使我们可以将视图理解为模型状态的映射。当客户端的数据模型发生变化时,视图就能反映出这些变化,并且不需要写任何自定义的代码,它就可以工作。

(3)当AngularJS认为某个值可能发生变化时,它会运行自己的事件循环来检查这个值是否发变’脏’。如果该值从上次事件循环运行之后就发生了变化,则该值被认为是’脏’值。这也是Angular可以跟踪和响应应用变化的方式。

(4)当Angular关心的事件发生在浏览器中时,比如用户在通过ng-model属性监控的输入字段中输入,或者带有ng-click属性的按钮被点击时,Angular的事件循环都会启动。

Controller使用过程中的注意点

 AngularJS中的控制器是一个函数,用来向视图的作用域中添加额外的功能。我们用它来给作用域对象设置初始状态,并添加自定义行为。


(1)不要试图去复用Controller,一个控制器一般只负责一小块视图

(2)不要在Controller中操作DOM,这不是控制器的职责

        操作DOM效率很低,导致浏览器重绘,一般建议在指令中进行DOM的操作

(3)不要在Controller里面做数据格式化,ng有很好用的表单控件

(4)不要在Controller里面做数据过滤操作,ng有$filter服务

(5)一般来说,Controller是不会相互调用的,控制器之间的交互会通过事件进行

 神奇的$scope


AngularJS启动并生成视图时,会将根ng-app元素同$rootScope进行绑定。$rootScope时所有$scope对象的最上层



(1)$scope是一个POJO(Plain Old JavaScript Object)

(2)$scope提供了一些工具方法$watch()/$apply()/$watchCollection

$apply(expression) 向作用域应用变化

$watch(expression,handler)用来监听数据模型的变化,当expression表达式所引用的值变化时,该函数将会被通知

$watchCollection(object,handler)当指定的object对象的任意属性变化时,该函数将会被通知到

(3)$scope是表达式的执行环境(或者叫作用域)

(4)$scope是一个树型结构,与DOM标签平行

(5)子$scope对象会继承父$scope上的属性和方法

(6)每一个Angular应用只有一个根$scope对象(一般位于ng-app上)

(7)$scope可以传播事件,类似DOM事件,可以向上也可以向下

(8)$scope不仅是MVC的基础,也是后面实现双向数据绑定的基础

(9)可以用angular.element($0).scope()进行调试


一个完整项目结构




ng-bind 和 ng-non-bindable


利用ng-bind代替{{}}取值,避免强刷页面时出现{{}}取值表达式。(建议在入口页面使用)。

内联绑定( {{}} )的缺点是AngularJS将寻找并处理内容中的每一对{{ 和 }} 括号。这有可能成为问题,特别是在混用 JavaScript工具包并想在HTML的某部分上使用一些其他模板系统时(或者只想在文本中使用双括号时)。解决方案是使用 ng-non-bindable 指令,可以阻止AngularJS处理内联绑定

<div ng-non-bindable>
    AngularJS uses {{ and }} characters for templates
</div>


restrict-匹配模式



指令


(1)指令的目的是用来自定义HTML标签,指令是一种标记,用来告诉HTML Parser “这里需要编译”

(2)指令本质上就是AngularJS扩展具有自定义功能的HTML元素的途径。

(3) 通过AngularJS模块API中的.directive()方法,我们可以通过传入一个字符串和一个函数来注册一个新指令。
var myapp = angular.module('myDirective', []);
myapp.directive('toGoogle', function() {
	return {
		restrict: "E",
		template: " < a href = 'http://google.com' > Click me to go to Google < /a>
		"
	};
});


默认情况下,AngularJS将模版生成的HTML代码嵌套在自定义标签<to-google>内部。通过设置replace:true可以将自定义标签从生成的DOM中完全移除掉。

<span style="font-family:SimSun;font-size:14px;">var myapp = angular.module('myDirective', []);

myapp.directive('toGoogle', function() {
	return {
		restrict: "E",
		replace:true,
		template: "<a href = 'http://google.com' > Click me to go to Google </a>"
	};
});
</span>

但是,如果指令下有子元素,希望保留子元素时候该如何做呢?  比如下面<hello>指令,希望保留<hello>中的<div>。

<hello>
	<div>这是指令内部的内容</div>
</hello>

可以通过设置transclude来实现
return 
{
  restrice:"AE",
  transclude:true,
 template:"<div>Hello everyone! <div ng-transclude></div><div>"
 }

注意模版中的ng-transclude指令。

那么,指令如何与控制器进行交互呢? 下面的例子中,loader指令上绑定了滑动加载的事件,它们的控制器分别是MyCtrl和MyCtr2,因此它们的howToLoad绑定的可能是不同的方法
<div ng-controller="MyCtr1">
		<loader howToLoad="loadData()">滑动加载</loader>
	</div>

	<div ng-controller="MyCtr2">
		<loader howToLoad="loadData2()">滑动加载</loader>
	</div>

在指令中,可以通过link函数来实现该功能
myapp.controller("MyCtr1",function($scope)
{
	$scope.loadData=function()
	{
		console.log("加载数据....");
	}

}).controller("MyCtr2",function($scope)
{
	$scope.loadData2=function()
	{
		console.log("加载数据22.。。。");
	}

});

myapp.directive("loader", function() {
    return {
    	restrict:"AE",
    	link:function(scope,element,attrs){
    		element.bind('mouseenter', function(event) {
    			//scope.loadData();
    			// scope.$apply("loadData()");
    			// 注意这里的坑,howToLoad会被转换成小写的howtoload
    			scope.$apply(attrs.howtoload);
    		});
        }
    } 
});

注意函数中的scope、element和attrs参数只是普通的JavaScript参数,而不是通过依赖注入提供的。也就意味着被传入链接函数的对象的顺序是固定的。
如果属性名是以data-为前缀的,AngularJS会在生成传给链接函数的属性集合时移除这一前缀。也就是说,例如,当属性名规范化并传给链接函数时,属性data-list-property 和
list-property 都会被表示为 listProperty

对于compile函数并不常用。以下是一个使用例

<div alotofhello="5">
		  	<p>指令的compile函数!</p>
	</div>



myapp.directive('alotofhello', function() {
    return {
        restrict: 'A',
        compile: function(el,attrs,transclude) {
            //这里开始对标签元素自身进行一些变换
            console.log("指令编译...");
            var tpl = el.children().clone();
            console.log(tpl);
            for (var i = 0; i < attrs.alotofhello - 1; i++) {
                el.append(tpl.clone());
            }
            return function(scope,el,attrs,controller){
                console.log("指令链接...");
            }
        },
        link:function(){
            //compile函数会覆盖link函数
            console.log("我自己的link函数...");
        }
    }
});

指令之间如何进行交互呢?
myapp.directive("superman", function() {
    return {
        scope: {},
        restrict: 'AE',
        controller: function($scope) {
            $scope.abilities = [];
            this.addStrength = function() {
                $scope.abilities.push("strength");
            };
            this.addSpeed = function() {
                $scope.abilities.push("speed");
            };
            this.addLight = function() {
                $scope.abilities.push("light");
            };
        },
        link: function(scope, element, attrs) {
            element.bind("mouseenter", function() {
                console.log(scope.abilities);
            });
        }
    }
});
myapp.directive("strength", function() {
    return {
        require: '^superman',
        link: function(scope, element, attrs, supermanCtrl) {
            supermanCtrl.addStrength();
        }
    }
});
myapp.directive("speed", function() {
    return {
        require: '^superman',
        link: function(scope, element, attrs, supermanCtrl) {
            supermanCtrl.addSpeed();
        }
    }
});
myapp.directive("light", function() {
    return {
        require: '^superman',
        link: function(scope, element, attrs, supermanCtrl) {
            supermanCtrl.addLight();
        }
    }
});

<superman strength>动感超人---力量</superman><br/>
	<superman strength speed>动感超人2---力量+敏捷</superman><br/>
	<superman strength speed light>动感超人3---力量+敏捷+发光</superman><br/>






其他参数:
--terminal是一个布尔型参数,可以被设置为true或false

这个参数用来告诉AngularJS停止运行当前元素上比本指令优先级低的指令。但同当前指令优先级相同的指令还是会被执行。

--template参数是可选的,必须被设置为一下两种形式之一

1)一段HTML文本

2)一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个代表模版的字符串。

--templateUrl是可选的参数,可以是以下类型:

1)一个代表外部HTML文件路径的字符串

2)一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个外部HTML文件路径的字符串

模版加载是异步的,意味着编译和链接要暂停,等待模版加载完成。

--controller参数可以是一个字符串或一个函数。当设置为字符串时,会以字符串的值为名字,来查找注册在应用中的控制器的构造函数:

angular.module(‘myapp’,[])
    .directive(‘myDirective’,function(){
	restrict: ‘A’ ,  //始终需要
	controller : ‘SomeController’
	})

可以在指令内部通过匿名构造函数的方式来定义一个内联的控制器:

angular.module(‘myapp’,[])
   .directive(‘myDirective’,function()
  {
	restrict:’A’ ,
	controller:
	function($scope,$element,$attrs,$transclude)
	{
		//控制器逻辑
	}
	})

-- $scope  与指令相关的当前作用域

$element  当前指令对应的元素

$attrs  当前元素的属性组成的对象

--指令的控制器和link函数可以进行互换。link函数可以将指令互相隔离开来,而controller则定义可复用的行为。

当想要与屏幕上的作用域交互时,可以使用被传入到link函数中的scope参数。


--controllerAs参数用来设置控制器的别名。--require参数可以被设置为字符串或数组。require会将控制器注入到其值指定的指令中,并作为当前指令的链接函数的第四个参数。

require参数的值可以用下面的前缀进行修饰,这回改变查看控制器的行为:

?

如果在当前指令中没有找到所需要的控制器,会将null作为传给link函数的第四个参数

^

如果添加了^前缀,指令会在上游的指令链中查找required参数所指定的控制器。

?^

将前面两个选项的行为组合起来

没有前缀

如果没有前缀,指令将会在自身所提供的控制器进行查找,如果没有找到任何控制器就抛出一个错误。




--compile选项可以返回一个对象或函数

通常情况下,如果设置了compile函数,说明我们希望在指令和实时数据被放到DOM中之前进行DOM操作,在这个函数中进行诸如添加和删除节点等DOM操作是安全的。

compile和link选项是互斥的。如果同时设置了这两个选项,那么会吧compile所返回的函数当作链接函数,而link选项本身则会被忽略

--链接函数是可选的。下面两种定义指令的方式在功能上时完全一样的:
angular.module(‘myApp’,[])
.directive(‘myDirective’,function(){
	return {
		pre:function(tElement,tAttrs,transclude)
		{
				//….	
		},
		post:function(scope,iElement,iAttrs,controller)
		{
				//….
		}
	};

});


angular.module(‘myApp’,[])
.directive(‘myDirective’,function()
{
	return {
		link:function(scope,ele,attrs)
		{
			pre:function(tElement,tAttrs,transclude)
			{
				//….	
			},
			post:function(scope,iElement,iAttrs,controller)
			{
				//….
			}
	
		}

	}

})


当定义了编译函数来取代链接函数时,链接函数是我们能提供给返回对象的第二个方法,也就是postLink函数。本质上讲,这个事实正说明了链接函数的作用。它会在模版编译并同作用域进行链接后被调用,同时它负责设置事件监听器,监视数据变化和实时的操作DOM。

链接函数的签名如下:

ink:function(scope,element,attrs){
	//在这里操作DOM
}

下面看一下链接函数中的参数

scope

指令用来在其内部注册监听器的作用域

iElement

iElement参数代表实例元素,指使用此指令的元素。在postLink函数中我们应该只操作此元素的子元素,因为子元素已经被链接过了。

iAttrs

iAttrs参数代表实例属性,是一个由定义在元素上的属性组成的标准化列表

controller

controller参数指向require选项定义的控制器。如果没有设置require选项,那么controller参数的值为undefined



(4)下面都是用来声明前面创建指令的合法格式:

<my-directive></my-directive>

<div my-directive></div>

<div class=“my-directive”></div>

<!- - directive:my-directive - ->

为了让AngularJS能够调用我们的指令,需要修改指令定义中的restrict设置。这个设置告诉AngularJS在编译HTML时用哪种声明格式来匹配指令定义。元素(E)、属性(A)、类(C)或注释(M)。

建议使用属性方式,因为它有比较好的浏览器兼容性。


(5)有好几种途径可以设置指令内部作用域的值。最简单的方法就是使用由所属控制器提供的已经存在的作用域。但共享状态会导致很多其他问题。如果控制器被移除,或者在控制器的作用域中也定义了一个叫myUrl的属性,我们就被迫要修改代码。AngularJS允许通过创建新的子作用域或者隔离作用域来解决这个问题。
scope{
	someProperty: “needs to be set”
}

 实际上创造的是隔离作用域。本质上,意味着指令有了一个属于自己的$scope对象。

cope{
	someProperty: ‘@’
}


我们在作用域对象内部把someProperty值设置为@这个绑定策略。这个绑定策略告诉AngularJS将DOM中some-property属性的值复制给新作用域对象中的someProperty。如果想显示地指定绑定的属性名,可以用:

scope{
	someProperty: ‘@someAttr’
}

例:

myapp.directive('myLink',function()
{
	return {
		restrict: "A",
		replace:true,
		scope:{
			myUrl: '@' ,//绑定策略
			myLinkText: '@', //绑定策略
		},
		template: "<a href='{{ myUrl }}'>{{ myLinkText }}</a>"
	};
});


 <div my-link my-url="http://google.com" my-link-text="Click me to go to Google">
    </div>


(7)指令嵌套并不一定意味着需要改变它的作用域。默认情况下,子指令会被赋予父DOM元素对应的的作用域能力。指令scope默认值为false
(8)scope 参数是可选的,可以被设置为true或一个对象。默认值为false。

当scope设置为true时,会从父作用域继承并创建一个新的作用域对象。

关于scope的三种不同的设置 false、true、{}  可以参考这篇文章  http://segmentfault.com/a/1190000002773689

(9)AngularJS提供了几种方法能够将指令内部的隔离作用域,同指令外部的作用域进行数据绑定。

    本地作用域属性:使用@符号将本地作用域同DOM属性的值进行绑定。

     @(or @attr)

     双向绑定:通过=可以将本地作用域上的属性同父级作用域上的属性进行双向的数据绑定。

    =(or @attr)

     父级作用域绑定 :通过&符号可以对父级作用域进行绑定,以便在其中运行函数。意味着对这个值进行设置时会产生一个指向父级作用域的包装函数。

     &(or @attr)

例:

 <input type=“text” ng-model=“to” />
  <div scope-example ng-model=“to” on-send=“sendMail(email)” from-name=“ari@fullstack.io” />


这里有一个数据模型(ng-model),一个函数(sendMail())和一个字符串(from-name)

<span style="font-family:SimSun;font-size:14px;"> scope:
 {
	ngModel: ‘=’ ,    //将ngModel同指定对象绑定
	onSend: ‘& ’ ,    //将引用传递给这个方法
	fromName: ’@’ //储存与fromName相关联的字符串
  }
</span>




(10)指令的三个阶段



内置指令


(1)ng-disabled

   下面的例子中按钮会一直禁用,直到用户在文本字段中输入内容

<input type="text" ng-model="someProperty" placeholder="Type to Enable">
  <button ng-model="button" ng-disabled="!someProperty">A Button</button>


(2)ng-href 和ng-src 都能有效帮助重构和避免错误,因此在改进代码时强烈建议使用它们代替原来的href和src属性

 (3)ng-app 为AngularJS应用创建$rootScope,ng-controller则会以$rootScope或另外一个ng-controller的作用域作为原型创建新的子作用域。 

(4)使用ng-if指令可以完全根据表达式的值在DOM中生成或移除一个元素。ng-if同ng-show和ng-hide指令最本质的区别是,它不是通过css显示或隐藏DOM节点,而是真正生成或移除节点。

(5)ng-repeat用来遍历一个集合或为集合中的每个元素生成一个模版实例。同时每个模版实例的作用域都会暴露一些特殊的属性。

  $index: 遍历的进度(0 ,1,2,….,length-1)

  $first: 当元素是遍历的第一个时值为true

  $middle: 当元素处于第一个和最后元素之间时值为true

  $last: 当元素是遍历的最后一个时值为true

  $even: 当$index值是偶数时值为true

  $odd: 当$index值是奇数时值为true

  下面的例子展示了如何用$odd和$even来制作一个红蓝相间的列表。由于索引从0开始,因此用!$even和!$odd来将$even和$odd反转。

<ul ng-controller="PeopleController">
		<li ng-repeat="person in people" ng-class="{even: !$even, odd: !$odd}">{{person.name}} lives in {{person.city}}</li>
	</ul>

myapp.controller('PeopleController', function($scope) {
  $scope.people = [
    {name: "Ari", city: "San Francisco"},
    {name: "Erik", city: "Seattle"}
  ];
});


(6)ng-init指令用来在指令被调用时设置内部作用域的初始状态

(7){{ }} 语法是AngularJS内置的模版语法,它会在内部$scope和视图之间创建绑定。事实上它也是指令,虽然看起来并不像,实际上它是ng-bind的简略形式。注意,在屏幕可视的区域内使用{{ }}会导致页面加载时未渲染的元素发生闪烁,用ng-bind可以避免这个问题

(8)同ng-bind指令类似,ng-bind-template用来在视图中绑定多个表达式

<h2 ng-bind-template="{{greeting1}} {{greeting2}}"></h2> 

(9)ng-model指令用来将input、select、text area或自定义表单控件同包含它们的作用域中的属性进行绑定。我们应该始终使用ngModel来绑定$scope加上功能一个数据模型内的属性,而不是$scope上的属性,这可以避免在作用域或后代作用域中发生属性覆盖。像这样直接在作用域上定义属性:

$scope.dataValue="Hello canghong"

意味着使用ng-model指令时将会创建局部变量,并使用一个对象作为中介者,类似这样:

$scope.data ={
   dataValue :"Hello canghong"
}

浙江确保ng-model会对在父作用域上定义的数据值进行更新


(10)ng-change 这个指令会在表单输入发生变化时计算给定表达式的值。

(11)ng-form用来在一个表单内部嵌套另一个表单。普通HTML <form>标签不允许潜逃,但ng-form可以。

(12)ng-select 用来将数据同HTML的<select>元素进行绑定。这个指令可以和ng-model以及ng-options指令一同使用,构建精细且表现优良的动态表单。

 <div ng-controller="CityController">
	  <select ng-model="city" ng-options="city.name for city in cities">
	    <option value="">Choose City</option>
	  </select>
	  Best City: {{ city.name }}
</div>


<span style="font-family:SimSun;font-size:14px;">angular.module('myApp', [])
.controller('CityController', function($scope) {
    $scope.cities = [
      {name: 'Seattle'},
      {name: 'San Francisco'},
      {name: 'Chicago'},
      {name: 'New York'},
      {name: 'Boston'}
    ];
});
</span>

用这种方式,选择出的是一个object 对象。

下面介绍4种不同的使用方法,不同点,可以自己查看页面生成的源代码

 <select ng-model="" ng-options="item.action for item in todos" >
      <option value="">(Pick One)</option>
    </select>

    <select ng-model="" ng-options="item.id as item.action for item in todos" >
      <option value="">(Pick One)</option>
    </select>

     <select ng-model="" ng-options="item.id group by item.place for item in todos" >
      <option value="">(Pick One)</option>
    </select>

     <select ng-model="" ng-options="item.action group by item.place for item in todos" >
      <option value="">(Pick One)</option>
    </select>

$scope.todos=[
            {id:100,action:"Get groceries",complete:false},
            {id:200,action:"Call plumber",complete:false},
            {id:300,action:"Buy running shoes",complete:true}];

(13)ng-submit用来将表达式同onsubmit事件进行绑定。这个指令同时会阻止默认行为。

(14)使用ng-class动态设置元素的类,方法是绑定一个代表所有需要添加的类的表达式。

(15)ngModel是一个用法特殊的指令,它提供更低层的API来处理控制器内的数据。ngModel控制器ngModelController会随ngModel被一直注入到指令中,其中包含了一些方法。在该控制器中定义$render方法可以定义视图具体的渲染方式。这个方法会在$parser流水线完成后被调用。接下来

ngModelController中有几个属性可以用来检查甚至修改视图。

$render()  这是当数据绑定的值发生变化时ngmodel控制器调用更新UI的函数

$setViewValue(value)  更新数据绑定的值

注意调用$setViewValue方法不会导致ngmodel控制器调用$render方法。

$viewValue   属性保存着更新视图所需的实际字符串

$modelValue  由数据模型持有。$modelValue和$viewValue可能是不同的,取决于$parser流水线是否对其进行了操作

$parser 的值是一个由函数组成的数组,其中的函数会以流水线的形式被逐一调用。ngModel从DOM中读取的值会被传入$parser中的函数,并依次被其中的解析器处理

formatters 的值是一个由函数组成的数组,其中的函数以流水线的形式在数据模型的值发生变化时被逐一调用。它和$parser流水线互补影响,用来对值进行格式化和转换,以便在绑定了这个值的空间中显示

$viewChangeListeners  的值是一个由函数组成的数组,其中的函数会以流水线的形式在视图中的值发生变化时被逐一调用。通过$viewChangeListeners,可以在无需使用$watch的情况下实现类似的行为。由于返回值被忽略,因此这些函数不需要返回值。

$error 对象中保存着没有通过验证的验证器名称以及对应的错误信息。

$pristine  的值是布尔型的,可以告诉我们用户是否对控件进行了修改

$dirty 的值和$pristine相反,可以告诉我们用户是否和控件进行过交互

$valid 值可以告诉我们当前的控件中是否有错误。当有错误时值为false,没有错误时值为true

$invalid 值可以告诉我们当前控件中是否存在至少一个错误,它的值和$valid相反

(16)ng-cloak指令使用css对被应用到的元素进行隐藏,当内容被处理过后AngularJS库移除css样式





Service的特性


 服务提供了一种能在应用的整个生命周期内保持数据的方法,它能够在控制器之间进行通信,并且保证数据的一致性。

服务是一个单例对象,在每个应用中只会被实例化一次(被你injector实例化),并且是延迟加载的(需要时才会被创建)。

如果希望在config()函数中可以对服务进行配置,必须用provider()来定义服务。

可以将一个已经存在变量值注册为服务,并将其注入到应用的其他部分。

angular.module(‘myApp’).constant(‘apiKey’,’123123’)

如果服务的$get方法返回的是一个常量,那就没必要定义一个包含复杂功能的完整函数。

angular.module(‘myApp’).value(‘apiKey’,’123123’);

value和constant方法最主要的区别是,value不可以注入的配置函数config中。


(1)服务特性

   1)Service都是单例的

   2)Service由$injector负责实例化

   3)Service在整个应用的生命周期中存在,可以用来共享数据

   4)在需要使用的地方利用依赖注入机制注入Service

   5)自定义的Service需要写在内置的Service后面

   6)内置Service的命名以$符号开头,自定义Service应该避免

   7)Service、Provider、Factory本质上都是Provider

   8)Provider模式是“策略模式”+“抽象工厂模式”的混合


(2) $provider服务提供了在服务实例创建时对其进行拦截的功能,可以对服务进行扩展,或者用另外的内容完全代替它。装饰器是非常强大的,它不仅可以应用在我们自己的服务上,可以对AngularJS的核心服务进行拦截、中断甚至替换功能的操作。事实上AngularJS很多功能的测试就是借助$provide.decorator()建立的。

decorate()函数可以接受两个参数。

--name(字符串) :将要拦截的服务名称

--decoratorFn (函数):在服务实例化时调用该函数,这个函数由injector.invoke调用,可以将服务注入这个函数中。

$delegate是可以进行装饰的最原始的服务,为了装饰其他服务,需要将其注入进装饰器。

 使用$filter服务


(1)$filter时用来进行数据格式化的专用服务

         使用$filter可以通过名称找到对应的过滤器

 

myapp.directive('highlight',function($filter)
{
    var myFilter = $filter('filterName');//获取过滤器
    
    return function(scope,element,attrs)
    {
      //可以使用myFilter过滤器了
     }

})

(2)AngularJS内置了9个filter:

   currency, date , filter, json, limitTo, lowercase, number, orderBy, uppercase

    ---filter过滤器可以从给定数组中选择一个子集,并将其生成一个新数组返回。

    ---json过滤器可以将一个JSON或JavaScript对象转换成字符串。

    ---limitTo过滤器将会根据传入的参数生成一个新的数组或字符串,新的数组或字符串的长度取决于传入的参数,通过传入参数的正负值来控制从前面还是后面开始截取

    ---lowercase过滤器将字符串转换为小写

    ---number过滤器将数字格式化成文本。它的第二个参数是可选的,用来控制小数点后截取的位数。

    --- orderBy过滤器可以用表达式对指定的数组进行排序


<tr ng-repeat="p in products | orderBy: 'price' ">
注意此处队属性名使用了引号: 'price'  而不是price 。如果你在往指令的表达式里手写一个属性名时忘记了引号,orderBy过滤器将会悄无声息地失败。没有引号,过滤器会假设你想使用一个名为price的作用域变量或者控制器变量,并且认为你将来在某处会着手定义这个变量。
可以通过显示地使用+或-字符来设置排序顺序
<tr ng-repeat="p in products | orderBy:'-price'  ">


    ----uppercase过滤器将字符串转换为大写形式

(3)filter可以嵌套使用(用管道符号|分隔)

(4)filter是可以传递参数的

(5)用户可以定义自己的filter

使用$interpolate

$interpolate将字符串标记编译为一个插值函数。 什么意思呢,官网给出了一个非常简单明了的例子

var $interpolate = ...; // injected
var exp = $interpolate('Hello {{name | uppercase}}!');
expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');

也就是说,$interpolate将字符串 "Hello {{name | uppercase}}!"编译成了一个插值函数,因此,当调用exp({name:'Angular'})时,name的值被插入到了 "Hello { Angular | uppercase}}!" 。因此结果就是 Hello ANGULAR!


 使用$inject服务

inject一般用来获取服务或者进行依赖注入。通过$inject的 invoke方法,直接获得服务 。而annotate是一个非常神奇的函数,用来分析函数的参数。

var myModule = angular.module("MyModule", []);

myModule.factory('game', function() {
    return {
        gameName: '大漠吃豆子'
    }
});

myModule.controller('MyCtrl', ['$scope', '$injector',
    function($scope, $injector) {
        // console.log($scope);
         $injector.invoke(function(game) {
             console.log(game.gameName);
         });
        // console.log($injector);
        //console.log($injector.annotate(function(arg0,arg1,arg2){}));
    }
]);





 compile与link的区别


(1)scope在链接阶段才会被绑定到元素上,因此compile阶段操作scope会报错

(2)link的作用是在模型和视图之间建立关联,包括在元素上注册事件监听

(3)scope在链接阶段才会被绑定到元素上,因此compile阶段操作scope会报错;

(4)对于同一个指令的多个实例,compile只会执行一次;而link对于指令的每个实例都会执行一次

(5)一般情况下我们只要编写link函数就够了

(6)请注意,如果你编写的自定义的compile函数,自定义的link函数无效,因为compile函数应该返回一个link函数供后续处理

表单


(1)如果想要屏蔽浏览器对表单的默认验证行为,可以在表单元素上添加novalidate标记

(2)AngularJS表单验证 属性

如果有任何input符合下面的描述,angular将会在该input上添加相应的Property和Class



(3)AngularJS中如何访问表单属性

 1)访问表单: <form name>.<angular property>

 2)访问input:<form name>.<input name>.<angular>

(4)ng-repeat的使用会造成一定问题,比如点击一个条目所有的相同name表单元素都会改变。可以通过以下方式解决


<html ng-app="FormModule2">
<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<script src="framework/angular-1.4.7/angular.js"></script>
	<script src="js/form2.js"></script>
</head>
<body>
	<div ng-controller="FormController">
		<form name="uerForm" novalidate>
			<div  ng-repeat="user in formData.users">
			{{ user.name }}
			<ng-form name="userFiledForm">
				<label>{{ user.name }}'s Email </label>
				<input type="text" name="email" ng-model="user.email" required />
				<p ng-show="userFiledForm.email.$invalid">Valid Email Address Required</p>
			</ng-form>
			</div>
		</form>
	</div>
</html>


var form= angular.module('FormModule2',[]);

var users=[
	
	{
		name:"CangHong",
		email:""
	},
	{
		name:"ChenHong",
		email:""
	}
	 ];

form.controller("FormController",['$scope',function($scope)
{

	$scope.formData={};
	$scope.formData.users=users;
		
}]);


(5)在表单中的控制变量

   1)  未修改的表单

   

formName.inputFieldName.$pristine

    这是一个布尔属性,用来判断用户是否修改了表单。如果未修改,值为true,如果修改过值为false。

   2)修改过的表单

   

formName.inputFieldName.$dirty

    只要用户修改过表单,无论输入是否通过验证,该值都返回true

  3)合法的表单
formName.inputFieldName.$valid

   这个布尔型的属性用来判断表单的内容是否合法。如果当前表单内容是合法的, 该属性的值就是true

  4)不合法的表单
formName.inputFieldName.$invalid

 这个布尔属性用来判断表单的内容是否不合法。如果当前表单的内容是不合法的,该属性的值为true

 5)错误
 formName.inputFieldName.$error

这是AngularJS提供的另外一个非常有用的属性:$error对象。它包含当前表单的所有验证内容,以及它们是否合法的信息。

     如果验证失败,这个属性的值为true;如果值为false,说明输入字段的值通过了验证。

(6)一些有用的css样式

 AngularJS处理表单时,会根据表单当前的状态添加一些css类

.ng-pristin{}

.ng-dirty{}

.ng-valid{}

.ng-invalid{}


(7)从1.3开始,Angular中新增了ngMessages指令,使用该指令需要两步

   1)引用 angular-message.js 文件

   2)告诉Angular将ngMessages作为应用程序的依赖模块引入

        angular.module(‘myApp’,[‘ngMessage’]);

(8)AngularJS的校验具有持续性,意味着一个空的、未和用户发生过交互的input元素如果定义了required属性,将会是无效状态,因为还未输入值。如果不想用户在开始输入数据前显示错误信息,就检查$dirty是否为true,表示只有当用户与元素发生过交互后才显示错误信息。
<div class="error" ng-show="myForm.userEmail.$invalid && myForm.userEmail.$dirty">
  $error 对象直到校验出问题时才会被定义
(9)ng-true-value  和 ng-false-value 属性的值将被用于设置所绑定的表达式的值,但是只在当复选框的勾选状态被改变时生效。也就是说模型属性不会被自动创建,直到有用户与元素的交互产生时才会被创建。
   
<input name="sample" type="checkbox" ng-model="inputValue" ng-true-value="Hurrah!" ng-false-value="Boo!">



路由

(1) 路由事件

1)$routeChangeStart

 AngularJS会在路由变化之前广播$routeChangeStart事件。带有两个参数

- 将要导航到的下一个URL

- 路由变化前的URL

2)$routeChangeSuccess

AngularJS会在路由的依赖被加载后广播$routeChangeSuccess事件。带有三个参数

-原始的AngularJS evt对象

-用户当前所处的路由

-上一个路由

3)$routeChangeError

AngularJS会在任何一个promise被拒绝或失败时广播$routeChangeError事件。带有三个参数

-当前路由的信息;

- 上一个路由的信息

- 被拒绝的promise的错误信息

4)$routeUpdate

AngularJS在reloadOnSearch属性被设置为false情况下,重新使用某个控制器的实例时,会广播$routeUpdate事件



$q

promise是一种用异步方式处理值(或者非值)的方法。想要在Angular中创建promise,可以使用内置的$q服务。要创建一个deferred对象,可以调用defer()方法。Angular的$q deferred对象是可以串成链的,这样即使是then,返回的也是一个promise。这个promise一被执行,then返回的promise就已经是resolved或者rejected的了。


事件

1) Angular事件系统并不与浏览器的事件系统相通,这意味着,我们只能在作用域上监听Angular事件而不是DOM事件。

2) 要把事件沿着作用域链向上派送(从子作用域到父作用域),我们需要使用$emit()函数。该函数带有两个参数:

 --name(字符串):要发出的事件的名称。

 --args(集合):一个参数的集合,作为对象传递到事件监听器中。

3)要把事件向下传递(从父作用域到子作用域),我们使用$broadcast()函数。该函数带有两个参数

--name(字符串):要发出的事件名称

--args(集合):一个参数的集合,作为对象传递到事件监听器中

4)从监听器中发出的一切异常都会被传递到$exceptionHandler服务中。

    $exceptionHandler服务处理未捕获的异常。你可以使用JavaScript的try ....catch块来捕获异常,它将不被服务处理。

5)要监听一个事件,我们可以使用$on()方法。这个方法为具有某个特定名称的事件注册一个监听器。

6)事件对象有以下属性

--targetScope(作用域对象):这个属性是发送或者广播事件的作用域

--currentScope(作用域对象):这个属性包含了当前处理事件的作用域

--name(字符串):这个字符串是触发之后,我们正在处理的事件名称

--stopPropagation(函数):该函数取消通过$emit触发的事件的进一步传播

--preventDefault(函数):preventDefault把defaultPrevented标记设置为true。尽管不能停止事件的传播,我们可以告诉子作用域无需处理这个事件。

--defaultPrevented(布尔值):调用preventDefault()会把defaultPrevented设置为true

7) 核心系统的$emitted事件

--$includeContentLoaded : $includeContentLoaded事件当ngInclude的内容重新加载时,从ngInclude指令上触发。

--$includeContentRequested:$includeContentRequested事件从调用ngInclude的作用域上发送。每次ngInclude的内容被请求时,它都会被发送。

--$viewContentLoaded:$viewContentLoaded事件每当ngView内容被重新加载时,从当前ngView作用域上发送。

8)核心系统的$broadcast事件

--locationChangeStart:当Angular从$location(通过$location.path()、$location.search()等)对浏览器的地址作更新时,会出发$locationChangeStart事件。URL被触发前,可以在Event对象中调用preventDefault方法来阻止URL改变。

--locationChangeSuccess:当且仅当浏览器的地址成功变更,又没有阻止$locationChangeStart事件的情况下,$locationChangeSuccess事件会从$rootScope上广播出来

--routeChangeStart:在路由变更之前,$routeChangeStart事件从$rootScope发送出来

--routeChangeSuccess:在所有路由依赖项跟着$routeChangeStart被解析,$routeChangeSuccess被从$rootScope上广播出来

--routeChangeError:如果路由对象上任意的resolve属性被拒绝了,$routeChangeError就会被出发

--routeUpdate:如果$routeProvider上的reloadOnSearch属性被设置成false,并且使用了控制器的同一实例,$routeUpdate事件会被从$rootScope上广播

--destroy:在作用域被毁之前,$destroy事件会在作用域上广播



缓存


1) $cacheFactory是一个为所有Angular服务生成缓存对象的服务。在内部,$cacheFactory会创建一个默认的缓存对象,即使我们并没有显示地创建。

2)Angular的$http服务创建了一个带有ID为$http的缓存。要让$http请求使用默认的缓存对象很简单:$http()方法允许我们给它传递一个cache参数。

3)为了引用$http的默认请求,只需通过$cacheFactory()使用ID来获取到该缓存:
var cache = $cacheFactory(‘$http’)

安全

1)严格的上下文转义: $sce服务,通过使用指令包装授权请求的方式,保障文本输入框的安全性。

2)使用 $sceDelegateProvider.resourceUrlWhitelist()方法设置新的白名单。默认情况下,白名单被设置为[‘self’]

3)使用$sceDelegateProvider.resourceUrlBlacklist()方法设置新的黑名单。默认情况下,黑名单被设置为一个空数组[]

4)$sanitize  将HTML字符串中的危险字符替换为与之对应的转义字符

 动画

(1) ng-enter定义起始点和转变细节。ng-enter-active定义转变的结束点


 其他内容

(1) AngularJS允许我们使用angular.module()方法来声明模块,这个方法能够接受两个参数,第一个是模块的名称,第二个是依赖列表,也就是可以被注入到模块中的对象列表。

 angular.module(‘myApp’,[]);


(2)在模块的加载阶段,AngularJS会在提供者注册和配置的过程中对模块进行配置。在整个AngularJS的工作流中,这个阶段是唯一能够在应用启动前进行修改的部分。

angular.module(‘myApp’,[]).config(function($provide)
{
})

和配置块不同,运行块在注入器创建之后被执行,它是所有AngularJS应用中第一个被执行的方法。运行块通常用来注册全局的事件监听器。例如,我们会在.run()块中设置路由事件的监听器以及过滤未经授权的请求。也就是说

config方法接收一个函数,该函数在调用方法的模块被加载后调用。

run方法也可以接收一个函数,但是函数只会在所有模块加载完后以及解析完它们的依赖后才会被调用。

(3)$location服务用于解析地址栏中的URL,并让你可以访问应用当前路径所对应的路由

      // 以 http://localhost:9000/app/test.html#/ 为例
            $scope.setUrl = function(component){
              switch(component){
                case "reset":  //url变为http://localhost:9000/app/test.html#/
                    $location.path("");
                    $location.hash("");
                    $location.search("");
                    break;  
                case "path": //url后加上地址 /cities/london  http://localhost:9000/app/test.html#/cities/london
                    $location.path("/cities/london");
                    break;
                case "hash": //url后加上#north   http://localhost:9000/app/test.html#/#north
                    $location.hash("north");
                    break; 
                case "search": //url后加上 ?select=hotesls http://localhost:9000/app/test.html#/?select=hotels
                    $location.search("select","hotels");
                    break;
                case "url": //url变为  http://localhost:9000/app/test.html#/cities/london?select=hotels#north
                    $location.url("/cities/london?select=hotels#north");
                    break;
              }



(4)$injector负责实例化AngularJS中所有组件,包括应用的模块、指令和控制器等。

可以通过$inject属性来实现显示注入声明的功能

(5)AngularJS传递给then()方法的响应对象包含几个属性。

1)data(字符串或对象): 这个数据代表转换过后的响应体(如果定义了转换的话)

2)status(数值型):响应的HTTP状态码

3)headers(函数):这个函数是头信息的getter函数,可以接受一个参数,用来获取对应名字的值。

4)config(对象):这个对象时用来生成原始请求的完整设置对象

5)statusText(字符串):这个字符串是响应HTTP状态文本

(6)AngularJS提供了一个非常有用的可选服务$resource,这个服务可以创建一个资源对象,可以用它非常方便地同支持RESTful的服务端数据源进行交互。

(7)ngResourc模块是一个可选的AngularJS模块,支持与RESTful的后端数据源进行交互。

<script src=“bower_components/angular-resource/angular-resource.js"></script>
angular.module(‘myAPP’,[‘ngResource’]);


(8)为了在AngularJS中使用CORS,需要进行设置

angular.module(‘myApp’,[])

.config(function($httpProvider){

$httpProvider.defaults.useXDomain = true;

delete $httpProvider.defaults.headers.common[‘X-Requested-With’];

})

(9) AngularJS通过拦截器提供了一个从全局层面对响应进行处理的途径
angular.module(‘myapp’,[])
.factory(‘myInterceptor’,function($q)
{
     var interceptor = {
	‘request’ : function(config)
		{
			//成功的请求方法
			return config; //或者 $q.when(config)
		},
	‘response’:function(config)
		{
			//响应成功
			return response; //或者 $q.when(config)
		},
	‘requestError’:function(rejection)
		{
			//请求发生错误,如果能从错误中恢复,可以返回一个新的请求或promise
			return response;  //或新的 promise
			//或者,可以通过返回一个rejection来阻止下一步
			// return $q.reject(rejection)
		},
	‘responseError’: function(rejection)
		{
			//请求发生了错误,如果能从错误中恢复,可以返回一个新的响应或promise
			return rejection ; //或新的promise
			//或者,可以通过返回一个rejection来阻止下一步
			// return $q.reject(rejection);	
		}
	};
	return interceptor;
});

我们需要使用$httpProvider在.config函数中注册拦截器:

angular.module(‘myApp’,[])
.config(function($httpProvider)
{
	$httpProvider.interceptors.push(‘myInterceptor’);
})
(10)$anchorScroll服务滚动浏览器窗口到显示id 与 $locaiton.hash方法返回值一致的元素处。$anchorScroll与普通服务不同,因为你并非一定要使用服务对象,仅仅需要声明依赖。当创建服务对象时,它就开始监听$location.hash值,然后在其改变时自动滚动。你也可以通过服务提供器禁用自动滚动,它允许你调用$anchorScroll服务作为函数来选择性地滚动。

测试

1)Jasmine套件的核心部分是describe函数。describe()函数带有两个参数,一个字符串,一个函数。字符串是待建立的细则(spec)套件名称或者描述,函数封装了测试套件。

2)通过调用it()函数来定义一个细则。it()函数带有两个参数:一个字符串,是细则的标题或者描述;一个函数,包含了一个或多个用于测试代码功能的预期。

3)使用expect()函数来建立预期。expect()函数带有一个单值参数。这个参数被称为真实值。要建立一个预期,我们给它串联一个带单值参数的匹配器函数,这个参数就是期望值。

4)内置函数

-toBe() 匹配器使用JavaScript操作符===来比较值

-toEqual()匹配器比较的是值,对简单字面量和变量有效;

-toMatch()匹配器使用正则表达式匹配字符串;

-toBeDefined()匹配器将值与undefined进行比较

-toBeUndefined()匹配器的功能跟toBeDefined()匹配器相反

-toBeNull()匹配器将值与null进行比较

-toBeTruthy()匹配器把值转换成布尔类型后与true进行比较

-toBeFasly()匹配器把值转换成布尔类型后与false比较

-toContain()匹配器检测一个条目是否在数组中

-toBeLessThan()匹配器建立一个期望,比较一个数值是否小于预期

-toBeGreaterThan()匹配器建立一个期望,比较一个数值是否大于预期

-toBeCloseTo()匹配器在一个指定的精度级别内比较一个值是否接近另一个值

-toThrow()匹配器验证一个函数是否抛出了异常

5)要在单元测试中建立模拟对象,需要确保在Karma配置中包含angular-mock.js文件。因此,必须确保test/karma.conf.js文件的files数组中包含了angular-mock.js。包含了这个依赖之后,就可以创建Angular模块的模拟引用了。

describe(‘myApp’,function()

{

var myService ;

//模拟我们的’myApp’ angular模块 

beforeEach(module(‘myApp’));

beforeEach(

inject(function(_myService_)

{

myService =_myService_;

}));

it(‘……’)

});

6)Angular也内置了$httpBackend模拟库,这样我们可以在应用中模拟任何外部的XHR请求,避免在测试中创建昂贵的$http请求。$httpBackend仅在单元测试中使用。

Q & A

Q: 如何把一个Model绑定到多个View ?
A: 观察者模式
Q: 如何才能知道Model发生了变化? 
A: (脏值检测$watch与$digest)
Q: 如果Model是深层嵌套结构,如何知道某个属性是不是变了?
A:  对象深比较
Q: A和B两个方法互相watch对方的时候,如何避免发生“振荡”? 
A:  TTL机制





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值