AngularJS学习笔记
AngularJS在加载启动时,会做3件事情:
1.依赖注入
2.创建 root scope作为整个模型的上下文
3.从ngApp开始编译DOM,处理后续的指令和绑定
当它启动后,它会等待浏览器的输入事件(鼠标、HTTP请求等),若输入事件改变了model,那么AngularJS会通过更新绑定,将model的改变反应到view上。
AngularJS中指令都是带有短划线(-)的,自定义的属性都是驼峰命名的
1.ng-app:
2.ng-controller:定义了一个叫做PhoneListController的控制器,并且向module(phonecatApp)注册。model模型(手机的数据)在controller中,model由controller实例化,controller函数就是一个简单的构造函数。参数scope在该controller范围内都是有效的。scope是template、model、controller之间的联系纽带(template可以理解为是view中的一部分,包括数据绑定、展示逻辑)。
我们在scope下分配参数,可以在其他页面(使用相同的controller)使用该参数,这样交互就会显得比较灵活。但是这种方法只适用于小型的系统,若在大型系统上,紧耦合就会成为一个问题。
3.ng-repeat:
PS:不要搞错module模块和model模型,MVC里的M是指model。
// Define the `phonecatApp` module
var phonecatApp = angular.module('phonecatApp', []);
// Define the `PhoneListController` controller on the `phonecatApp` module
phonecatApp.controller('PhoneListController', function PhoneListController($scope) {
$scope.phones = [
{
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.'
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.'
}
];
});
component
相对于上述例子,还有一些地方我们可以改进:
1.如果我们要复用template和controller,在上面的例子中,我们需要把他们都复制到复用的地方,这样容易出错。
2.我们把变量都赋值到scope下面,这样会造成混乱。
//组件greetUser向模块myAPP注册
angular.
module('myApp').
component('greetUser', {
template: 'Hello, {{$ctrl.user}}!',
controller: function GreetUserController() {
this.user = 'world';
}
});
我们可以在html中使用标签<greet-user></greet-user>
,然后AngularJS会用template去替换它。$ctrl
在默认情况下,代表着controller。我们在controller的构造函数中用this代替了在scope下赋值。标签<greet-user></greet-user>
可以自任何地方使用,也实现了最大限度的解耦。我们也不用担心component会在其他地方被修改,因为template、controller都在component里面。
避免直接使用scope,这是一条最佳实践。
文件组织的最佳实践
1.一个特性一个文件
不要把controller都放在一个js文件里,不要把component都放在一个js文件里。比如上述的componentgreetUser
,需要放在greetUser.component.js里,那么在这js文件里就不要放其他东西了。
2.按特性组织
按照1中的方法,一个文件放置一个特性,那么在app/
目录下会产生许多文件,我们需要将这些文件按特性组织成文件夹。如果在许多的地方都会用到同一个特性,那么我们就把这个特性文件夹放到aap/core/
里面。
app/
phone-list/
phone-list.component.js
phone-list.component.spec.js(test)
core/
feature1/
xxx.js
xxx.spec.js
app.js
// phone-list.component.js
// Register `phoneList` component to module 'phonecatApp'
angular.
module('phonecatApp').
component('phoneList',...)
// app.js
// Define the `phonecatApp` module
// 模块phonecatApp没有依赖其他模块,只有它本身
angular.module('phonecatApp', []);
3.模块化
如果现在有其他应用要用到phone-list,我们不需要重复发明轮子,只需要把目录phone-list/
复制到新应用下。但是我们发现,原来在phone-list/
中,componentphoneList
是向modulephonecatApp
注册的,在新的应用中,应用名就不叫phonecatApp
了,这时我们需要把phonecatApp
都改为当前的应用名。显然,这样的改动容易出错且重复劳动。我们有更好的方法去做这件事。引用一句最近在《Head first 设计模式》上看到的话,对改动关闭,对扩展开放,我们不要去改动已有代码,这样可能在修改一个bug的同时代入另外一个bug,我们应该是封装、抽象、扩展我们已有的代码。
1.新建一个phone-list.module.js
文件,把component提高到module层次。
2.在新的应用newApp中,添加phonecatApp依赖
app/
phone-list/
phone-list.module.js
phone-list.component.js
phone-list.component.spec.js
core/
feature1/
xxx.js
xxx.spec.js
app.js
//app/phone-list/phone-list.module.js:
// Define the `phonecatApp` module
angular.module('phonecatApp', []);
// phone-list.component.js
// Register `phoneList` component to module 'phonecatApp'
angular.
module('phonecatApp').
component('phoneList',{
template:
'<ul>' +
'<li ng-repeat="phone in $ctrl.phones">' +
'<span>{{phone.name}}</span>' +
'<p>{{phone.snippet}}</p>' +
'</li>' +
'</ul>',
...
})
// app.js
// Define the `newApp` module
angular.module('newApp', [
// ...which depends on the `phonecatApp` module
'phonecatApp'
]);
4.使用外部模板
在3中,component里面的template是内嵌式的html,这样不易于维护和模块化,我们把内嵌式的html改为外部的html。这里有一个点需要注意:当AngularJS解析到templateUrl时,会发起一个HTTP请求,虽然AngularJS使用了延时加载和缓存技术,但随着外部模板变多,HTTP请求也不可避免的变多。详细可以参阅$templateRequest
和$templateCache
这两项服务。
// phone-list.component.js
angular.
module('phoneList').
component('phoneList', {
// Note: The URL is relative to our `index.html` file
templateUrl: 'phone-list/phone-list.template.html',
controller: ...
});
// phone-list.template.html
<ul>
<li ng-repeat="phone in $ctrl.phones">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
//最终的目录
app/
phone-list/
phone-list.component.js
phone-list.component.spec.js
phone-list.module.js
phone-list.template.html
app.css
app.js
index.html
第六节.双向数据绑定
双向数据绑定是指view和model在两个方向上都绑定住了。如果我们在浏览器上改变view,那么就会反应到model里面,进而改变model。如果我们在代码里面改变model,那么就会向外反应到view上。
第七节.依赖注入
使用内置的http服务为例子。
$http
服务会让AngularJS在客户端发起一个http请求,在下面的例子中,http去请求一个json文件,路径是相对于index.html来说的。服务的名字需要作为参数传入到controller的构造函数中。服务的名字很重要,不能写错,注入器就是依靠名字去寻找依赖的。注入器在加载一个服务时,会分析这个服务会依赖其他哪几个服务,然后依次去加载服务。
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: function PhoneListController($http) {
var self = this;//this对象在闭包中会发生变化,在这里将this保存成一个变量,该变量在闭包中的含义不会发生变化
self.orderProp = 'age';
//phone的数据不像之前被写死,而是由http请求去json文件里面读取
$http.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
});
AngularJS内置的服务都是带有$
前缀的,如果我们要定义自己的服务,就不要带$
前缀。而且我们会看到在Scope下面,有些变量是带有$$
前缀的,那说明这个变量是私有的,虽然我们还是可以去访问,但不建议这么做。
JS在发布前都会经过压缩混淆,混淆其中有个步骤就是替换局部变量,如果$http
这个变量被替换了,AngularJS就识别不出来使用了内置的http服务,所以针对混淆,代码要换个结构。有以下两种方法:
1.创建$inject
属性
$inject
后面跟一组字符串,字符串就不会被js混淆替换掉,这些字符串就是要依赖的服务。
function PhoneListController($http) {...}
PhoneListController.$inject = ['$http'];
...
.component('phoneList', {..., controller: PhoneListController});
2.使用内联的注解
在controller后面跟一组字符串。
function PhoneListController($http) {...}
...
.component('phoneList', {..., controller: ['$http', PhoneListController]});
2.1使用内联的注解
当我们使用方法2时,一般都是把构造函数也内联进来。(在刚接触AngularJS时,经常看到这种形式的内联,当时就感觉JS代码一层套一层好复杂,一句话好长,而且对$http
出现2次感到不解)
.component('phoneList', {..., controller: ['$http', function PhoneListController($http) {...}]});
对本例代码使用2.1的注解形式:
//app/phone-list/phone-list.component.js
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: ['$http',
function PhoneListController($http) {
var self = this;
self.orderProp = 'age';
$http.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
]
});
第九节.路由和多视图
依赖注射器做以下3件事:
1.加载定义的module
2.注册所有在module后面定义的Providers
3.当要使用服务了,这些服务和他们的依赖,就会通过他们的Providers被传入注射依赖函数
Providers是一个对象,这个对象可以创建相应的服务。
在url上加前缀!
是一个最佳实践,如urlhttp://127.0.0.1:8000/#!/phones
关于自模块的依赖:
// app/app.js
angular.module('phonecatApp', [
'ngRoute',
'phoneDetail',
'phoneList'
]);
// app/phone-detail/phone-detail-module.js
angular.module('phoneDetail', [
'ngRoute'
]);
顶层模块phonecatApp和子模块phoneDetail都包含ngRoute服务,如果我们把phoneDetail中的ngRoute去掉,程序也是可以工作的,但不建议这么做,因为这样会破坏模块化,子模块不应该依赖由父模块继承下来的服务。假如phoneDetail模块被放到其他地方,这个顶层模块没有ngRoute,那么phoneDetail模块就不能正常工作。我们不用担心多个模块包含了多份一样的服务会造成多余的浪费,AngularJS只会加载一份服务。