《AngularJs权威指南》--读书笔记

第二章、数据绑定

2.2 简单的数据绑定

<!DOCTYPE html>
<html ng-app>
  <head>
    <title>Simple app</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.js"></script>
  </head>
  <body>
    <input ng-model="name" type="text" placeholder="Your name" />
    <h1>Hello {{ name }}</h1>
  </body>
</html>

ng-app 指令定义一个 AngularJS 应用程序。ng-app 声明所有被它包含的元素都属于 AngularJS 应用
ng-model 指令把元素值(比如输入域的值)绑定到应用程序。
ng-controller 指令定义了应用程序控制器。DOM 元素上的 ng-controller 声明所有被它包含的元素都属于某个控制器。
AngularJS 表达式写在双大括号内:。

  • 它们可以包含文字、运算符和变量。
  • 可以写在 HTML 中
  • 支持过滤器,不支持条件判断,循环及异常。
是通过 ng-model="name" 进行同步。

2.3 数据绑定的最佳实现

通常认为,在视图中通过对象的属性而非对象本身来进行引用绑定,是 Angular 中的最佳实践。

function MyController($scope) {
    $scope.clock = {
        now: new Date()
    };
    var updateClock = function() {
        $scope.clock.now = new Date()
    };
    setInterval(function() {
        $scope.$apply(updateClock);
    }, 1000);
    updateClock();
};

第三章、模块

模块包含了主要的应用代码。一个应用可以包含多个模块,每一个模块都包含了定义具体功能的代码。
AngularJS 允许我们使用 angular.module()方法来声明模块,这个方法能够接受两个参数,第一个是模块的名称,第二个是依赖列表,也就是可以被注入到模块中的对象列表。
angular.module(name,requires);

  • name 是模块的名称,字符串变量。
  • requires 包含了一个字符串变量组成的列表,每个元素都是一个模块名称,本模块依赖于这些模块,依赖需要在本模块加载之前由注入器进行预加载。
//是用来定义模块的
angular.module('myApp', []);
// 用于获取应用
angular.module('myApp')

第四章、作用域

4.1 视图和 $scope

AngularJS 启动并生成视图时,会将根 ng-app 元素同$rootScope进行绑定。$rootScope是所有$scope对象的最上层。$rootScope是AngularJS中最接近全局作用域的对象
$scope 对象就是一个普通的 JavaScript 对象,我们可以在其上随意修改或添加属性。

4.2 作用域能做什么

作用域的功能:

  • 提供观察者以监视数据模型的变化;
  • 可以将数据模型的变化通知给整个应用,甚至是系统外的组件;
  • 可以进行嵌套,隔离业务功能和数据;
  • 给表达式提供运算时所需的执行环境。

ng-controller 和 ng-repeat 指令会创建自己的子作用域并将它们附加到 DOM 元素上。

第五章、控制器

控制器并不适合用来执行 DOM 操作、格式化或数据操作,以及存储数据之外的状态维护操作,允许在$scope上设置数据

5.1 控制器嵌套

<div ng-controller="ParentController">
  <div ng-controller="ChildController">
    <a ng-click="sayHello()">Say hello</a>
  </div>
  {{ person }}
</div>

点击按钮时,可以在 ChildController 中访问 ParentController 中$scope.person的值

var app=angular.module("myApp",[]);
app.controller("myController",['$scope','aService',...,function($scope,aService,...){
    //可以注入你写的factory,provider等等
}]);

controller 第一个参数是名称,后面是一个数组,数组的前面是声明注入的内容,可以是 n 个,最后是个 function,function 的参数个数也必须是 n 个,必须跟前面声明注入的内容一一对应,就这样实现了依赖注入

第六章、表达式

  • 所有的表达式都在其所属的作用域内部执行,并有访问本地$scope 的权限;
  • 如果表达式发生了 TypeError 和 ReferenceError 并不会抛出异常;
  • 不允许使用任何流程控制功能(条件控制,例如 if/eles)
  • 可以接受过滤器和过滤器链。

6.1 解析 angularjs 表达式

$watch(watchFn, watchAction, deepWatch)
watchFn 该参数是一个带有 Angular 表达式或者函数的字符串,它会返回被监控的数据模型的当前值
watchAction 这是一个函数或者表达式,当 watchFn 发生变化时会被调用。
如果是函数的形式,它将会接收到 watchFn 的新旧两个值,以及作用域对象的引用。function(newValue, oldValue, scope)。
deepWatch 如果设置为 true,这个可选的布尔型参数将会命令 Angular 去检查被监控对象的每个属性是否发生了变化。
$parse(expression)
将一个 AngularJS 表达式转换成一个函数 function(context,locals)
context[object]:针对你要解析的语句,这个对象中含有你要解析的语句中的表达式(通常是一个 scope object)
locals[object]: 关于 context 中变量的本地变量,对于覆盖 context 中的变量值很有用。

angular
  .module("myApp", [])
  .controller("MyController", function ($scope, $parse) {
    $scope.$watch("expr", function (newVal, oldVal, scope) {
      if (newVal !== oldVal) {
        // 用该表达式设置parseFun
        var parseFun = $parse(newVal);
        // 获取经过解析后表达式的值
        $scope.parsedValue = parseFun(scope);
      }
    });
  });

6.2 插值字符串

要在字符串模板中做插值操作,需要在你的对象中注入$interpolate服务。
$interpolate 服务是一个可以接受三个参数的函数,其中第一个参数是必需的。返回一个函数,用来在特定的上下文中运算表达式。

  • text(字符串):一个包含字符插值标记的字符串。
  • mustHaveExpression(布尔型):如果将这个参数设为 true,当传入的字符串中不含有表达式时会返回 null。
  • trustedContext(字符串):AngularJS 会对已经进行过字符插值操作的字符串通过$sec.getTrusted()方法进行严格的上下文转义。
<div ng-controller="MyController">
    <input ng-model="to" type="email" placeholder="Recipient" />
    <textarea ng-model="emailBody"></textarea>
    <pre>{{ previewText }}</pre>
</div>
angular.module('myApp', [])
.controller('MyController', function($scope, $interpolate) {
    $scope.$watch('emailBody', function(body) {
        if (body) {
            var template = $interpolate(body);
            $scope.previewText = template({to: $scope.to});
        }
    };
});

第七章、过滤器

在 HTML 中的模板绑定符号运算符内通过|符号来调用过滤器。

{{ name | uppercase }}

在 JavaScript 代码中可以通过$filter来调用过滤器

app.controller("DemoController", [
  "$scope",
  "$filter",
  function ($scope, $filter) {
    $scope.name = $filter("lowercase")("Ari");
  },
]);

内置过滤器

  1. currency
  2. date
  3. filter
    这个过滤器的第一个参数可以是字符串、对象或是一个用来从数组中选择元素的函数。
    我们也可以给 filter 过滤器传入第二个参数,用来指定预期值同实际值进行比较的方式
    • 字符串
      返回所有包含这个字符串的元素。如果我们想返回不包含该字符串的元素,在参数前加!符号。
    {{ ['Ari','Lerner','Likes','To','Eat','Pizza'] | filter:'e' }}
    <!-- ["Lerner","Likes","Eat"] -->
    • 对象
      AngularJS 会将待过滤对象的属性同这个对象中的同名属性进行比较,如果属性值是字符串就会判断是否包含该字符串。
    {{ [
          {'name': 'Ari','City': 'San Francisco','favorite food': 'Pizza'},
          {'name': 'Nate','City': 'San Francisco','favorite food': 'indian food'}
       ] | filter:{'favorite food': 'Pizza'}
    }}
    <!-- [{"name":"Ari","City":"SanFrancisco","favoritefood":"Pizza"}] -->
    • 函数
      对每个元素都执行这个函数,返回非假值的元素会出现在新的数组中并返回。
    {{ ['Ari','likes','to','travel'] | filter:isCapitalized }}
    <!-- ["Ari"] -->
    $scope.isCapitalized = function(str) {
      return str[0] == str[0].toUpperCase();
    };
  4. json
    json 过滤器可以将一个 JSON 或 JavaScript 对象转换成字符串
  5. limitTo
    limitTo 过滤器会根据传入的参数生成一个新的数组或字符串,新的数组或字符串的长度取决于传入的参数,通过传入参数的正负值来控制从前面还是从后面开始截取。
{{ San Francisco is very cloudy | limitTo:3 }}<!-- San -->
  1. lowercase
  2. uppercase
  3. number
    number 过滤器将数字格式化成文本。它的第二个参数是可选的,用来控制小数点后截取的位数。
  4. orderBy
    orderBy 过滤器可以用表达式对指定的数组进行排序。可以接受两个参数,第一个是必需的,第二个是可选的,用来控制排序的方向(是否逆向)。
    {{
      [{
          'name': 'Ari','status': 'awake'
      },{
          'name': 'Q','status': 'sleeping'
      },{
           'name': 'Nate','status': 'awake'
      }] | orderBy:'name':true
    }}
    <!--[
        {"name":"Q","status":"sleeping"},
        {"name":"Nate","status":"awake"},
        {"name":"Ari","status":"awake"}
    ]-->

7.1 自定义过滤器

{{ 'ginger loves dog treats' | lowercase | capitalize }}
<!-- Ginger loves dog treats -->
//自定义capitalize过滤器
angular.module('myApp', [])
.filter('capitalize', function() {
    return function(input) {
    // input是我们传入的字符串
    if (input) {
        return input[0].toUpperCase() + input.slice(1);
    }
});

7.2 表单验证

如果想要屏蔽浏览器对表单的默认验证行为,可以在表单元素上添加 novalidate 标记。
可以在 input 元素上使用的所有验证选项

  • 最小长度 <input type="text" ng-minlength="5" />
  • 最大长度 <input type="text" ng-maxlength="20" />
  • 模式匹配 <input type="text" ng-pattern="[a-zA-Z]" />
  • 在表单中控制变量 + 未修改的表单 formName.inputFieldName.$pristine 如果未修改,值为 true + 修改过的表单 formName.inputFieldName.$dirty 只要用户修改过表单,该值都返回 true + 合法的表单 formName.inputFieldName.$valid + 不合法的表单 formName.inputFieldName.$invalid + 错误 formName.inputfieldName.$error 如果验证失败,这个属性的值为 true + 一些有用的 CSS 样式 .ng-pristine{} .ng-dirty{} .ng-valid{} .ng-invalid{} + $parsers + $formatters
    给输入字段添加 name 属性非常重要:这决定了我们将验证信息展示给用户时如何引用表单输入字段。

第八章、指令简介

8.1 自定义 HTML 元素和属性

通过 AngularJS 模块 API 中的.directive()方法,我们可以通过传入一个字符串和一个函数来注册一个新指令。其中字符串是这个指令的名字函数应该返回
一个对象包含了用来定义和配置指令所需的方法和属性。
<my-directive></my-directive>

angular.module('myApp',[])
.directive('myDirective', function() {
    return {
        restrict: 'E',
        template: '<a href="http://google.com">
        Click me to go to Google</a>'
    };
});

将 replace 设置为 true 就将自定义标签从生成的 DOM 中完全移除掉,并只留下由模版生成的链接
restrict 可以指定以元素(E)、属性(A)、类(C)或注释(M)的格式来调用指令,好的经验法则就是始终用属性来声明指

8.2 向指令中传递数据

AngularJS 允许通过创建新的子作用域或者隔离作用域来解决这个常见问题

第九章、内置指令

9.1 基础 ng 属性指令

9.1.1 布尔属性
  1. ng-disabled 用 ng-disabled 可以把 disabled 属性绑定到<input><textarea><select><button>表单输入字段上
  2. ng-readonly 通过 ng-readonly 可以将某个返回真或假的表达式同是否出现 readonly 属性进行绑定:
  3. ng-checked 通过 ng-checked 将某个表达式同是否出现 checked 属性进行绑定。
  4. ng-selected ng-selected 可以对是否出现 option 标签的 selected 属性进行绑定
9.1.2 类布尔属性
  1. ng-href 当使用当前作用域中的属性动态创建 URL 时,应该用 ng-href 代替 href。
  2. ng-src AngularJS 会告诉浏览器在 ng-src 对应的表达式生效之前不要加载图像

9.2 在指令中使用子作用域

  1. ng-app
    ng-app 为 AngularJS 应用创建$rootScope,任何具有ng-app属性的DOM元素将被标记为$rootScope 的起始点。任何嵌套在 ng-app 内的指令都会继承它

  2. ng-controller
    ng-controller 则会以$rootScope或另外一个ng-controller的作用域为原型创建新的子作用域
    子$scope 只是一个 JavaScript 对象,其中含有从父级$scope中通过原型继承得到的方法和属性,包括应用的$rootScope

  3. ng-include
    使用 ng-include 可以加载、编译并包含外部 HTML 片段到当前的应用中。模板的 URL 被限制在与应用文档相同的域和协议下.
    <div ng-include="/myTemplateName.html" ng-controller="MyController" ng-init="name = 'World'"> Hello {{ name }} </div>

  4. ng-switch
    这个指令和 ng-switch-when 及 on="propertyName"一起使用,可以在 propertyName 发生变
    化时渲染不同指令到视图中
    <input type="text" ng-model="person.name"/> <div ng-switch on="person.name"> <p ng-switch-default>And the winner is</p> <h1 ng-switch-when="Ari">{{ person.name }}</h1> </div>

  5. ng-view
    ng-view 指令用来设置将被路由管理和放置在 HTML 中的视图的位置

  6. ng-if
    使用 ng-if 指令可以完全根据表达式的值在 DOM 中生成或移除一个元素
    ng-if 同 no-show 和 ng-hide 指令最本质的区别是,它不是通过 CSS 显示或隐藏 DOM 节点,而是真正生成或移除节点。

  7. ng-repeat
    ng-repeat 用来遍历一个集合或为集合中的每个元素生成一个模板实例

    • $index:遍历的进度(0…length-1)。
    • $first:当元素是遍历的第一个时值为 true。
    • $middle:当元素处于第一个和最后元素之间时值为 true。
    • $last:当元素是遍历的最后一个时值为 true。
    • $even:当$index值是偶数时值为true。 + $odd:当$index值是奇数时值为 true。
  8. ng-init
    ng-init 指令用来在指令被调用时设置内部作用域的初始状态。

  9. 双括号 与 ng-bind
    双括号语法是 AngularJS 内置的模板语法,它会在内部$scope 和视图之间创建绑定.在屏幕可视的区域内使用双括号会导致页面加载时未渲染的元素发生闪烁,用 ng-bind 可以避免这个问题。

  10. ng-cloak
    除使用 ng-bind 来避免未渲染元素闪烁,还可以在含有双括号的元素上使用 ng-cloak 指令,ng-cloak 指令会将内部元素隐藏,直到路由调用对应的页面时才显示出来
    <body ng-init="greeting='HelloWorld'"> <p ng-cloak>{{ greeting }}</p> </body>

  11. ng-bind-template
    同 ng-bind 指令类似,ng-bind-template 用来在视图中绑定多个表达式

  12. ng-model
    ng-model 指令用来将 input、select、text area 或自定义表单控件同包含它们的作用域中的属性进行绑定
    我们应该始终用 ngModel 来绑定$scope上一个数据模型内的属性,而不是$scope 上的属性,这可以避免在作用域或后代作用域中发生属性覆盖。

  13. ng-show/ng-hide
    ng-show 和 ng-hide 根据所给表达式的值来显示或隐藏 HTML 元素
    元素的显示或隐藏是通过移除或添加 ng-hide 这个 CSS 类来实现的。.ng-hide 类被预先定义并且它的 display 属性的值为 none

  14. ng-change
    这个指令会在表单输入发生变化时计算给定表达式的值。因为要处理表单输入,这个指令要和 ngModel 联合起来使用。

  15. ng-form
    ng-form 用来在一个表单内部嵌套另一个表单。普通的 HTML<form>标签不允许嵌套,但 ng-form 可以。

  16. ng-click
    ng-click 用来指定一个元素被点击时调用的方法或表达式

  17. ng-select
    ng-select 用来将数据同 HTML 的<select>元素进行绑定。这个指令可以和 ng-model 以及 ng-options 指令一同使用
    <div ng-controller="CityController"> <select ng-model="city" ng-options="city.name for city in cities"> <option value="">Choose City</option> </select> Best City: {{ city.name }} </div> angular.module('myApp',[]) .controller('CityController',function($scope) { $scope.cities = [ {name: 'Seattle'}, {name: 'San Francisco'}, {name: 'Chicago'}, {name: 'New York'}, {name: 'Boston'} ]; });

  18. ng-submit
    ng-submit 用来将表达式同 onsubmit 事件进行绑定。这个指令同时会阻止默认行为(发送请求并重新加载页面),除非表单不含有 action 属性

  19. ng-class
    使用 ng-class 动态设置元素的类,方法是绑定一个代表所有需要添加的类的表达式。

  20. ng-attr-(suffix)

<svg>
    <circle cx="{{ cx }}"></circle>
</svg>

指出我们有一个非法属性。可以用 ng-attr-cx 来解决这个问题。

<svg>
    <circle ng-attr-cx="{{ cx }}"><circle>
</svg>

第十章、指令详解

10.1 指令定义

directive()这个方法是用来定义指令的

angular.module('myApp', [])
.directive(name,factory_function(){
    // 指令定义放在这里
});
angular.module('myApp', [])
.directive('myDirective', function() {
    return {
        restrict: String,
        priority: Number,
        terminal: Boolean,
        template: String or Template Function:
        function(tElement, tAttrs) (...},
        templateUrl: String,
        replace: Boolean or String,
        scope: Boolean or Object,
        transclude: Boolean,
        controller: String or function(scope, element, attrs, transclude, otherInjectables){ ... },
        controllerAs: String,
        require: String,
        link: function(scope, iElement, iAttrs) { ... },
        compile:
            // 返回一个对象或连接函数,如下所示:
            function(tElement, tAttrs, transclude) {
                return {
                    pre: function(scope, iElement, iAttrs, controller) { ... },
                    post: function(scope, iElement, iAttrs, controller) { ... }
                }
                // 或者
                return function postLink(...) { ... }
            }
    };
});

指令的生命周期开始于$compile 方法并结束于 link 方法

10.1.1 restrict(字符串)

restrict 是一个可选的参数。它告诉 AngularJS 这个指令在 DOM 中可以何种形式被声明,默认 AngularJS 认为 restrict 的值是 A,即以属性的形式来进行声明

10.1.2 优先级(数值型)

如果一个元素上具有两个优先级相同的指令,声明在前面的那个会被优先调用。如果其中一个的优先级更高,则不管声明的顺序如何都会被优先调用:具有更高优先级的指令总是优先运行

10.1.3 terminal(布尔型)

这个参数用来告诉 AngularJS 停止运行当前元素上比本指令优先级低的指令。

10.1.4 template(字符串或函数)

template 参数是可选的,必须被设置为以下两种形式之一:

  • 一段 HTML 文本;
  • 一个可以接受两个参数的函数,参数为 tElement 和 tAttrs,并返回一个代表模板的字符串

在实际生产中,更好的选择是使用 templateUrl 参数引用外部模板

10.1.5 templateUrl(字符串或函数)

templateUrl 是可选的参数,可以是以下类型:

  • 一个代表外部 HTML 文件路径的字符串;
  • 一个可以接受两个参数的函数,参数为 tElement 和 tAttrs,并返回一个外部 HTML 文件路径的字符串。

默认情况下,调用指令时会在后台通过 Ajax 来请求 HTML 模板文件,模板加载后,AngularJS 会将它默认缓存到$templateCache 服务中。在实际生产中,可以提前将模板缓存到一个定义模板的 JavaScript 文件中,这样就不需要通过 XHR 来加载模板了

10.1.6 replace(布尔型)

replace 是一个可选参数,如果设置了这个参数,值必须为 true,因为默认值为 false。默认值意味着模板会被当作子元素插入到调用此指令的元素内部

10.2 指令作用域

10.2.1 scope 参数(布尔型或对象)

scope 参数是可选的,可以被设置为 true 或一个对象。默认值是 false。
当 scope 设置为 true 时,会从父作用域继承并创建一个新的作用域对象。
内置指令 ng-controller 的作用,就是从父级作用域继承并创建一个新的子作用域。
如果要创建一个能够从外部原型继承作用域的指令,需将 scope 属性设置为 true

10.2.2 隔离作用域

具有隔离作用域的指令最主要的使用场景是创建可复用的组件

10.3 绑定策略

AngularJS 提供了几种方法能够将指令内部的隔离作用域,同指令外部的作用域进行数据绑定。
为了让新的指令作用域可以访问当前本地作用域中的变量,需要使用下面三种别名中的一种

  • 本地作用域属性:使用@符号将本地作用域同 DOM 属性的值进行绑定。指令内部作用域可以使用外部作用域的变量
  • 双向绑定:通过=可以将本地作用域上的属性同父级作用域上的属性进行双向的数据绑定。
  • 父级作用域绑定 通过&符号可以对父级作用域进行绑定,以便在其中运行函数。意味着对这个值进行设置时会生成一个指向父级作用域的包装函数。
10.3.1 transclude

transclude 是一个可选的参数。如果设置了,其值必须为 true,它的默认值是 false。
嵌入通常用来创建可复用的组件,典型的例子是模态对话框或导航栏。
为了将作用域传递进去,scope 参数的值必须通过{}或 true 设置成隔离作用域。如果没有设置 scope 参数,那么指令内部的作用域将被设置为传入模板的作用域
如果指令使用了 transclude 参数,那么在控制器(下面马上会介绍)中就无法正常监听数据模型的变化了。这就是最佳实践总是建议在链接函数里使用$watch服务的原因。

10.3.2 controller(字符串或函数)

controller 参数可以是一个字符串或一个函数

angular.module('myApp', [])
.directive('myDirective', function() {
    restrict: 'A', // 始终需要
    controller: 'SomeController'
})

可以在指令内部通过匿名构造函数的方式来定义一个内联的控制器:

angular.module('myApp',[])
.directive('myDirective', function() {
    restrict: 'A',
    controller:
    function($scope, $element, $attrs, $transclude) {
    // 控制器逻辑放在这里
    }
});

控制器中也有一些特殊的服务可以被注入到指令当中。这些服务有:

  1. $scope 与指令元素相关联的当前作用域
  2. $element 当前指令对应的元素
  3. $attrs 由当前元素的属性组成的对象
  4. $transclude 嵌入链接函数会与对应的嵌入作用域进行预绑定