1.angular 基本概念
-
类库( 提供类方法 ) 和框架
-
类库提供一系列的函数和方法的合集,能够加快你写代码的速度。但是主导逻辑的还是自己的代码。常用的类库 eg: jquery
-
框架 特殊的已经实现了 web 的应用。只需要按照其逻辑填充你的业务逻辑就能得到完整的应用
-
-
angular 的特点
-
提供端对端的解决方案
构建一个 CRUD(add retrieve update delete) 应用的全部内容:`数据绑定,表单验证,路由,深度链接,组件重用,依赖注入` 测试方案: `单元测试, 端对端测试,模拟和自动化测试` 具有各种种子应用作为模板和起点
-
特点
angular 主要考虑构建 CRUD 应用,并不是所有的应用都适合使用 angular 来构建 例如游戏,图形编辑界面就不适合使用 angular
-
-
angular 的标榜概念
-
angular 认为声明式的代码比命令式的代码更加符合 构建 (视图 + 软件)逻辑的代码
声明式的语言 :提前将所有的操作内置,使用时只需要按照规定声明该操作,语言或者机器本身可以进行构建应用 声明式的语言介绍:HTML 就是声明式的结构,比如需要某个元素居中,不需要告诉浏览器具体的行为(需要找到元素的中间位置,将元素放在那里),只需要添加一个 align='center' 的属性给新元素的可以了。这就是声明式的语言 声明式的语言也有不好的地方,就是所有可以使用的操作已经提前内置,所以他不能识别新的语法结构,比如你想让元素居左 1/3 处就很难处理
-
将 DOM 操作和应用逻辑解耦
-
将测试和开发同等看待
-
大幅度减少应用中需要使用的各种 回调的逻辑,摆脱大量的回调逻辑
-
解放DOM 操作,
-
对页面的UI操作可控,例如大量的DOM事件
-
angular 已经有了许多搭建好的基础服务框架
-
-
angular 的初始化信息
angular 会在 DOMContentLoaded 事件触发时执行, 通过 ng-app 指令 寻找你的应用的根作用域
1. 首先载入和指令相关的模块 2. 穿件应用的 注入器(injector) 3. 将 ng-app 作为根节点编译 DOM 。
也可以使用 angular.bootstrap( 节点 ) 来手动装载节点
2. angular 的指令
指令的定义:由一个新的属性,元素名称,css类名等带来DOM 样式或者行为的改变。
指令( angular 的行为扩展 ):HTML 编译器,能够识别新的 HTML 语法,可以将行为动作关联到HTML或者其属性上面,设置可以创造自定义行为的元素,可复用。
注意指令是在最开始的时候被载入页面的
指令本质上就是一个代用功能的函数 ** return 一个函数 **,类比于 react 的自定义组件
** angular API 有几个大的的分类 **
ng.function ( 功能函数,类比于jquery 的方法函数 )
** ng.directive( angular 的重大模块,eg: ng-model 等 ) **
** ng.provider ( 依赖注入功能 )**
.......
3. angular 的 编译器( compiler )
编译器通过遍历 DOM 来查找和关联属性, 其分为 编译 和 链接 两个阶段
-
编译:遍历所有的 DOM 收集指令,生成一个 链接函数集合
-
链接:将指令和作用域绑定,生成一个动态的视图。
作用域模型的改变会反映到视图上,视图的操作会反映到底作用域模型( 中间通过链接函数得以实现 )
4. angular 的视图 ( 动态的 )

5. angular 核心
启动程序
+ 执行期
+ 作用域
+ 控制器 ( 应用的行为 )
+ 模型 ( 应用的数据 )
+ 视图
+ 指令
+ 过滤器
+ 注入器
+ 模块
+ 命名空间

1. 启动程序

** 启动阶段主要工作是建立指令关联关系和渲染DOM **
- 浏览器解析HTML,然后将其解析成为 DOM
- 浏览器载入 angularJS
- angular 等待 DOMContentLoaded event 事件触发
- angular 找到 ng-app 指令,作为应用程序的边界或者根作用域
- 使用 ng-app 中的模块来逐个配置注入器( $injector )
- 注入器 ( $injector ) 是用于创建 “编译服务($compile service)” 和 “根作用域( $rootScope )”。
- 编译服务的作用: 首先将 DOM 和 根作用域进行链接
- 编译服务将指令( ng-model ... ng-init...等 ) 和 作用域的变量进行一一关联。
- 通过变量替换,将构件好的视图展现在页面上
注意上面 编译服务的作用:两个阶段:
编译阶段
和链接阶段
** 注意点: **
ng-app 作为根应用指令,首先将注入器配置在根模块上面。( 这一步与 DOM 无关 )
$injector 创建了 $compile 和 $rootScope
$compile 将得到的所有的根 DOM 和 $rootScope 进行关联
2. 执行时期 ( 主要是事件回调,响应操作等触发执行 )

** 执行时期主要工作内容是 事件要被正确的执行和渲染 **
-
关于执行时期的重点概念
- 只有在angular 的执行的上下文环境中才能享受到angular 提供的各种数据绑定,异常处理,资源管理和服务等等。eg: 使用 angular 的 $setTimeOut 完成延时后可以自动更新页面视图
- 可以使用 $apply() 来从普通的JavaScript 进入 angularJs的上下文环境。只有在使用自定义的事件或者使用第三方类库时,才需要执行 $apply。
-
执行时期的流程:
-
通过调用 scope.$apply( fn ) 进入angular 的上下文环境。fn 为需要在上下文中执行的函数
-
angular 执行 fn, 此函数改变应用的状态
-
angular 进入 $digest 循环,$digest 由两个小循环组成(
$evalAsync 队列和$watch列表,如上图
), 该循环一直迭代,直到模型稳定.一个大循环由两个小循环构成。 模型稳定的标志是:$evalAsync 队列为空,$watch 列表中再无任何改变。
-
$evalAsync 通常用于管理
视图渲染前需要在当前框架外面执行的操作
-
$watch是个表达式的集合,若检测到有更改,$watch 函数就会调用,将新的值更新到 DOM 中
-
一旦 angular 的 $digest 结束循环,整个执行就会离开 angular 和 JavaScript 的上下文环境,
-
最后一步,浏览器更新界面视图重新渲染。
-
3. 作用域

将模型整理好传递给视图,将浏览器的动作和事件传递给控制器
1. 作为中介存在 ( 链接数据模型和数据视图 )
2. 作用域拥有层级结构,此层级结构和 DOM 的层级结构相互对应
3. 每一个作用域都有独立的上下文环境
作用域的特点:
1. 作用域提供 API ( $watch 来观察模型的变化 )
2. 作用域提供 API ( $apply ) 将任何模型的改变从 angular 领域 通过系统映射到视图上
3. 作用域通过共享模型成员的方法嵌套到应用组件上面,一个作用域从父作用域继承属性
4. 作用域提供表达式执行的上下文环境
控制器和视图都持有对作用域的引用,但是控制器和视图之间没有任何关系。
作用域的事件传递:
作用域的事件传递和 DOM 的事件传递类似,事件可以广播给子作用域,也可以传递给父作用域。
作用域的声明周期
1. 创建: 根作用域在应用被 $injector 启动的时候被创建,在模板链接阶段,有些执行会自动创建新的作用域 ( eg:ng-repeat )
2. 观察者注册:模板链接阶段,指令会在作用域上注册观察者,观察者用于将 DOM 的改变传递给 DOM
3. 模型改版: 只有在 scope.$apply() 中变化的数据才能被准确反映到模型上
angular 本身的 API 会自动应用 apply,eg: $http $timeout 不需要额外的 $apply
4. 变化的观测:在 $apply 的最后,angular 会在根作用域执行一个 $digest 循环,将所有的变化传递给子作用域,只要在 $digest 循环中的所有表达式和函数都会被检测,用于观察模型的变化。
模型的变化检测机制就是 angular 的 脏检查机制
4. 控制器
控制器用于构造视图的控制代码,主要作用就是构造数据模型。
控制器的特点 ( 控制器应该和视图做到分离 )
1. 控制器是由 JavaScript 书写,控制器不应该包含任何 HTML 代码。
2. 视图使用 HTML 书写的,视图不应该包含任何 JavaScript 代码。
3. 控制器和视图没有直接的关系。所以可以使用一个控制器对应多个视图。
控制器的三个作用( 书写,分清 c 和 v 的区别 )
1. 在应用中设置模型的初始状态,
2. 将整理好的模型和函数交给作用域( scope )
3. 监听模型的变化并响应事件或者动作( 事件响应函数 )
5. 模型
模型就是和模板结合生成视图
模板就是单纯的 HTML 代码,
模型的特点:
模型必须使用作用域来引用
模型可以是任何 JavaScript 类型的数据

说明:
控制器和 视图没有直接的关系
mvc 的核心是由作用域承担起来的
一个控制器可以对应多个模型
6. angular 的 watch, apply, digest
-
$watch
$watch 队列是在 UI 上使用了一个指令时,就自动生成一条 $watch,所有的 $watch 组合成为一个 $watch 列表,
$watch 列表是在编译的时候就生成完毕
<ul> <li ng-repeat="person in people"> {{person.name}} - {{person.age}} </li> </ul>
对于上述 ng-repeat 若有 10 个 people 会生成 10 * 2 +1 个 $watch
*** $watch 参数详解 ***
-
使用 $watch 函数可以进行 自定义的 操作监听,更改 视图
$scope.$watch('name', function(newValue, oldValue) { if (newValue === oldValue) { return; } // AKA first run $scope.updated++; }); $watch 的 第二个参数是一个 函数,用于 监听 前面的变量是否更改。 $scope.$watch('user', function(newValue, oldValue) { if (newValue === oldValue) { return; } $scope.updated++; }, true); $watch 的 第三个参数是 boolear 类型的值, ** 作用:** ** newValue 和 oldValue 默认是比较 新旧值的引用,若 user 是一个对象,则无法判断对象内部值的改变,需要使用第三个参数来进行判断对象内部深层次的值是否改变。**
-
$digest
$digest 循环过程 会 包含两个小循环,$evalAsync 和 $watch 队列循环。
$digest 会涉及到脏检查机制,反复询问 $watch 队列是否有数据改变
{{ name }} <button ng-click="changeFoo()">Change the name</button> // 这里有 一个 $watch( name 会生成 $watch , 而 ng-click 不会生成 $watch, 因为函数 是不会变的。
- 我们按下按钮
- 浏览器接收到一个事件,进入angular context(后面会解释为什么)。
- $digest循环开始执行,查询每个$watch是否变化。
- 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。
- 新的$digest循环没有检测到变化。
- 浏览器拿回控制权,更新与$scope.name新值相应部分的DOM
-
$apply 用于进入 angular 的 上下文环境, 只是一个angular 提供的一个 api 没有 循环等
$apply 会自动触发 $digest 循环
angular 自己的事件动作会自动触发 $apply
非 angular 环境需要手动触发 $apply
有时自己添加了额外的 操作或动作,需要手动 $apply() 执行 $digest 循环( 典型:访问服务器以后的回调操作 )element.bind('click', function() { scope.foo++; scope.bar++; scope.$apply(); // 手动触发,进行一次 $digest 循环 }); // 第二种 element.bind('click', function() { // scope.$apply(function() { 使用 $apply 函数进行 $digest 循环 scope.foo++; scope.bar++; }); })
-
angular 执行时期 再解释 ( 主要是 $apply $digest $watch 这三个方法的流程 )

1. 页面触发 DOM 事件,回调等。
2. 应用程序自动调用 $apply() 方法进入 angular 上下文,触发 $digest 循环开始,
3. $digest 循环 遍历 $watch 列表集合,脏检查 数据模型的改变,循环过程
4. 检查完毕,更新 数据模型,( 更改变量或者其他动作操作 )
5. 作用域根据数据模型渲染 UI视图。
6. 继续监听。
7. angular 的 通讯
angular 模块之间的通讯方式
1. 作用域传递( 父子模块通讯 )$parent $child
2. 作用域数据传递 + $watch ( 类似于指令的数据传递的 = )
3. 事件广播 ( $emit $on $boardcast )
-
使用 $rootscope
将 $rootscope 作为依赖注入项,在子组件中使用 $rootscope
使用场景: 对于一处改变,需要多处通知更改的变量,频繁的调用可以使用 $rootscope(对于登录使用的用户名称,在一个地方使用,需要多处显示,并且更改后需要多处更改
)myAppModule.controller('myCtrl', function($scope, $rootScope) {
$scope.change = function() {
$scope.test = new Date();
};$scope.getOrig = function() { return $rootScope.test; };
})
-
作用域继承 的 模块通讯
作用域继承是在子模块中可以直接使用父模块方法/变量的 通讯方式
** 只适合数据从 父模块传递到子模块 **** 在指令的数据传递中 通常使用这种模式进行通讯 **
<div ng-controller="Parent"> <div ng-controller="Child"> <div ng-controller="ChildOfChild"> <button ng-click="someParentFunctionInScope()">Do</button> </div> </div> </div>
特点:
- 只适合数据从 父模块传递到子模块
- 子模块的同名方法会覆盖父模块的 方法/变量
- 不能进行同级组件之间的数据传递。除非显示更改 $rootscope
- 层级较多时 维护比较麻烦
-
作用域通讯 + $watch
作用域的数据只能从 父作用域传递到子作用域,** 使用 $watch() 可以监控子作用于数据的改变,类似于 指令数据传递的 = ( 等于号 ) **
.controller("Parent", function($scope){ $scope.VM = {a: "a", b: "b"}; $scope.$watch("VM.a", function(newVal, oldVal){ // code }); }) // 需要用到 $parent 等 .controller('Child', function($scope){ $scope.$parent.$watch('$scope.VM.a', function() { ..... }) })
-
消息机制
scope 提供了冒泡和隧道机制,$on, $emit, $boardcast$boardcast 将事件广播给所有的子组件, $on 用于注册事件函数, $emit 用于事件向上冒泡传递
优缺点: 相比于 $emit,$boardcast 需要向所有的子组件广播组件的改变,会消耗更多的资源
** 所以 在应用的通讯数据达到很大体量 **
$rootscope $scope + watch 都可以成为设计的基本手法
-
使用 Service 进行通讯
因为 angular 中所有的 Service 都是单例的,使用 Service 能够比 时间隧道机制在逻辑上更加清晰。-
简单抽取基本的数据
var myApp = angular.module('myApp', []); myApp.factory('Data', function() { return { message: "I'm data from a service" } }) function FirstCtrl($scope, Data) { $scope.data = Data; } function SecondCtrl($scope, Data) { $scope.data = Data; }
-
进阶版一: 使用 $watch 来监测数据变化
angular.module('Store', []) // 提供基本数据模型初始数据的 Service .factory('Products', function() { return { query: function() { return [{ name: 'Lightsaber', price: 1299.99}, { name: 'Jet Pack', price: 9999.99}, { name: 'Speeder', price: 24999.99}]; } }; }) // 提供数据模型的 Service .factory('Order', function() { var add = function(item, qty) { item.qty = qty; this.items.push(item); }; var remove = function(item) { if (this.items.indexOf(item) > -1) { this.items.splice(this.items.indexOf(item), 1); } }; var total = function() { return this.items.reduce(function(memo, item) { return memo + (item.qty * item.price); }, 0); }; return { // 返回完整的数据模型 items: [], addToOrder: add, removeFromOrder: remove, totalPrice: total }; }).controller('OrderCtrl', function(Products, Order) { this.products = Products.query(); this.items = Order.items; this.addToOrder = function(item) { Order.addToOrder(item, 1); }; this.removeFromOrder = function(item) { Order.removeFromOrder(item); }; this.totalPrice = function() { return Order.total(); }; }).controller('CartCtrl', function($scope, Order) { $scope.items = Order.items; $scope.$watchCollection('items', function() { $scope.totalPrice = Order.totalPrice().toFixed(2); }.bind(this)); }); <nav> // 整个页面只有这里需要根据数据模型的变化而 改变视图 UI <div ng-controller="CartCtrl">Total Price: {{totalPrice}} </div> </nav> <div ng-controller="OrderCtrl as order"> <ul> <li ng-repeat="product in order.products"> <h3>{{product.name}} - {{product.price}}</h3> <button ng-click="order.addToOrder(product)">Add</button> <button ng-click="order.removeFromOrder(product)">Remove</button> </li> </ul> </div>
-
var myModule = angular.module('myModule', []); myModule.factory('mySharedService', function($rootScope) { var sharedService = {}; sharedService.message = ''; sharedService.prepForBroadcast = function(msg) { this.message = msg; this.broadcastItem(); // 执行 广播 }; sharedService.broadcastItem = function() { $rootScope.$broadcast('handleBroadcast'); }; return sharedService; }); function ControllerZero($scope, sharedService) { $scope.handleClick = function(msg) { sharedService.prepForBroadcast(msg); // 调用包含广播的函数 }; $scope.$on('handleBroadcast', function() { $scope.message = sharedService.message; }); } function ControllerOne($scope, sharedService) { $scope.$on('handleBroadcast', function() { $scope.message = 'ONE: ' + sharedService.message; }); } function ControllerTwo($scope, sharedService) { $scope.$on('handleBroadcast', function() { $scope.message = 'TWO: ' + sharedService.message; }); } ControllerZero.$inject = ['$scope', 'mySharedService']; ControllerOne.$inject = ['$scope', 'mySharedService']; ControllerTwo.$inject = ['$scope', 'mySharedService'];
-
.
网友评论