美文网首页一起学AngularJS我爱编程
【一起学AngularJS】第四章、Angular模版技术

【一起学AngularJS】第四章、Angular模版技术

作者: 40ab6525bf35 | 来源:发表于2016-03-08 17:25 被阅读349次

    本章将使用AngularJS打造动态网页。同时我们还会测试下控制器代码。
    应用开发中组织代码结构的方法有很多种。对于Angular应用来说,我们鼓励使用MVC的设计方法,它可以很好的解耦代码,让其有各自的侧重关注点。下面,我们将用一点Angular和JS代码来为我们的APP添加一些模型(model)、视图(Views)和控制器(Controllers)控件。
    代码不用自己写~,只需要使用git命令切换出该步骤对应的代码即可。命令如下:

    git checkout -f step-2
    

    我们假设你如第一、二章介绍的那样已经运行了你的网站,所以你只需要刷新浏览器来查看效果。如果还没有运行,请参照第一章或第二章。

    视图和模版

    在Angular中,视图可以被理解为是模型(Model)数据在HTML页面的一种投射。这意味着无论何时,只要模型数据发生变化,视图也应该变化。这一切将由Angular自动完成,它将更新对应的数据绑定点的数据,从而更新视图。
    下面是本章实例中视图对应的模版代码:
    app/index.html

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

    我们把之前静态页面里的展示手机列表的硬编码代码换成了ngRepeat指令和2个Angualr表达式。

    • <li>标签里中属性ng-repeat="phone in phones"对应了Angular中的repeater(循环)指令,这个循环指令告诉Angular为phones列表中的每一个phone对象都创建一个<li>标签。
    • 花括号中的2个表达式({{phone.name}}{{phone.snippet}})将会被Angular替换为表达式对应的值。

    细心的读者可以发现,这个例子中我们新增了一种Angular命令——ng-controller,它把一个控制器即PhoneListCtrl附着在<body>标签中。
    目前来看:
    花括号中的两个变量代表了模型数据,而模型数据由PhoneListCtrl控制器来完成设置或者叫做填充

    注意:我们已经使用ng-app="phonecatApp"加载了一个Angular模块,其中这个模块的名称叫phonecatApp。这个模块将包含我们定义的PhoneListCtrl控制器。

    如图所示为本例Angular对应的域(Scopes)。


    模型和控制器

    本例中的模型数据十分简单(一个简单的手机信息数组),它由PhoneListCtrl控制器负责初始化工作。 定义一个Angular控制器也很简单,它对应了一个JS函数,这个函数只有一个参数 $scope。如下所示:
    app/js/controllers.js

    var phonecatApp = angular.module('phonecatApp', []);
    phonecatApp.controller('PhoneListCtrl', function ($scope) {
      $scope.phones = [
        {'name': 'Nexus S',
         'snippet': 'Fast just got faster with Nexus S.'},
        {'name': 'Motorola XOOM? with Wi-Fi',
         'snippet': 'The Next, Next Generation tablet.'},
        {'name': 'MOTOROLA XOOM?',
         'snippet': 'The Next, Next Generation tablet.'}
      ];
    });
    

    这段代码首先定义了一个Angular应用phonecatApp(还记得大明湖畔的夏雨荷吗?...台词错了,还记得上文HTML中的<html ng-app="phonecatApp">吗?这里的名字可不是随便写的,而是要和JS中新建的Angular应用名称一致),然后为这个应用添加了一个控制器PhoneListCtrl,在这个控制器中,我们在该域下定义了一个数组变量phones,并且为它赋值。
    这个控制器代码看起来是十分简单的,不过它扮演的角色可是很关键的。它提供给我们一个存放模型数据的上下文(就是那个$scope参数),从而允许我们轻松建立模型和视图之间的绑定。Anglar在整个表示层、数据层、逻辑层的关联中做了以下两件事:

    • <body>标签中的ngController命令设置的控制器名称,指导Angular使用JS文件controllers.js中我们为应用创建的名为PhoneListCtrl的控制器。
    • PhoneListCtrl控制器中的代码把对应的手机数据绑定到$scope变量里,这个$scope变量代表了一个,这个域是根域(root scope)的派生。Angular应用定义的时候也会同时创建这个

    域(Scope)

    官方很希望我们能理解的概念,他们认为它非常关键。一个就像胶水一样,把模版、模型和控制器黏在一起。Angular使用和包含在模版、数据模型和控制器内的信息以同步的方式来分离模型和视图(注:这句话没太看懂,原文:Angular uses scopes, along with the information contained in the template, data model, and controller, to keep models and views separate, but in sync.)。任意模型的改变将被投射在视图上,同样的视图的变化也会反射到模型中。
    可以从这里angular scope documentation了解更多关于 Angular域的相关知识。

    测试

    Angular设计路线使用了把视图和控制器分开的方式,这让代码测试也变得简单了。如果我们的把控制器函数定义在了全局范围(就是JS的全局域),我们可以mock一个简单对象来初始化这个控制器。
    test/e2e/scenarios.js

    describe('PhoneListCtrl', function(){
    
      it('should create "phones" model with 3 phones', function() {
        var scope = {},
            ctrl = new PhoneListCtrl(scope);
    
        expect(scope.phones.length).toBe(3);
      });
    
    });
    

    这段测试代码初始话了控制器PhoneListCtrl并且校验了中绑定的3条手机记录数组。这个例子告诉我们在Angular中创建单元测试是十分简单的。软件开发过程中测试是很重要的,所以我们鼓励大家写单元测试。

    测试非全局范围内的控制器

    实际情况中,我们可能不希望在全局命名控件内定义控制器函数。比如,我们本例中就使用了匿名函数为phoneCatApp模块创建了一个控制器。(指这句phonecatApp.controller('PhoneListCtrl', function ($scope) {)。
    Angular为这种情况提供了一个服务即$controller,它将根据名字收集你的控制器。下面是一个使用了$controller服务的测试代码,功能和上一段代码一样:
    test/unit/controllersSpec.js:

    describe('PhoneListCtrl', function(){
    
      beforeEach(module('phonecatApp'));
    
      it('should create "phones" model with 3 phones', inject(function($controller) {
        var scope = {},
            ctrl = $controller('PhoneListCtrl', {$scope:scope});
    
        expect(scope.phones.length).toBe(3);
      }));
    
    });
    

    这个测试的过程包含4个关键步骤:

    • 在每次测试开始之前,我们告诉Angular加载phoneApp应用模块。
    • 我们要求Angular把$controller服务注入到测试函数中。
    • 我们使用$controller服务创建PhoneListCtrl控制器的一个实例。
    • 有了这个实例之后,我们就可以验证三条手机信息记录了。

    编写和运行测试

    Angular开发者在编写测试时很喜欢使用Jasmine开创的行为驱动开发框架(Behavior-driven Development (BBD) framework)。虽然Angular不要求你一定要使用Jasmine的这个框架,该教程中所有的测试用例都是基于Jasmine V1.3。你可以从Jasmine的主页或者Jasmine框架文档获得更多的知识。
    Angular种子项目已经预先配置了使用Karma来运行单元测试,前提是你得确保你已经安装了Karma和响应的依赖控件。没有的话你需要先 npm install
    然后我们输入npm test来运行我们的测试代码。有几点需要说明一下:

    • 首先Karma将自动创建Chrome和Fixfox浏览器的实例。这些都可以在后台自动完成,你无需care。Karma将自动使用创建的浏览器实例来进行测试。
    • 如果你本地只安装了这两个浏览器中的一个,那么对应的你要修改下对应的Karma配置。比如如果你只安装了Chrome浏览器,你可以打开test/karma.conf.js文件,然后做以下修改:
    ...
    browsers: ['Chrome' ],
    ...
    
    • 你将看到类似下面一样的终端输出:
      info: Karma server started at http://localhost:9876/
      info (launcher): Starting  browser "Chrome"
      info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
      Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
    

    这就对了!说明已经通过了。

    • 要想重新运行测试,你只需要修改任意JS源代码。Karma将会监测到,然后重新运行测试。怎么样,Angular是不是很贴心?

    有一点需要注意一下,如果自动测试过程中Karma打开了浏览器窗口,请不要最小化。因为有些操作系统对于最小化的浏览器,会限制其内存分配,这将会是的Karma的整个测试过程很慢。

    实验小能手

    在index.html中新增一个绑定,比如:

    <p>Total number of phones: {{phones.length}}</p>
    

    在控制器中创建一个新的模型变量,如:

    $scope.name = "World";
    

    然后绑定其到index.html模版中:

    <p>Hello, {{name}}!</p>
    

    刷新你的浏览器,看看是不是显示"Hello, World!"。
    在控制器测试代码中添加对本次改动的测试,打开./test/unit/controllersSpec.js添加:

    expect(scope.name).toBe('World');
    

    index.html中定义一个循环器,用它来创建一个简单的表格:

    <table>
      <tr><th>row number</th></tr>
      <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
    </table>
    

    然后,让表格的序号从1开始。

    <table>
      <tr><th>row number</th></tr>
      <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
    </table>
    

    加分作业:尝试使用ng-repeat创建一个8*8的表格。


    为了故意让单元测试出错,从而观察输出,我们可以把代码expect(scope.phones.length).toBe(3) 改成 toBe(4)

    总结

    现在你已经初步了解了如何使用Angular以MVC设计方法构建一个动态网站,以及如何测试它。接下来的一章,我们将为这个网站添加全文搜索功能。

    相关文章

      网友评论

        本文标题:【一起学AngularJS】第四章、Angular模版技术

        本文链接:https://www.haomeiwen.com/subject/hogakttx.html