• angular 使用概术


    框架技术细节说明 must

    该文档详细说明了基于Angular的机制及关键技术。

    目录: - 路由机制 - 通过路由来切分页面模块 - Lazyload机制 - 指令 - 程序bootstrap - 数据绑定 - filters - 何时编写自定义指令 - controller之间的通信 - 依赖注入 - 统一的http请求拦截器 - 根据不同的页面,显示不一样的头部菜单选中 - 如何进行国际化 - 如何进行表单校验 - 动画 - 测试

    路由机制

    路由机制是指页面是如何根据url跳转的。如,访问#/home,该加载哪个页面模板,这个页面模板该哪个控制器来控制。

    在SPA中,路由由前端控制,整个工程只有一个真正的页面。如,我们这只有一个index.html页面或者index.jsp页面。

    <body ng-controller="CtrlApp" class="{{lang}}">
        <div class="wrap">
          <div ui-view="header" class="app-header"></div>
          <div ui-view="body" class="app-body"></div>
        </div>
        <div ui-view="footer"></div>
      </body>

    在我们的工程中,前端路由交由ui-router组件控制。ui-router控制的路由其实是一个状态机,可以由一个状态跳转到另一个状态。每一个状态有url的映射,有views的映射。

    // Home
          $stateProvider.state('home', {
            url: '/home',
            views: {
              'body': {
                templateUrl: baseUrl + 'home/home.tpl.html',
              },
              header: headerConfig,
              footer: footerConfig
            }
          });

    上图,我们定义了一个状态home,对应的url是/home,加载的views有body、header、footer。再参照index.html,我们可以知道home.tpl模板会append到

    中,header、footer也做相应的事情。 也就是说,当我们流转到home这个状态时,我们得到的是这样的页面。

    <body>
            <div class="wrap">
              <div ui-view="header" class="app-header">
                  <!-- header content -->
              </div>
              <div ui-view="body" class="app-body">
                  <!-- home content -->
              </div>
            </div>
            <div ui-view="footer">
                <!-- footer content -->
            </div>
          </body>

    如果需要Controller来控制页面,状态中还可以加上Controller依赖,如下

    // Home
          $stateProvider.state('home', {
            url: '/home',
            views: {
              'body': {
                templateUrl: baseUrl + 'home/home.tpl.html',
                controller: 'CtrlHome',
                resolve: {
                  ctrl: $couchPotatoProvider.resolveDependencies(['home/CtrlHome'])
                }
              },
              header: headerConfig,
              footer: footerConfig
            }
          });

    为什么要加上controller: 'CtrlHome'还要加上$couchPotatoProvider.resolveDependencies(['home/CtrlHome'])这句呢,详见 Lazyload机制

    通过路由来切分页面模块

    有时候页面会比较复杂,我们希望将页面拆分。考虑下如下页面结构 某项目首页UI

    可以看到这个页面看似简单,但其实包含了很多内容:头部、轮播、数字统计、推荐创意、最热创意五个部分。内容很多,但相关性却没有。好,那我们不希望一团糟,所以我们做出改变。我们希望把每一块的模板和业务逻辑拆分开来,这里我们用路由帮我们做这件事。看下下面代码

    // Home
          $stateProvider.state('home', {
            url: '/home',
            abstract: true,
            views: {
              body: {
                templateUrl: baseUrl + 'home/home.tpl.html',
                controller: 'CtrlHome',
                resolve: {
                  ctrl: $couchPotatoProvider.resolveDependencies(['home/CtrlHome'])
                }
              },
              header: headerConfig,
              footer: footerConfig
            }
          })
            .state('home.facade', {
              url: '',
              views: {
                'homeBanner': {
                  templateUrl: baseUrl + 'home/banner/homeBanner.tpl.html',
                  controller: 'CtrlHomeBanner',
                  resolve: {
                    ctrl: $couchPotatoProvider.resolveDependencies(['home/banner/CtrlHomeBanner'])
                  }
                },
                'homeCount': {
                  templateUrl: baseUrl + 'home/count/homeCount.tpl.html',
                  controller: 'CtrlHomeCount',
                  resolve: {
                    ctrl: $couchPotatoProvider.resolveDependencies(['home/count/CtrlHomeCount'])
                  }
                }
            });

    我们可以看到,我们将home这个状态定义为一个抽象的状态abstract,然后定义一个home的子状态叫home.facade,url为'',这样访问#/home这样的url,就会触发home的路由,以及home.facade路由。注意到home.facade路由里定义了homeBannerhomeCount,在home.tpl.html这样定义

    <div>
        <div ui-view="homeBanner"></div>
        <div ui-view="homeCount"></div>
      </div>

    这样,就能把页面拆分成子模块。如果在别的页面也需要引入这些模块,只需要在路由里面定义,然后在父级模板里面声明需要的ui-view,就能够复用了。

    Lazyload机制

    Lazyload机制可以使状态流转的时候去按需加载页面所需的资源,而不是在一开始就加载。AngularJS本身没有实现lazyload,这样的话就导致了webapp的性能受影响,在首次加载的时候就要加载全部的依赖。

    //定义angular模块
      var app = angular.module('app', ['scs.couch-potato', 'ui.router', 'ui.bootstrap', 'ui.bootstrap.tpls', 'chieffancypants.loadingBar']);

    上述代码片段展示了Angular module的加载机制。而在实际应用中,特别是业务系统,一般是业务比较繁杂,功能模块比较多,在这种情况下,angular module的默认机制就不符合我们要求了。

    因此,我们采用requirejs + angular-couch-patato来实现Lazyload。angular-couch-patato负责托管angular的各种注册,包括controller,directive,filter,service等。平时我们写

    angular.module('app').controller(function () {
        //ctrl code here
        });

    为了使用lazyload,现在我们要这样写

    angular.module('app').registerController(function () {
        //ctrl code here
        });

    将部件注册,这样所有的部件都交给couch-patato来管理,在需要的时候(下文会提到如何使用)。加上使用requirejs,app是统一管理的angular module,我们就这样写:

    define(['app'], function(app) {
        app.registerController(function () {
        //ctrl code here
        });
     })

    首先加载依赖app,app是之前定义好的angular module,如var app = angular.module('app', ['scs.couch-potato', 'ui.router', 'ui.bootstrap', 'ui.bootstrap.tpls', 'chieffancypants.loadingBar']);。然后在app上注册controller等。

    上文路由机制部分提到,在注册路由的时候需要调用$couchPotatoProvider.resolveDependencies来实现lazyload。这里设计到couchPatato实现的一个关键,就是利用angular在路由里面的resolve机制,这里的resolve是指,路由在触发之前,必须拿到resolve里面定义的所有值。这样couchPatato就有机会去获取定义的依赖,而在每个依赖里面,会将controller注册到app,然后在路由触发后,定义的controller才得以调用。

    指令

    Directive。angular的一大特性,他可以定义一个标签,或者说赋予dom一定的功能,他也是angular程序里面唯一操作dom的地方。一处定义多处使用,是组件化的一个利器!


    如何定义指令? javascript app.registerDirective('dragable', function() { return { link: function(scope, element, attrs) { //code make it dragable } } }); 上述代码定义了一个指令,这个指令有赋予拖动功能的能力。

    如果在模板里面声明,说,需要这个能力,他就有了这个能力 html <div dragable></div> 这个div可以拖动了!

    没错就是这么神奇,当你还在等待html5规范出来,当你还在等各个浏览器厂商都实现这个规范的时候,angular帮你做了这件事,在技术上解决了这问题。

    angular的zen之一,声明式优于命令式!当你声明了,你就拥有了,这是多么的nice。具体的directive教程请到https://docs.angularjs.org/guide/directive。


    常用的angular的内置指令 - ng-repeat 迭代 - ng-if 控制显示 - ng-model 数据模型绑定

    程序bootstrap

    angular是如何解析指令的,是如何获得控制权的?(因为什么还有入口,相信很多后端选手都没有这个概念,他们从来不需要关心main函数的东西。但web不一样,web是先去加载html,渲染完成之后加载js,然后js才能托管整个app)

    angular的bootstrap分两种,手动初始化和自动初始化。

    自动初始化方法,在html标签定义 ng-app指令,在angular.js加载完成之后,会compile整个html,将ng-app所在的dom作为根来生成动态的html。 html <html ng-app>

    </html>

    手动初始化 javascript angular.element(document).ready(function() { angular.bootstrap(document, ['myApp']); });

    我们是选择在main.js里面手动初始化angular的,这样我们可以做更多的事情,比如在初始化加载一些我们需要的东西,比如权限,用户。

    数据绑定

    在angular里,数据绑定是指双向绑定。双向绑定是指视图改变的时候,对应的数据发生改变;反之亦然。数据绑定在很多mvc框架里面都有,但是angular里的数据绑定是好用的一种。看下我们需要写什么:

    <div ng-controller="Ctrl">{{name}}</div>
    function Ctrl($scope) {
      $scope.name = '炸油条';
    }

    一旦name这个数据发生变化,就直接在视图上反映出来了,中间的过程全部交给angular来处理。

    filters

    filter用来format展现的数据,这样用

    <div class="date">{{date | dateFilter}}</div>

    dateFilter就是日期的过滤器,这样数据源就不用变,我们考虑的只是怎么展现,下面是个自定义filter的写法

    app.registerFilter('PortionFilter', function () {
        return function ( input ) {
          if ( input <= 1 ) {
            return ( input * 100 ).toFixed( 2 ) + '%';
          }
          return input;
        };
      });

    何时编写自定义指令

    • 指令其实可以理解为组件化的思想,当有多个场景需要相同的功能时,应该编写一个指令。
    • 当不得不进行dom操作时,编写指令
    • 当需要扩展dom功能时,编写指令
    • 如果开源的社区有更好的相同功能的指令,使用或者参考之

    controller之间的通信

    有时候,ctrl之间需要通信。我们根据ctrl的不同关系做不同的处理。 当ctrl具有父子或者祖宗关系时,我们采用$broadcast,$emit来进行通信。$broadcast往下传播,$emit反之。 当不具有直接层级关系的时候,我们通过他们共同的父级ctrl来进行交互,因为无论ctrl如何分布,他们最终都有一个共同的root。具体的做法是在父级ctrl定义一个他们共同维护的变量,然后监听这一变量的变化,最终通过$broadcast,$emit来进行通信。父级ctrl等于做了个中间人。

    使用. 来进行通信 https://egghead.io/lessons/angularjs-the-dot

    依赖注入

    angular最好用的特性之一。依赖注入也是声明式优于命令式的一个体现。看下面代码

    function Ctrl($scope, $http) {
      $scope.doSth();
      $http.doSth();
    }

    我们可以看到,我们在想使用$http服务的时候,仅仅需要声明一下,我们就可以获得该引用。编写angular的service非常简单

    app.registerService('util', function(){
        return {
          compress: function A(),
          sthElse: function(){
            console.log(1);
          }
        };
      });

    有了这个能力,我们就可以精简ctrl里面的代码,把可重用的或者冗长的业务代码抽离出来,使ctrl更多的关注业务流程,具体的业务代码维护在service里面。

    统一的http请求拦截器

    angular允许你注册http请求拦截器。有什么用么?有,在想统一对某类请求做统一处理的时候非常有用。如希望后端返回404的情况下,跳转到404页面。这样,就不用在每一个http请求的处理函数都加上这段。还可以针对ie做防缓存处理,及判断,如果是ie低版本浏览器,在每个get请求都加上时间戳,nice!

    play ground:youtube的顶部加载效果!

    根据不同的页面,显示不一样的头部菜单选中

    比如说在首页,命中的是首页,当到列表页的时候,头部命中的应该是列表。 如何实现? 1. 得在相应的controller里面定义一套命中规则,规则规定了哪个路由应该高亮哪个item 2. 定义一个service来管理头部选中,在每个路由级的控制器中调用service来改变高亮

    如何进行国际化

    什么是国际化,国际化是根据不同的语种,来显示不同的文案。思路,我们根据不同的语种来加载不同的资源文件。如,当是中文的时候我们使用zh-cn.json,英文的时候我们使用us-en.json。

    在angular中,filter负责显示。所以我们在filter上做文章。对于静态的文本,我们用一个key来代替如 '语言',我们用 'lang'来替换,在html我们这样写 {{'lang' | translate}}, translate的filter的作用就是从我们之前加载的语言包找到'lang'这个key所对应的文本。

    {
      "lang": "中文哦"
    }

    那对于动态的数据应该怎么做呢,这个就需要跟后端做一定的约定。 - 值列表,我们直接去翻译好的i18n数据 - 枚举型,约定返回具有意义的字符串,如'success','failed'。然后我们在前端做一定的拼接,如{{'type' + type | translate}},然后我们在资源文件里加入typesuccess和type_failed的key,就ok了~ - 这里我们鼓励rd返回具有意义的字符串,而不是0,1,2这种


    另外,国际化不仅仅影响了文本,还可能影响布局,就是说不同的语言环境,页面长得不一样,这种情况下,我们的处理类似换肤,我们在body上绑定一个语言环境的class,针对特殊的需求,我们做不同的处理。

    如果国际化影响了业务逻辑,那建议对模块进行语言环境的逻辑判断。

    如何进行表单校验

    表单校验是个世纪难题,同学们可能都用过各种jquery表单校验的插件,是不是没几个好用的。。

    表单校验为什么难,是因为你不仅仅需要校验,而且你需要不同的校验的展示方式。所以往往会根据具体情况来进行处理,下面说一种比较常见的校验处理方法。

    <div class="material-url form-group">
        <label class="control-label col-md-2">
          url:
        </label>
        <div ng-class="{'has-error':e.materialValidator && formAd.materialUrl.$error.required}">
          <div class="col-md-3">
            <input placeholder="" type="url" class="form-control" name="materialUrl"
            ng-model="e.adSolutionContent.materialUrl" />
          </div>
          <div class="col-md-3 help-block" ng-show="formAd.materialUrl.$error.url">
            {{'AD_CONTENT_NO_URL' | translate}}
          </div>
        </div>
      </div>

    input需要输入合法的url,url不合法的会马上在右侧显示错误提示。这点angular非常轻松的就做到了。。。然后我们可以让提交按钮无效

    <button ng-show="form.$valid" />

    但是变态的需求又来了,按钮永远可以点。。。好,那我们在提交的时候做判断。我们同样通过form.$valid很容易的判断form有没有ok,如果ok,那ok,否则我们要focus到第一个出错的input。那我们就只能悲催的挨个判断input是否合法,然后写个指令控制下focus动作。

    综上表单校验,根据不同的需求做不同的处理。

    动画

    angular推荐的是用css3来进行动画处理,因为angular可以很方便的操纵数据,也就能方便的切换css,再经由css3来动起来。https://docs.angularjs.org/guide/animations ,通过angular的教程可以搭建list的简单动画。

    测试

    angular程序是可以做测试的,这非常神奇,原因是angular将所有的dom操作代码都限制在了指令上,而dom操作代码是很难进行测试的。前面提到的尽量将逻辑处理的代码封装到service,这样就可以对service进行测试,保证代码的质量。

    angular的测试还得力于他的依赖注入,因为这正是跑测试的关键。测试用例会首先创建一个angular-mock,假的壳,然后利用里面的$injector.get()来加载想要测试的service,这个时候已经能获得service的实例了,就可以进行测试。 angular推荐的是jasmine。

  • 相关阅读:
    Python之格式化unix时间戳
    Python简单的验证码生成
    Python字符串常用的一些东西
    PHP explode()函数
    PHP函数number_format()
    PHP简单的计算位数的函数
    python之列表推导式
    python之把列表当做队列使用
    python之列表操作的几个函数
    python之map函数
  • 原文地址:https://www.cnblogs.com/icelin/p/3985684.html
Copyright © 2020-2023  润新知