编辑推荐: |
本文来自简书,什么是
Angular,我们为什么要学习它啊?那么今天就让我们认识一下什么是Angular,Angular是一种用于创建单一应用程序界面的前端框架,它有许多核心功能例如数据绑定,服务,指令以及依赖注入等等 |
|
一.angular 基本概念
1.类库( 提供类方法 ) 和框架
类库提供一系列的函数和方法的合集,能够加快你写代码的速度。但是主导逻辑的还是自己的代码。常用的类库
eg: jquery
框架 特殊的已经实现了 web 的应用。只需要按照其逻辑填充你的业务逻辑就能得到完整的应用
2.angular 的特点
提供端对端的解决方案
构建一个 CRUD(add retrieve update delete) 应用的全部内容:`数据绑定,表单验证,路由,深度链接,组件重用,依赖注入`
测试方案: `单元测试, 端对端测试,模拟和自动化测试`
具有各种种子应用作为模板和起点
特点
angular 主要考虑构建 CRUD 应用,并不是所有的应用都适合使用 angular 来构建
例如游戏,图形编辑界面就不适合使用 angular
angular 的标榜概念
angular 认为声明式的代码比命令式的代码更加符合 构建 (视图
+ 软件)逻辑的代码
声明式的语言 :提前将所有的操作内置,使用时只需要按照规定声明该操作,语言或者机器本身可以进行构建应用
声明式的语言介绍:HTML 就是声明式的结构,比如需要某个元素居中,不需要告诉浏览器具体的行为(需要找到元素的中间位置,将元素放在那里),只需要添加一个
align='center' 的属性给新元素的可以了。这就是声明式的语言
声明式的语言也有不好的地方,就是所有可以使用的操作已经提前内置,所以他不能识别新的语法结构,比如你想让元素居左
1/3 处就很难处理将 DOM 操作和应用逻辑解耦
将测试和开发同等看待
大幅度减少应用中需要使用的各种 回调的逻辑,摆脱大量的回调逻辑
解放DOM 操作,
对页面的UI操作可控,例如大量的DOM事件
angular 已经有了许多搭建好的基础服务框架
4.angular 的初始化信息
angular 会在 DOMContentLoaded 事件触发时执行, 通过 ng-app 指令
寻找你的应用的根作用域
1. 首先载入和指令相关的模块
2. 穿件应用的 注入器(injector)
3. 将 ng-app 作为根节点编译 DOM 。
也可以使用 angular.bootstrap( 节点 ) 来手动装载节点
二. angular 的指令
指令的定义:由一个新的属性,元素名称,css类名等带来DOM 样式或者行为的改变。
指令( angular 的行为扩展 ):HTML 编译器,能够识别新的 HTML 语法,可以将行为动作关联到HTML或者其属性上面,设置可以创造自定义行为的元素,可复用。
注意指令是在最开始的时候被载入页面的
指令本质上就是一个代用功能的函数 ** return 一个函数 **,类比于 react 的自定义组件
** angular API 有几个大的的分类 **
ng.function ( 功能函数,类比于jquery 的方法函数 )
** ng.directive( angular 的重大模块,eg: ng-model 等 ) **
** ng.provider ( 依赖注入功能 )**
.......
三. angular 的 编译器( compiler )
编译器通过遍历 DOM 来查找和关联属性, 其分为 编译 和 链接 两个阶段
编译:遍历所有的 DOM 收集指令,生成一个 链接函数集合
链接:将指令和作用域绑定,生成一个动态的视图。
作用域模型的改变会反映到视图上,视图的操作会反映到底作用域模型(
中间通过链接函数得以实现 )
四. angular 的视图 ( 动态的 )
五. angular 核心
启动程序 + 执行期 + 作用域 + 控制器 ( 应用的行为 ) + 模型 ( 应用的数据 ) +
视图 + 指令 + 过滤器 + 注入器 + 模块 + 命名空间
angular 执行流程.png
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. 执行时期 ( 主要是事件回调,响应操作等触发执行 )
concepts-runtime.png
** 执行时期主要工作内容是 事件要被正确的执行和渲染 **关于执行时期的重点概念
只有在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. 作用域
mvc.png
将模型整理好传递给视图,将浏览器的动作和事件传递给控制器
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 循环中的所有表达式和函数都会被检测,用于观察模型的变化。 |
4. 控制器
控制器用于构造视图的控制代码,主要作用就是构造数据模型。
控制器的特点 ( 控制器应该和视图做到分离 )
1. 控制器是由 JavaScript
书写,控制器不应该包含任何 HTML 代码。
2. 视图使用 HTML 书写的,视图不应该包含任何 JavaScript 代码。
3. 控制器和视图没有直接的关系。所以可以使用一个控制器对应多个视图。 |
控制器的三个作用( 书写,分清 c 和 v 的区别 )
1. 在应用中设置模型的初始状态,
2. 将整理好的模型和函数交给作用域( scope )
3. 监听模型的变化并响应事件或者动作( 事件响应函数 ) |
5. 模型
模型就是和模板结合生成视图
模板就是单纯的 HTML 代码,
模型的特点:
模型必须使用作用域来引用
模型可以是任何 JavaScript 类型的数据 |
mvc.png
说明:
控制器和 视图没有直接的关系
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
1.*** $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 是一个对象,则无法判断对象内部值的改变,需要使用第三个参数来进行判断对象内部深层次的值是否改变。** |
2.$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
3.$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++;
});
}) |
4.angular 执行时期 再解释 ( 主要是 $apply $digest
$watch 这三个方法的流程 )
concepts-runtime.png
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 ) |
1.使用 $rootscope
将 $rootscope 作为依赖注入项,在子组件中使用 $rootscope
使用场景: 对于一处改变,需要多处通知更改的变量,频繁的调用可以使用 $rootscope( 对于登录使用的用户名称,在一个地方使用,需要多处显示,并且更改后需要多处更改
)
myAppModule.controller('myCtrl', function($scope,
$rootScope) {
$scope.change = function() {
$scope.test = new Date();
};
$scope.getOrig
= function() {
return $rootScope.test;
}; |
})
2.作用域继承 的 模块通讯
作用域继承是在子模块中可以直接使用父模块方法/变量的 通讯方式
** 只适合数据从 父模块传递到子模块 **
** 在指令的数据传递中 通常使用这种模式进行通讯 **
<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()
{ ..... })
}) |
4.消息机制
scope 提供了冒泡和隧道机制,$on, $emit, $boardcast
$boardcast 将事件广播给所有的子组件,
$on 用于注册事件函数,
$emit 用于事件向上冒泡传递 |
优缺点: 相比于 $emit,$boardcast 需要向所有的子组件广播组件的改变,会消耗更多的资源
** 所以 在应用的通讯数据达到很大体量
**
$rootscope $scope + watch 都可以成为设计的基本手法 |
5.使用 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> |
angularjs的$watch、$watchGroup、$watchCollection
使用方式 区别
angular 几种数据通讯机制
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']; |
|