• AngularJS学习笔记


    近日通过图灵社区的教程开始学习AngularJS,在此记录下要点。


    准备

    教程中有一些不完善的地方,包括

    • Github上的代码已经更新,主要部分大体相同,但测试部分已经不能像教程中说的那样工作,因此先略过测试部分,待完整了解AngularJS后再做研究;
    • nodeJS开启服务器的方式也和文章所述不同,下面会提到。

    所需要的工具

    • nodeJS
    • git
    • 代码库:git clone git://github.com/angular/angular-phonecat.git

    node配置

    教程中的方法已经失效,应使用如下方法:

    在git中先转到angular-phonecat目录,输入:

    npm install //这一步要一段时间,要耐心等完。强制退出将导致失败,失败后需要删除目录下的node_modules文件夹重来
    npm start

    每个教程之间都需要用到checkout命令,建议另开个git bash,这样就不需要重复开启node服务器了。

    一些说明

    • 一些名词会比较混淆,例如 ngRoute$routeProvider,我的理解是: ngRoute 是模块名,而$routeProvider开头的是服务名。加载模块后才可以使用对应的服务。
    • angular.module(‘newModelName’, [‘relayModelName’,’…’]) 这个语句是声明一个模块,第一个参数是声明的模块名,第二个参数是一个数组,包含了该模块所依赖的模块,可以是空数组。
    • 相关JS文件的引入,只需要保证angular.js放在第一,其他文件的顺序没有关系,例如
    <head>
      <meta charset="utf-8">
      <title>Google Phone Gallery</title>
      <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
      <link rel="stylesheet" href="css/app.css">
      <!-- angular.js要放在第一位 -->
      <script src="bower_components/angular/angular.js"></script>
    
      <!-- 下面的引入顺序没有影响 -->
      <script src="bower_components/angular-route/angular-route.js"></script>
      <script src="bower_components/angular-resource/angular-resource.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>
    </head>

    基本入门

    ng-app标记了AngularJS的作用范围:

    <html lang="en" ng-app>

    双花括号绑定表达式,类似于JavaScript表达式,可以用于计算,动态输出,例如:

    <p>1 + 2 = {{ 1 + 2 }}</p>

    也可以是从其他地方的变量读取过来,例如:

    <html ng-app>
    <head>
      ...
      <script src="lib/angular/angular.js"></script>
      <script src="js/controllers.js"></script>
    </head>
    <body ng-controller="PhoneListCtrl">
    
      <ul>
        <li ng-repeat="phone in phones">
          {{phone.name}}
        <p>{{phone.snippet}}</p>
        </li>
      </ul>
    </body>
    </html>

    PhoneListCtrl是controllers.js定义的控制器的名字,controllers.js的内容,大概长这样:

    function PhoneListCtrl($scope) {
      $scope.phones = [
        {"name": "Nexus S",
         "snippet": "Fast just got faster with Nexus S.",
         "age": 0},
        {"name": "Motorola XOOM™ with Wi-Fi",
         "snippet": "The Next, Next Generation tablet.",
         "age": 1},
        {"name": "MOTOROLA XOOM™",
         "snippet": "The Next, Next Generation tablet.",
         "age": 2}
      ];
    
      $scope.orderProp = 'age';
    }

    phones是controllers.js中定义的数据,其作用域$scope是定义ng-controller元素以内的区域(即body)

    ng-repeat是AngularJS的迭代器,会遍历phones数组


    MVC

    AngularJS使用了MVC模式,即模型Model-视图View-控制器Controller。

    根据我的初步了解,可以作如下对应:

    模型:HTML标签,例如:

      <ul>
        <li ng-repeat="phone in phones">
          {{phone.name}}
        <p>{{phone.snippet}}</p>
        </li>
      </ul>

    视图:我们在页面上看到的实时数据
    控制器:在上个例子中,controllers.js就是控制器,他负责模型和视图的同步。


    双向绑定

    抽象地说,双向绑定即数据从视图到模型的绑定,以及从模型到视图的绑定。

    在实际的例子中,可以这样理解:假如视图中一个input元素(视图)绑定了query变量(模型),那么输入框内容(视图)的修改,会实时更新到这个变量(模型)中;反之亦然。

    可以看下面迭代器过滤这个例子


    迭代器过滤/排序

    迭代器中使用filter可以实时过滤输出

    下面这个输入框绑定了query,当输入框(视图)修改内容时,变量query(模型)的值会实时更新,这个更新的直接表现为filter:query改变,导致列表结果的更新。

    <input ng-model="query">

    在迭代器中添加filter:query,可以实时显示匹配搜索框的结果

    <ul class="phones">
      <li ng-repeat="phone in phones | filter:query">
        {{phone.name}}
        <p>{{phone.snippet}}</p>
      </li>
    </ul>

    类似地,如果为每个phone实例添加一个可以比较大小的属性,如age,那么我们就可以有name和age两种排序方式可以选择。

    Search: <input ng-model="query">
    Sort by:
    <select ng-model="orderProp">
      <option value="name">Alphabetical</option>
      <option value="age">Newest</option>
    </select>
    
    
    <ul class="phones">
      <li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
        {{phone.name}}
        <p>{{phone.snippet}}</p>
      </li>
    </ul>

    select选中项的value值(视图)会实时更新到orderProp(模型)中,迭代器会根据实际值来进行排序输出。


    XHR和依赖注入

    XHR即XMLHttpRequest。之前我们的数据都是定义在js文件内的,现在我们也可以用XHR异步请求一个JSON文件,并在请求成功后的回调函数里,将解析后的JSON赋值给模型。

    而所谓依赖注入,即AngularJS会根据你的需要,加载需要的服务(这里我们需要的是$http),以及该模块依赖的服务。

    function PhoneListCtrl($scope, $http) {//将需要的模块名作为参数传入
      $http.get('phones/phones.json').success(function(data) {
        $scope.phones = data;
      });
    
      $scope.orderProp = 'age';
    }
    
    PhoneListCtrl.$inject = ['$scope', '$http'];

    最后一行代码是为了防止JS压缩时,参数名字被修改导致无法识别正确的模块名而导致的错误。

    也可以使用传入数组的方式:

    var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];

    延迟替换

    对于img元素,如果src中带有{{…}},由于在AngularJS解析前浏览器就尝试直接去获取改src的资源,便会导致错误。因此,可以将src属性改写为ng-src:

    <img ng-src="{{phone.imageUrl}}">

    类似的还有Title元素,可以用ng-bind-template来替换,如:

    <title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>

    关于ng-bind-template和ng-bind的区别,官方解释如下:

    ng-bind-template

    The ngBindTemplate directive specifies that the element text content should be replaced with the interpolation of the template in the ngBindTemplate attribute. Unlike ngBind, the ngBindTemplate can contain multiple {{ }} expressions. This directive is needed since some HTML elements (such as TITLE and OPTION) cannot contain SPAN elements.

    即ng-bind-template可以包含{{}},而ng-bind则不能包含{{}}(ng-bind内的模型直接使用即可,不需要{{}})


    路由与多视图

    AngularJS可以方便地实现前端路由和多视图功能,可以在一个页面内,在不完全刷新的情况下跳转到另一个页面。

    在这种情况下,index.html是一个空模版:

    <!doctype html>
    <html lang="en" ng-app="phonecatApp">
    <head>
      <meta charset="utf-8">
      <title>Google Phone Gallery</title>
      <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
      <link rel="stylesheet" href="css/app.css">
      <script src="bower_components/angular/angular.js"></script>
      <script src="bower_components/angular-route/angular-route.js"></script>
      <script src="js/app.js"></script>
      <script src="js/controllers.js"></script>
    </head>
    <body>
    
      <div ng-view></div>
    
    </body>
    </html>

    body内的div具有ng-view属性,代表他是载入其他页面的容器。

    AngularJS的路由功能,可以让该容器,在不同URL上载入不同的页面模版。

    使用路由功能的JS代码如下:

    'use strict';
    
    /* App Module */
    
    var phonecatApp = angular.module('phonecatApp', [
      'ngRoute',
      'phonecatControllers'
    ]);
    
    phonecatApp.config(['$routeProvider',
      function($routeProvider) {
        $routeProvider.
          when('/phones', {
            templateUrl: 'partials/phone-list.html',
            controller: 'PhoneListCtrl'
          }).
          when('/phones/:phoneId', {
            templateUrl: 'partials/phone-detail.html',
            controller: 'PhoneDetailCtrl'
          }).
          otherwise({
            redirectTo: '/phones'
          });
      }]);

    $routeProvider.when告诉了浏览器路由规则,包括使用的模版页面以及对应的控制器。

    注意/phones/:phoneId,冒号声明的变量将会被提取,并存放到$routeParams中,调用方法如下:

    controllers.js

    'use strict';
    
    /* Controllers */
    
    var phonecatControllers = angular.module('phonecatControllers', []);
    
    phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
      function($scope, $http) {
        $http.get('phones/phones.json').success(function(data) {
          $scope.phones = data;
        });
    
        $scope.orderProp = 'age';
      }]);
    
    phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
      function($scope, $routeParams, $http) {
        $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
          $scope.phone = data;
        });
      }]);

    过滤器(映射器)

    过滤器,这名字乍一听,觉得会和前面讲的”迭代过滤器”混淆。对他进行一番了解后,觉得”映射器”的名字更为合适,也更容易理解。他的功能很容易理解:

    假如有一个模型的值是true或者false,我们可以利用过滤器,将其在视图中映射为√或者×。

    过滤器关键代码如下:

    'use strict';
    
    /* Filters */
    
    angular.module('phonecatFilters', []).filter('checkmark', function() {
      return function(input) {
        return input ? 'u2713' : 'u2718';//√和×的unicode编码
      };
    });

    这样我们新建了一个名为phonecatFilters的模块,其中有名为checkmark的过滤器。之后在phonecatApp里添加对phonecatFilters模块的依赖:

    var phonecatApp = angular.module('phonecatApp', [
      'ngRoute',
      'phonecatControllers',
      'phonecatFilters'
    ]);

    之后就可以在模型中使用了:

    ...
        <dl>
          <dt>Infrared</dt>
          <dd>{{phone.connectivity.infrared | checkmark}}</dd>
          <dt>GPS</dt>
          <dd>{{phone.connectivity.gps | checkmark}}</dd>
        </dl>
    ...

    事件处理

    AngularJS有自己的一套事件处理机制,click事件绑定如下,首先定义事件处理函数:

    function PhoneDetailCtrl($scope, $routeParams, $http) {
    ...
     $scope.setImage = function(imageUrl) {
        $scope.mainImageUrl = imageUrl;
      }
    }

    然后在模版中进行绑定

    <img ng-src="{{mainImageUrl}}" class="phone">
    
    ...
    
    <ul class="phone-thumbs">
      <li ng-repeat="img in phone.images">
        <img ng-src="{{img}}" ng-click="setImage(img)">
      </li>
    </ul>
    ...

    定制服务

    我对定制服务的理解还不是非常深刻。定制服务的优势,原文是这样描述的

    对我们应用所做的最后一个改进就是定义一个代表RESTful客户端的定制服务。有了这个客户端我们可以用一种更简单的方式来发送XHR请求,而不用去关心更底层的$http服务(API、HTTP方法和URL)。

    我们定义的服务写在services.js中,另外我们还需要ngResource模块的$resource服务,他被定义在angularjs-resource.js中。

    services.js

    'use strict';
    
    /* Services */
    
    var phonecatServices = angular.module('phonecatServices', ['ngResource']);
    
    phonecatServices.factory('Phone', ['$resource',
      function($resource){
        return $resource('phones/:phoneId.json', {}, {
          query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
        });
      }]);

    app.js中引入phonecatServices模块

    var phonecatApp = angular.module('phonecatApp', [
      'ngRoute',
      'phonecatControllers',
      'phonecatFilters',
      'phonecatServices'
    ]);

    之后,我们就可以把controllers.js改写为下面这样

    'use strict';
    
    /* Controllers */
    
    var phonecatControllers = angular.module('phonecatControllers', []);
    
    phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
      function($scope, Phone) {
        $scope.phones = Phone.query({phoneId:'phones'});
        $scope.orderProp = 'age';
      }]);
    
    phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
      function($scope, $routeParams, Phone) {
        $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
          $scope.mainImageUrl = phone.images[0];
        });
    
        $scope.setImage = function(imageUrl) {
          $scope.mainImageUrl = imageUrl;
        }
      }]);

    services.js作一些说明:

     var phonecatServices = angular.module('phonecatServices', ['ngResource']);
    //声明一个phonecatServices模块
    
     phonecatServices.factory('Phone', ['$resource',
    //使用工厂方法,声明一个名为Phone的服务
      function($resource){
        return $resource('phones/:phoneId.json', {}, {
          query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
    //定义Phone服务的query方法,用于GET资源'phones/:phoneId.json',其中:phoneId是变量,该默认值被定义为"phones"。当然也可以不声明默认变量,而是在调用时传入实参,下面会提及。
        });
      }]);

    controller.js作一些说明:

    'use strict';
    
    /* Controllers */
    
    var phonecatControllers = angular.module('phonecatControllers', []);
    
    phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
      function($scope, Phone) {
        $scope.phones = Phone.query();
    /*
    之前:$http.get('phones/phones.json').success(function(data) {  $scope.phones = data;});
    现在:$scope.phones = Phone.query();
    可见这里被简化了。如果不给Phone.query传入实参,他会使用默认参数phones,当然我们也可以手动传入参数:Phone.query({phoneId:'phones'})
    */
        $scope.orderProp = 'age';
      }]);
    
    phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
      function($scope, $routeParams, Phone) {
        $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
          $scope.mainImageUrl = phone.images[0];
        });
    /*
    在services.js中并没有定义Phone.get方法,为什么可以使用它呢?
    
    通过查阅API文档可知,$resource方法的返回值是一个可被扩展的对象,其自带get,save,query(query已经被我们扩展了),remove,delete函数。
    
    通过get方法,并传入被点击的链接对应的phoneId作为参数,获取手机详细页所需要的json文件,在成功后执行回调函数。
    */
        $scope.setImage = function(imageUrl) {
          $scope.mainImageUrl = imageUrl;
        }
      }]);
  • 相关阅读:
    idea 插件之 SequenceDiagram
    idea 中添加mybatis的mapper文件模板
    springBoot 中 logback配置文件详解
    Mysql show processlist、show profiles 排查问题
    input 输入框效验
    Java基础之comparator和comparable的区别以及使用
    mysql sql使用记录
    mysql 优化之索引的使用
    IDEA 中常用快捷键的使用
    form表单中method的get和post区别
  • 原文地址:https://www.cnblogs.com/qs20199/p/4452268.html
Copyright © 2020-2023  润新知