初识Angular,理解它的基本设计原理可以更好的把握Angular。看了慕课网大漠穷秋老师的视频,总结一下。下面的源码也来自慕课网,可以下载到。
1 Angular的四大特点
MVC、模块化、双向数据绑定、指令系统。
2 MVC
2.1 什么是MVC,为什么要用MVC?
MVC:模型、视图、控制器,互联网的三层架构:视图负责客户端与用户交互,控制器负责中间业务逻辑的处理,互通了视图和数据,模型用语对数据的增删改查。
在大型项目中,需要多人合作开发,而每个人只是负责其中一个功能快,MVC划分了视图、控制和模型,让指责更加清晰,也提高了复用性,同一个视图可以调用不同的模型实现样式相同内容不一的页面(eg:不同班级的课程表),不同的视图也可以调用同一个模型实现同一份数据的不同展示(eg:用三点图/折线图/雷达图... 展示同一份报告)。功能划分明确,也便于维护。
以上,NVC是为了模块化和复用,MVC只是手段,目标比手段更重要。
2.2 前端实现MVC有哪些困难
实现MVC思路是很清晰的,控制器就是大脑,负责把模型中的数据交给视图来展现,交互过程中又通过大脑来操作数据,改变视图。
然而在前端,有一些情况需要去考虑:
对DOM的操作,必须保证是在DOM加载完成之后执行,如何保证,MVC并未给出解决方案。
资源文件之间经常会有依赖关系,如何确保依赖关系的正确性,需要程序员自己解决。
js的原型继承也给前端编程带来很多困难
2.3 Angular如何实现MVC
2.3.1 在Angular中,M、V、C分别是什么?
<!doctype html>
<html ng-app>
<head>
<meta charset="utf-8">
</head>
<body>
<div ng-controller="HelloAngular">
<p>{{greeting.text}},Angular</p>
</div>
</body>
<script src="js/angular-1.3.0.js"></script>
<script src="HelloAngular_MVC.js"></script>
</html>
js/angular-1.3.0.js 是Angular的源文件
HelloAngular_MVC.js:
function HelloAngular($scope) {
$scope.greeting = {
text: 'Hello'
};
}
视图很好理解,ng-controller="HelloAngular"
指定了控制器,{{greeting.text}}
指定了模型,在js代码中可以了解控制器是如何控制模型的。$scope指明了作用域,这里的作用域是div。
2.3.2 Controller的使用误区
1.不要复用controller,需要复用的地方又service实现,不良写法:
<div ng-controller="CommonController">
<div ng-controller="Controller1">
<p>{{greeting.text}},Angular</p>
<button ng-click="test1()">test1</button>
</div>
<div ng-controller="Controller2">
<p>{{greeting.text}},Angular</p>
<button ng-click="test2()">test2</button>
<button ng-click="commonFn()">通用</button>
</div>
</div>
Controller:
function CommonController($scope){
$scope.commonFn=function(){
alert("这里是通用功能!");
};
}
function Controller1($scope) {
$scope.greeting = {
text: 'Hello1'
};
$scope.test1=function(){
alert("test1");
};
}
function Controller2($scope) {
$scope.greeting = {
text: 'Hello2'
};
$scope.test2=function(){
alert("test2");
}
}
2.不要用controller操作DOM,因为controller操作DOM的代价很昂贵,需要重绘或者重新布局,angular中通过指令操作DOM。
3.数据的格式化操作由表单控件完成,不要用controler做数据格式化操作。
4.$filter服务可以做数据过滤,不需要用controller做数据过滤。
5.不要互相调用controller,而是通过事件来调用。
2.3.3 如何复用Model
跑一下下面的代码吧:
<!doctype html>
<html ng-app>
<head>
<meta charset="utf-8">
</head>
<body>
<div>
<input ng-model="greeting.text"/>
<p>{{greeting.text}},Angular</p>
</div>
</body>
<script src="js/angular-1.3.0.js"></script>
</html>
p元素内容会随文本框内容实时动态变更。
在加载完angular文件后,angular 通过ng-app
确定了自己的生效范围。ng-model="greeting.text"
指定了模型,文本框的数据便是这个模型的内容,模型的作用域被绑定到了根作用域上。
和控制器的制定范围不同,在控制器的div外面调用控制器时,是无效的,但是在div外面在调用模型时是生效的,被绑定道根作用域上全局都可访问了。
2.3.4 如何复用View
angular可以自己创建html标签,可以自己制作标签了,好开心,制作一次使用n次,便可以实现view的复用:
我想有一个hello
标签
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
</head>
<body>
<hello></hello>
</body>
<script src="js/angular-1.3.0.js"></script>
<script src="HelloAngular_Directive.js"></script>
</html>
HelloAngular_Directive.js:
var myModule = angular.module("MyModule", []);
myModule.directive("hello", function() {
return {
restrict: 'E',
template: '<div>Hi everyone!</div>',
replace: true
}
});
通过指令便可满足!指令在指令一节会细讲。
2.3.5 angular中的作用域$scope
在控制器和模型中我们都使用了作用域,$rootscope是根作用域,由ng-app
指定,同DOM树相似,作用域也是一个树形结构,本级找不到将会向上层查找,感受一下$scope吧,这里我们通过$scope传播事件:
<!doctype html>
<html ng-app>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="Scope1.css" />
</head>
<body>
<div ng-controller="EventController">
Root scope
<tt>MyEvent</tt> count: {{count}}
<ul>
<li ng-repeat="i in [1]" ng-controller="EventController">
<button ng-click="$emit('MyEvent')">
$emit('MyEvent')
</button>
<button ng-click="$broadcast('MyEvent')">
$broadcast('MyEvent')
</button>
<br>
Middle scope
<tt>MyEvent</tt> count: {{count}}
<ul>
<li ng-repeat="item in [1, 2,3]" ng-controller="EventController">
Leaf scope
<tt>MyEvent</tt> count: {{count}}
</li>
</ul>
</li>
</ul>
</div>
</body>
<script src="js/angular-1.3.0.js"></script>
<script src="Scope2.js"></script>
</html>
Scope2.js:
function EventController($scope) {
$scope.count = 0;
$scope.$on('MyEvent', function() {
$scope.count++;
});
}
$emit('MyEvent')可以控制控制器中事件控制本级和上级模型,
$broadcast('MyEvent')可以控制控制器中事件控制本级和下级模型,
说明:ng-repeat
表明了元素中内容重复的次数。
angular.element($0).scope()可以对$scope进行调试。
$scope的生命周期:创建-》注册监控-》检测模型变化-》观察模型是否脏-》销毁
3 模块化
3.1 没有模块化的项目组织
思考一下,我们现在会如何开发一个项目呢?一个项目会有多个文件,这些文件又该如何组织呢,一种方法是:
js文件放入scripts文件夹下,
css文件放在styles文件夹下,
img文件放在images文件夹下,
资源包放在node_modules类似的文件夹下,
嗯,似乎看起看很工整,但是想一下,这样是否存在问题呢?
实现逻辑时,函数变量会挂在全局,这样合理吗?
这么多的功能,是否需要功能划分呢?
每个控制器都要单独存入一个文件嘛?这样也太多了吧
把多个控制器写在同一个文件,多人开发的时候同时处理一个文件很容易产生冲突的
文件之间如果存在依赖关系,该如何解决呢?比如在开发多页应用时,要用到路由机制,路由需要调用控制器和视图,如何保证路由执行的时候,控制器和视图及其依赖的服务加载完成了呢?
这里需要借助模块化来解决问题。
3.2 有模块化的项目组织
3.2.1 何为模块,
模块就是实现一定功能的程序集合,这个集合中包括控制、视图、服务、过滤等。
3.2.2 angular中的模块实现
<!doctype html>
<html ng-app="HelloAngular">
<head>
<meta charset="utf-8">
</head>
<body>
<div ng-controller="helloNgCtrl">
<p>{{greeting.text}},Angular</p>
</div>
</body>
<script src="js/angular-1.3.0.js"></script>
<script src="NgModule1.js"></script>
</html>
NgModule1.js:
var helloModule=angular.module('HelloAngular', []);
helloModule.controller('helloNgCtrl', ['$scope', function($scope){
$scope.greeting = {
text: 'Hello'
};
}]);
3.2.3 模块化优势
这样,便解决了1.变量污染全局的问题,并且可以2.按照功能划分模块。
3.4.当同一个文件加载多个控制器时,可以通过前端强大的自动化工具grunt来实现,助力了模块化的开发。
5.通过依赖注入可以解决资源依赖的问题。
3.3.4 模块化实现
首先先了解一个项目的目录,再具体讲解如何实现依赖注入。以BookStore 为例
说明:
framework存放了资源文件
tpls:templates,存放模版文件,主要用于视图的管理
index.htm为入口文件
跑一下应用:
来解析一下文件:
首先看入口文件 index.html:
<!doctype html>
<html ng-app="bookStoreApp">
<head>
<meta charset="UTF-8">
<title>BookStore</title>
<script src="framework/1.3.0.14/angular.js"></script>
<script src="framework/1.3.0.14/angular-route.js"></script>
<script src="framework/1.3.0.14/angular-animate.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
<script src="js/services.js"></script>
<script src="js/directives.js"></script>
</head>
<body>
<div ng-view>
</div>
</body>
</html>
主要是对一些资源文件的加载,这里通过路由控制加载视图 app.js:
var bookStoreApp = angular.module('bookStoreApp', [
'ngRoute', 'ngAnimate', 'bookStoreCtrls', 'bookStoreFilters',
'bookStoreServices', 'bookStoreDirectives'
]);
bookStoreApp.config(function($routeProvider) {
$routeProvider.when('/hello', {
templateUrl: 'tpls/hello.html',
controller: 'HelloCtrl'
}).when('/list',{
templateUrl:'tpls/bookList.html',
controller:'BookListCtrl'
}).otherwise({
redirectTo: '/hello'
})
});
bookStoreApp后面[]中指明了bookStoreApp依赖的模块,在加载时保证了依赖模块加载完之后才运行本模块,下面的配置项说明了哪个url链接应该对应哪个控制器管理的哪个视图,主要when方法和otherwise方法,otherwise方法说明未指明路由是默认定向到/hello路由。
看一下这个控制器是怎样的 controllers.js:
var bookStoreCtrls = angular.module('bookStoreCtrls', []);
bookStoreCtrls.controller('HelloCtrl', ['$scope',
function($scope) {
$scope.greeting = {
text: 'Hello'
};
}
]);
bookStoreCtrls.controller('BookListCtrl', ['$scope',
function($scope) {
$scope.books =[
{title:"《Ext江湖》",author:"大漠穷秋"},
{title:"《ActionScript游戏设计基础(第二版)》",author:"大漠穷秋"},
{title:"《用AngularJS开发下一代WEB应用》",author:"大漠穷秋"}
]
}
]);
其它的文件只是给了框架,为了给出一个项目的架构。
directives.js:
var bookStoreDirectives = angular.module('bookStoreDirectives', []);
bookStoreDirectives.directive('bookStoreDirective_1', ['$scope',
function($scope) {}
]);
bookStoreDirectives.directive('bookStoreDirective_2', ['$scope',
function($scope) {}
]);
filters.js:
var bookStoreFilters = angular.module('bookStoreFilters', []);
bookStoreFilters.filter('bookStoreFilter_1', ['$scope',
function($scope) {}
]);
bookStoreFilters.filter('bookStoreFilter_2', ['$scope',
function($scope) {}
]);
services.js:
var bookStoreServices = angular.module('bookStoreServices', []);
bookStoreServices.service('bookStoreService_1', ['$scope',
function($scope) {}
]);
bookStoreServices.service('bookStoreService_2', ['$scope',
function($scope) {}
]);
至此,应该了解了一个简单项目的架构和依赖管理。
4 双向数据绑定
4.1 什么是双向数据绑定
终于讲到双向数据绑定了,这个神奇的东西到底是什么?
就是视图到模型,模型到视图的双向绑定,模型和视图达成了同步。
4.2 双向数据绑定如何实现
4.2.1 从$scope -> view的单向绑定
MVC通常的思路是,将模型的数据展示在视图上,那么。如何实现从模型到视图的绑定呢?我们似乎实现过从模型到视图的绑定。
<!doctype html>
<html ng-app>
<head>
<meta charset="utf-8">
</head>
<body>
<div ng-controller="HelloAngular">
<p><span {{greeting.text}}></span>,Angular</p>
</div>
</body>
<script src="js/angular-1.3.0.js"></script>
<script src="HelloAngular_MVC.js"></script>
</html>
HelloAngular_MVC.js:
function HelloAngular($scope) {
$scope.greeting = {
text: 'Hello'
};
}
如果使用{{greeting.text}}
,在AngularJS使用数据替换模板中的花括号,其未被渲染的模板可能会被用户看到。狂刷屏幕,视图上会暂态出现{{greeting.text}},Angular
。
还有另一种实现方式:ng-bind="greeting.text"
。数据加载完成之前用户就不会看到任何内容。
模型到视图不仅可以控制内容,还可以控制样式:
<!doctype html>
<html ng-app="MyCSSModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="NgClass.css">
</head>
<body>
<div ng-controller='HeaderController'>
<div ng-class='{error: isError, warning: isWarning}'>{{messageText}}</div>
<button ng-click='showError()'>Simulate Error</button>
<button ng-click='showWarning()'>Simulate Warning</button>
</div>
</body>
<script src="js/angular-1.3.0.js"></script>
<script src="NgClass.js"></script>
</html>
NgClass.css:
.error {
background-color: red;
}
.warning {
background-color: yellow;
}
NgClass.js:
var myCSSModule = angular.module('MyCSSModule', []);
myCSSModule.controller('HeaderController', ['$scope',
function($scope) {
$scope.isError = false;
$scope.isWarning = false;
$scope.showError = function() {
$scope.messageText = 'This is an error!';
$scope.isError = true;
$scope.isWarning = false;
};
$scope.showWarning = function() {
$scope.messageText = 'Just a warning. Please carry on.';
$scope.isWarning = true;
$scope.isError = false;
};
}
])
ng-class
实现了从模型到样式数据的控制,ng-class='{error: isError, warning: isWarning}
,isError
为true,则加入error类。
还有一些其它的控制,有需求时可以查阅官网。请输入代码
4.2.2 从$scope <-> view的双向绑定
给一个实例,来实现双向数据绑定
我们希望,视图中表单的内容的改变可以影响模型,通过按钮控制的模型的改变可以影响到视图中表单的内容。
借助bootstrap实现了以上的布局:
<!doctype html>
<html ng-app="UserInfoModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
<script src="js/angular-1.3.0.js"></script>
<script src="Form.js"></script>
</head>
<body>
<div class="panel panel-primary">
<div class="panel-heading">
<div class="panel-title">双向数据绑定</div>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<form class="form-horizontal" role="form" ng-controller="UserInfoCtrl">
<div class="form-group">
<label class="col-md-2 control-label">
邮箱:
</label>
<div class="col-md-10">
<input type="email" class="form-control" placeholder="推荐使用126邮箱" ng-model="userInfo.email">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">
密码:
</label>
<div class="col-md-10">
<input type="password" class="form-control" placeholder="只能是数字、字母、下划线" ng-model="userInfo.password">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="userInfo.autoLogin">自动登录
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-default" ng-click="getFormData()">获取Form表单的值</button>
<button class="btn btn-default" ng-click="setFormData()">设置Form表单的值</button>
<button class="btn btn-default" ng-click="resetForm()">重置表单</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
Form.js:
var userInfoModule = angular.module('UserInfoModule', []);
userInfoModule.controller('UserInfoCtrl', ['$scope',
function($scope) {
$scope.userInfo = {
email: "253445528@qq.com",
password: "253445528",
autoLogin: true
};
$scope.getFormData = function() {
console.log($scope.userInfo);
};
$scope.setFormData = function() {
$scope.userInfo = {
email: 'damoqiongqiu@126.com',
password: 'damoqiongqiu',
autoLogin: false
}
};
$scope.resetForm = function() {
$scope.userInfo = {
email: "253445528@qq.com",
password: "253445528",
autoLogin: true
};
}
}
])
通过指令ng-model
实现了从$scope <-> view的双向绑定。
4.2.3 扩展路由
之前讲过用app.js实现页面的控制,但是如果一个页面内部需要局部更新,切换路由,再用$routeProvider
实现会很不合适, ui-router提供了页内的路由嵌套。
咦?为什么要用页内嵌套路由呢?需要什么信息,使用ajax加载局部更新不就可以了嘛?ajax在加载后页面的url是不会改变的,也就是在加载前和加载后是同一个url,那么我们也就无法定位到ajax加载后的页面,当想把加载后的页面加入书签或者推荐给好友时是无法获得浏览记录的。
5 指令
5.1 指令配置项
以hello
指令为例:
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
</head>
<body>
<hello></hello>
<div hello></div>
<div class="hello"></div>
<!-- directive:hello -->
<div></div>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="HelloAngular_Directive.js"></script>
</html>
HelloAngular_Directive.js:
var myModule = angular.module("MyModule", []);
myModule.directive("hello", function() {
return {
restrict: 'AEMC',
template: '<div>Hi everyone!</div>',
replace: true
}
});
5.1.1 匹配模式
restrict说明了匹配模式:
<hello></hello>
<div hello></div>
<div class="hello"></div>
<!-- directive:hello -->
<div></div>
四个hello
分别是元素、属性、样式类和注释的匹配模式。
5.1.2 模版
template模版指定了指令的内容。template: '<div>Hi everyone!</div>'
直接指定了模版的内容,如果模版内容很多饿时候在js里面写标签是很痛苦的。
`templateUrl: 'hello.html'可以通过指定url来指定一个文件为模版,
还可以通过缓存的方式存储复用模版:
var myModule = angular.module("MyModule", []);
//注射器加载完所有模块时,此方法执行一次
myModule.run(function($templateCache){
$templateCache.put("hello.html","<div>Hello everyone!!!!!!</div>");
});
myModule.directive("hello", function($templateCache) {
return {
restrict: 'AECM',
template: $templateCache.get("hello.html"),
replace: true
}
});
5.1.3 替换
当replace
为true时,
如果hello
中嵌套了内容,hello中的内容会被替换掉,不显示:
<hello>
<div>这里是指令内部的内容。</div>
</hello>
如果不想被替换,可以使用transclude:true
:
var myModule = angular.module("MyModule", []);
myModule.directive("hello", function() {
return {
restrict:"AE",
transclude:true,
template:"<div>Hello everyone!<div ng-transclude></div></div>"
}
});
当指令想要嵌套指令时,这种方式可以不让被嵌套的指令被替代掉。
以上大概讲解了一下指令,还有很多指令的内容,在基础入门这里就先介绍这么多。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。