AngularJS 基础知识总结(一)

UI 和控制器分离

控制器在应用中有三个作用:

  • 在应用模型中设置初始状态
  •  通过$scope 向视图(UI 模板)暴露模型和函数
  • 监视模型发生变化的其他部分并做出相应的动作

如果你有复杂的界面场景,那么你可以让你的代码保持简洁、可维护。通过创建内嵌的 控制器,通过继承结构这些控制器能够共享模型和函数。嵌套的控制器是简单的。你可以简 单地把一个控制器给以 DOM 元素,DOM 元素里面的是另外一个控制器,就像这样:

<div ng-controller="ParentController">
<div ng-controller="ChildController">
............
</div>
</div>

尽管我们通过内嵌控制器实现了这个,但是实际上是在作用域上发生了嵌套。传递给内 嵌控制的 scope scope。在这种场景下,这意味着传递给 ChildController 的 scope访ParentController scope 的所有属性。

**

发布 scope 中的模型数据

**
1. 使用表达式。由于表达式在和它们关联的元素的控制器域中执行,所以在表达式中设置 属性和在控制器中设置属性是一样的。那就是说,可以这做:

<button ng-click='count=3'>Set count to three</button>

和下面这样做有相同的效果。

<div ng-controller='CountController'>
<button ng-click='setCount()'>Set count to three</button>
</div>

控制器定义如下:

function CountController($scope) {
 $scope.setCount = function() { 
 $scope.count=3;
}
}
  1. 在表单输入框中使用 ng-model。和表达式一样,ng-model 指定的模型参数是在控制器
    作用域中执行的。此外,它还在表单字段状态和指定的模型建立了数据绑定。

**

用$watch 观察模型变化

**
也许所有的 scope 功能函数中使用最多的就是$watch 函数,因为当你的模型发生改变 时能够通知到呢。你可以监视单个对象属性或者计算结果(或函数),事实上,可以是任何 能被访问的属性或者可以计算结果的 JavaScript 函数。这个函数签名:

$watch(watchFn, watchAction, deepWatch)

每个参数的详细内容如下:
watchFn
这个参数是一个 Angular 表达式字符串或是一个返回监控的模型的当前值的函数。这个 表达式会被执行多次,因此你需要确保它没有副作用。那就是说,没有改变状态下可以调用 多次。出于同样的原因,watch 的表达式应该是计算上比较简单的。如果你传递一个字符串 方式的 Angular 表达式,它将会在被调用的 scope 上用对象执行。
watchAction
这是一个函数或者表达式,当 watchFn 变化时将调用他们。函数形式而言,它有 watchFn 的新旧值以及一个 scope 引用,函数签名是 function(newValue,oldValue,scope)
deepWatch
这是一个可选的布尔参数,如果设置成 true,Angular 将检查在被监视对象中每个属性 的每次变化。如果你希望监视数组中的私有元素或者对象中的属性,而不是仅仅一个简单的 值时,你应该使用这个参数。

当你不再希望收到变化通知时,$watch 函数返回一个取消监听的函数。 如果你希望监视一个属性,之后取消注册,我们使用如下形式:

var dereg = $scope.$watch('someModel.someProperty', callbackOnChange());
...
dereg( ) ;

比如说我们希望当用户购物车中的
总值超过 100 10 的优惠。我们使用如下模板:

<div ng-app="myApp" ng-controller="CartController">

    <div ng-repeat="item in items">

        <span>{{item.title}}</span>
        <input ng-model="item.quantity">
        <span>{{item.price | currency}}</span>
        <span>{{item.price * item.quantity | currency}}</span>

    </div>
    <div>Total: {{totalCart() | currency}}</div>
    <div>Discount: {{bill.discount | currency}}</div>
    <div>Subtotal: {{subtotal() | currency}}</div>
</div>

CartController 就像这样:

<script>
    var app = angular.module('myApp', []);
    app.controller('CartController', function($scope) {
        $scope.bill = {};
        $scope. items = [
            {title: 'Paint pots', quantity: 8, price: 3.95},
            {title: 'Polka dots', quantity: 17, price: 12.95},
            {title : 'Pebbles', quantity: 5, price: 6.95}
        ];
        $scope.totalCart = function() {
            var total = 0;
            for (var i = 0, len = $scope.items.length; i < len; i++) {
                total = total + $scope.items[i].price * $scope.items[i].quantity;
            }
            return total;
        }
        $scope.subtotal = function() {
            return $scope.totalCart() - $scope .discount;
        };


        function calculateDiscount(newValue, oldValue, scope) {
            $scope.bill.discount = newValue > 100 ? 10 : 0;
        }
        $scope.$watch($scope.totalCart, calculateDiscount);
    });
</script>

注意:在 CartController 控制器末尾,我们在 totalCart()上建立一个监视,totalCart()是用来计算付款总额的。每当这个值发生改变时,watch 函数就会调用 calculateDiscount(),然后 我们设置对这个值做适当的折扣。 ,就设置折扣为 10$,否则折扣为 0

**

Watch()性能注意事项

**
前面的例子虽然能够正确执行,但是有一个潜在的性能问题。虽然不是很明显,但是如 果在 totalCart()加一个调试断点,你将会看到它被调用了 6 次之后才渲染页面。虽然在应用 中从未关注过它,但是在更加复杂的应用中,运行 6 次可能是一个问题。
为什么是 6 次?我们很容易地能够跟踪到 3 次,因为他们每个只运行一次:

  •  模板中的{{totalCart | currency}}
  •  Subtotal()函数

Angular 又运行了它们一次,所以执行了 6 次。Angular 这样做是位了验证传递的模型变 化已经完全传递,模型已经不再变化。Angular 是通过对所有的被监视属性做了一份备份, 和当前值进行比对,从而观察它们是否发生变化来做这个检查的。事实上,Angular 可能运 行高达十次从而确保完全的传播。如果十次迭代后变化仍然出现,那么 Angular 会带一个错 误执行结束。如果发生了这种情况,那么你可能遇到了一个循环依赖,你需要修正它。
虽然你可能会担心这样的问题,但是到时候你读完本书后,这就不是问题了。虽然 Angular 在 JavaScript 中实现了数据绑定,但是我们一直致力于 上一个低层次的叫 Object.observe()本地实现。适当的使用这个,Angular 将会自动使用 Object.observe()随时随 地的向你展示本地加速的数据绑定效果。
在下一章节你将会看到,Angular 有一个非常棒的 Chrome 调试扩展应用,叫 Batarang, 它能够为你自动地高亮那些昂贵的数据绑定。
既然我们了解了这问题,我们可以用几种方法来解决它。一种方法就是在数组中的每个 元素上创建 watch scope 属性中的 total,discount 和 subtotal。
为了实现这点,我们将用这几个属性来更新模板:

<div>Total: {{bill.total| currency}}</div> <div>Discount: {{bill.discount | currency}}</div> <div>Subtotal: {{bill.subtotal | currency}}</div>

然后在 JavaScript 中,我们将监视数组中的元素,数组发生任何改变都会调用函数重新 计算总价,就像这样:

<script>
    var app = angular.module('myApp', []);
    app.controller('CartController', function($scope) {
$scope.bill = {};
$scope. items = [
{title: 'Paint pots', quantity: 8, price: 3.95},
{title: 'Polka dots', quantity: 17, price: 12.95}, {title : 'Pebbles', quantity: 5, price: 6.95}
];
var calculateTotals = function() {
var total = 0;
for (var i = 0, len = $scope.items.length; i < len; i++) {
total = total + $scope.items[i].price * $scope.items[i].quantity;
}
$scope.bill.totalCart = total;
$scope.bill.discount = total > 100 ? 10 : 0; $scope.bill.subtotal = total - $scope.bill.discount;
};
$scope.$watch('items', calculateTotals, true);

  });
</script>

注意: watchitems watch 函数既可以指定一 个函数(就像我们之前做的)或是一个字符串。如果给 watch scope 作用域中执行。
这种策略可能对你的应用非常有用。然而,由于我们监视着数组中的所有项,Angular 不得不为我们对它做一份拷贝用于对比。对于一个很大的元素列表,如果每次 Angular 执行 页面我们只是重新计算 bill 这个属性,那么它可能运行的很好。我们可以给$watch 只传一个 用于重新计算属性的 watchFn,就像这样:

$scope.$watch(function() {
var total = 0;
for (var i = 0; i < $scope.items.length; i++) {
total = total + $scope.items[i].price * $scope.items[i].quantity;
}
$scope.bill.totalCart = total;
$scope.bill.discount = total > 100 ? 10 : 0; $scope.bill.subtotal = total - $scope.bill.discount; });

监视多个事物
倘若你希望监视多个属性或对象,当他们发生变化是执行一个函数改怎么办?你有两种 基本的选择:

  •  把他们放到一个数组或对象中,同时传递 deepWatch 为 true
  •  监控这些属性的连接串

    第一种场景,如果你有一个对象,在作用域内有两个属性,希望在发生变化时执行 callMe
    函数,你可以通过这样来监视它们:

$scope.$watch('things.a + things.b', callMe(...));

当然,属性 a,b 可能在不同的对象上,那么只要你愿意你可以制造很长的列表。如果 列表很长,你可以写一个函数,返回连接串而不是依赖表达式的逻辑。
第二种场景,你可能希望监视 things 对象上所有的属性。这种场景,你可以这样做:

$scope.$watch('things', callMe(...), true);

这里,第三个参数传递了 true,就是要求 Angular 检查 things 对象的所有属性,只要它 们任何一个发生变化就会调用 callMe()函数。这个在数组上运行很好,同样适用对象。

**

组织模块依赖

**
在任何高价值的应用中,搞清楚如何组织管理你的代码功能到责任范围通常是一项艰难 的工作。我们已经看到了控制器是如何给我们一块地方用于存放暴露正确的数据和函数给视 图层模板的代码。但是我们还需要其他代码来支持我们的应用,那该怎么办?能够容纳这个 最明显的地方就是控制器中的函数。
这个对于小应用和到目前为止看到的示例很有用,但是在真实应用中很快变得难以管理。 控制器可能变成一块垃圾场,里面有任何我们需要的东西。它们很难理解,有可能很难再去 更改。
这里开始介绍 。它为应用中的函数域提供了一种组织管理依赖的方法,是一种自动
解决依赖的机制(也被称作依赖注入)。大体上说,我们称这些为依赖服务,因为它们给应 用提供了指定的服务。
例如:如果我们的购物站点里一个控制器需要从服务器端查询一份销售列表,我们想用 一些对象,暂时称作 Items,用来处理从服务器端查询列表。Items 对象依次需要一些通过 XHR 或 WebSockets 的方法和服务器上的数据库进行交互。
不用模块的做法类似这样:

function ItemsViewController($scope) { // make request to server
...
// parse response into Item objects
...
// set Items array on $scope so the view can display it
... }

虽然这个的确可以运行,但是有一些潜在的问题。

  •  如果其他控制器也需要从服务器获取 Items,现在就不得不重新拷贝一份代码。这将使维护成为一种负担,假如我们重新做了架构或其他变更,我们不得不去更新多个地方的 代码。 
  • 由于其他因素,比如服务器端认证,解析的复杂度等等,所以它很难说清这个控制器对 象的职责范围,同时阅读代码也变得更加困难。
  • 为了测试这部分代码,实际上我们需要一台运行的服务器,或者能够用动态方法 monkey 模拟 XMLHttpRequest
    返回模拟数据。不得不运行服务器这使测试工作非常缓慢, 这是一个痛苦的设置,通常给测试环节引入了片状问题。这个 monkey patch路由解决 了速度和片状问题,但是意味这你不得不记住未打补丁和已打补丁的对象,在测试用例,
    和它所带来的额外复杂度,以及强制你指定需要的线上数据格式(只要这个测试变化, 就不得不更新测试用例。)

    使用模块,和从他们那边获取的依赖注入,我们可以写我们的控制器更加简单,类似这 样:

function ShoppingController( $scope, Items) { $scope. items = Items. query( ) ;
}

你可能要问自己“的确,看上去很酷,但是 Items 从哪里来?”上面的代码假设我们已 经定义了 Items 作为一服务。
服务是单例的(单实例)对象,能够执行必要的任务以支持应用的功能。Angular 自带 很多服务,像用于和浏览器地址进行交互的 locationURL route, 以及和服务器交互的 httpAngular 开头,因此,虽然你可以按照你喜欢的来命名,但是避免使用用$开头从而 避免命名冲突是一个很好的想法。

你可以用模块对象的 API 来定义服务。这里有三种函数用于创建不同层次复杂度和能力 的通用服务:

函数:provider(name, Object OR constructor() ) 
定义:一个可配置的、有复杂逻辑的服务。如果你传递了一个对象,那么它应该有一个叫$get 的函数返回这个这个服务的实例。否则的话, Angular 假设你已经产地了一个构造函数,当被调用时,创建这个实例

函数:factory(name, $getFunction() ) 
定义:一个不可配置的、有复杂逻辑的服务。你指定一个函数,当被调用时,返回服务实例。 你可认为是 provider(name,{$get:$getFunction()})

 函数: service(name, constructor() )  
 定义:一个不可配置的、简单逻辑的服务。有点类 似于带构造函数的 providerAngular 掉用它 来创建服务实例。

稍后,我们看下 provider()的配置项,但是让我们讨论之前的 Items 示例使用 factory()
的示例。我们可以像这样写服务:

/ / Create a module to support our shopping views
var shoppingModule = angular.module('ShoppingModule', []);
// Set up the service factory to create our Items interface to the // server-side database shoppingModule.factory('Items',
function() {
var items = {}; items.query = function() {
// In real apps, we'd pull this data from the server... return [
{title: 'Paint pots', description: 'Pots full of paint', price: 3.95}, {title: 'Polka dots', description: 'Dots with polka’, price: 2.95}, {title: 'Pebbles', description: 'Just little rocks', price: 6.95}
]; };
return items; });

查找这些字符串依赖的结果意味着像控制器构造函数注入功能参数是与顺序无关的。因 此不是这样:

 function ShoppingController($scope, Items) {...}

我们可写成这样:

  function ShoppingController(Items, $scope) {...}

正如我们所预料的,它仍然能够运行。
为了能和我们的模板一起运行,我们需要用 ng-app 标识符标注模块名,就像这样:
为了完成这个示例,我们实现了模板的剩余部分如下:

  <html ng-app='ShoppingModule'>
  <body ng-controller="ShoppingController"> 
  <h1>Shop!</h1>
  <div>
  <table>
  <tr ng-repeat=”item in items”>
  </tr>
  <td>{{item.title}}</td> 
  <td>{{item.description}}</td>
  <td>{{item.price | currency}}</td> 
</table> 
</div>

需要多少个模块?

由于服务可能有依赖,因此模块 API 能够让你为依赖定义依赖。
懒虫一个翻译
在大多数应用中,为你的所有代码创建一个单独的模块,把你所依赖的都放到里面,这 将运行的非常好。如果你使用服务或者来自第三方的标识符,它们将带伤自己的模块。由于 你的应用依赖它们,因此你将他们作为你应用模块的依赖。
例如,如果你要包括(假设)SnazzyUIWidgets 和 SuperDataSync 模块,你的应用模块申 明可能像这样:

var appMod = angular.module('app', ['SnazzyUIWidgets', 'SuperDataSync'];

**

用过滤器格式话数据

**
过滤器允许你申明如何将展示给用户的数据转换后插入到你的模板中。过滤器的使用语 法是:

{{ expression | filterName : parameter1 : ...parameterN }}

其中表示是可以是任何 Angular 表达式,filterName 是你想用的过滤器名称,传递给过 滤器的参数用冒号隔开。这些参数可以是任何合法的 Angular 表达式。
Angular 自带几个过滤器,像之前看到的 currency。

 {{12.9 | currency}}

这块代码将显示成如下:
$12.90

我们吧这个申明放在视图层(而不是在控制器或模型),因为$符号在数字前面只有对人 有用,对我们处理数字的逻辑没有用。
Angular 自带的其他过滤器,包括 date,number,uppercase 等等。
在绑定时,过滤器可以用额外的管道标识链接起来。例如,我们可以格式话之前的示例, 通过 number 过滤器删除小数点后的数字,这个过滤器带一个要舍入的小数点位数作为参数。 因此:

  {{12.9 | currency | number:0 }}

将显示:
$13
这个并不限制于绑定的过滤器(就是说不限制于框架内嵌的),书写自定义的过滤器也 很简单。如果我们希望创建一个过滤器,它能够将标题的首字母大写,例如,我们可能这样 做。

var homeModule = angular.module('HomeModule', []); homeModule.filter('titleCase', function() {
var titleCaseFilter = function(input) { var words = input.split(' ');
for (var i = 0; i < words.length; i++) {
words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1);
}
return words.join(' '); };
return titleCaseFilter; });

对应的模板像这样:

<body ng-app='HomeModule' ng-controller="HomeController"> <h1>{{pageHeading | titleCase}}</h1>
</body>

通过控制器赋值给模型变量 pageHeading

function HomeController( $scope) {
$scope.pageHeading = 'behold the majesty of your page title'; }

标题大写过滤器:Behold The Majesty Of Your Page Title

**

通过 Route 和$location 改变视图

**
尽管 AJAX 应用在技术上算是单页面应用(因为它们在第一请求时加载 html 页面,然后 就是用 DOM 更新那些作用域),但是我们通常有多个子页面视图,适当的时候向用户展示 或隐藏。
我们可以使用$route 服务来为我们管理这种场景。Route 可以为一个给定的浏览指向 URL 详细指定 Angular 能够加载和显示一个模板,实例化一个控制器为模板提供上下文。
在应用中可以在$routeProvider 服务上调用函数创建路由作为一个配置块。有点像下面 的伪代码:

var someModule = angular.module('someModule', [...module dependencies...]) someModule.config(function($routeProvider) {
$routeProvider.
when('u rl', {controller:aController, templateUrl:'/path/to/tempate'}). when(...other mappings for your app...).
...
otherwise(...what to do if nothing else matches...);
)};

上面的代码的意思是:当浏览器的 URL 变成指定的 URL 时,Angular 将加载 /path/to/templete 下的模板,同时用 aController 管理这个模板的根元素(假设我们输入了一 个 aController 控制器)。
最后一行的 otherwise()告诉路由如果没有匹配到就走这一段。
让我们来实践一把。我们来构建一个超过 Gmail,Hotmail 等其他应用的 Email 应用。就 叫它 A-Mail.现在我们从简单地开始。我们用第一个视图显示带时期,标题和发件人的 email 信息列表。当你点击一个消息时,它将显示消息体内容的新视图。
由于浏览器的安全限制,如果你尝试运行的代码不在应用中,你需要从一个 web 服务 器上加载它,而不是使用 file://。如果你已经安装了 python,你可以在工作目录通过执行 python –m SimpleHTTPServer 8888 来提供服务。
对于主模板,我们会做一些不同寻常的东西。不是把所有的东西放到第一个加载的页面, 我们只是创建了一个布局模板,我们把所有的视图都放到它里面。我们会把所有的东西持久 话到视图,像这里的菜单。在这种情形下,我会只显示一个应用名称的标题。然后用 ng-view 标识符告诉 Angular 我们的视图应展示在哪里。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值