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只会加载一份服务。

posted @ 2017-04-24 22:20  水煮海鲜  阅读(124)  评论(0编辑  收藏  举报