zoukankan      html  css  js  c++  java
  • AngularJS 最佳实践

    AngularJS 是一个 Web 应用框架,它实现了前端的 MVC 架构,能让开发人员很方便地实现业务逻辑。

    举个栗子,要做到下面的效果,以前可能需要写一连串的 JavaScript 代码绑定 N 多事件。而使用 AngularJS 框架,一句 JavaScript 都不用写就能实现了,神奇吧?查看演示

        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
        <div data-ng-app>
            单价: <input type="number" min=0 ng-model="price" ng-init="price = 299">
            <br>
            数量: <input type="number" min=0 ng-model="quantity" ng-init="quantity = 1">
            <br>
            总价: {{ quantity * price }}
        </div>
    

      这得益于 AngularJS 中的双向数据绑定特性(Two Way Data-Binding),将 Model 和 View 自动关联了起来,在更复杂的业务场景下,还有代码分离的好处,将 DOM 操作和应用逻辑解耦,非常实用。

         不过没有银弹,和其他框架一样,AngularJS 也有它的局限。CRUD 类型的操作是它所擅长的,想想看以前写过的管理后台,几乎大部分都是从数据库中读取数据,然后呈现在页面上,进行各种增删改查。AngularJS 约定了一套规范(约定优于配置),于是你可以很便捷地操作数据。而在其他方面,例如开发复杂的 Web 游戏,AngularJS 则是无用武之地了。

    一、AngularJS 中的精美特性

    双向绑定

    上面的例子已经说明了,我们可以像 PHP Smarty 模板一样在 HTML 中写表达式,用 {{ 和 }} 包起来。在 AngularJS 里,View 和 Model 是在 Controller 里面绑定的,所以无论你在 View 的表单中修改了内容,还是在 Controller 里通过代码修改了 Model 值,两边都会即时发生变化,同步更新。因为 AngularJS 会监控 (watch) Model 对象的变化,随时反映到 View 中。

    Filter

    Filter 类似 Unix 里面的 | 管道概念,AngularJS 把它搬到了前端。还是举个例子,你们感受一下——

    <div>{{ 9999 | number }}</div>
    <div>{{ 9999+1 | number:2 }}</div>
    <div>{{ 9*9 | currency }}</div>
    <div>{{ 'Hello World' | uppercase }}</div>
    

    输出结果:

    9,999
    10,000.00
    $81.00
    HELLO WORLD
    

      

    二、AngularJS 中的一些“坑”

    由于过去写 JavaScript 的习惯使然,人们很容易掉进一些 AngularJS 的陷阱里。下面的内容假设你已经了解前端 MVC 概念,并对 AngularJS 有了一定经验,初学者读起来可能比较艰深晦涩。

    DOM 操作

    避免使用 jQuery 来操作 DOM,包括增加元素节点,移除元素节点,获取元素内容,隐藏或显示元素。你应该使用 directives 来实现这些动作,有必要的话你还要编写自己的 directives。

    如果你感到很难改变习惯,那么考虑从你的网页中移除 jQuery 吧。真的,AngularJS 中的 $http 服务非常强大,基本可以替代 jQuery 的 ajax 函数,而且 AngularJS 内嵌了 jQLite —— 它内部实现的一个 jQuery 子集,包含了常用的 jQuery DOM 操作方法,事件绑定等等。但这并不是说用了AngularJS 就不能用 jQuery 了。如果你的网页有载入 jQuery 那么 AngularJS 会优先采用你的 jQuery,否则它会 fall back 到 jQLite。

    需要自己编写 directives 的情况通常是当你使用了第三方的 jQuery 插件。因为插件在 AngularJS 之外对表单值进行更改,并不能即时反应到 Model 中。例如我们用得比较多的 jQueryUI datepicker 插件,当你选中一个日期后,插件会将日期字符串填到 input 输入框中。View 改变了,却并没有更新 Model,因为$('.datepicker').datepicker(); 这段代码不属于 AngularJS 的管理范围。我们需要编写一个directive 来让 DOM 的改变即时更新到 Model 里。

    var directives = angular.module('directives', []);
     
    directives.directive('datepicker', function() {
       return function(scope, element, attrs) {
           element.datepicker({
               inline: true,
               dateFormat: 'dd.mm.yy',
               onSelect: function(dateText) {
                   var modelPath = $(this).attr('ng-model');
                   putObject(modelPath, scope, dateText);
                   scope.$apply();
               }
           });
       }
    });
    

      然后在 HTML 中引入这个 direcitve

    <input type="text" datepicker ng-model="myObject.myDateValue" />
    

      

    说白了 directive 就是在 HTML 里写自定义的标签属性,达到插件的作用。这种声明式的语法扩展了 HTML。

    需要说明的是,有一个 AngularUI 项目提供了大量的 directive 给我们使用,包括 Bootstrap 框架中的插件以及基于 jQuery 的其他很热门的 UI 组件。我之前说过 AngularJS 的社区很活跃嘛,生态系统健全。

    ngOption 中的 value

    这是个大坑。如果你去查看 ngOption 生成的 <select> 中的 <option> 的选项值(每个 <option value="xxx"> 的 value 部分),那绝对是枉费心机。因为这里的值永远都会是 AngularJS 内部元素的索引,并不是你所指定的表单选项值。

    还是要转变观念,AngularJS 已经不再用表单进行数据交互了,而是用 Model。使用 $http 来提交 Model,在 php 中则使用 file_get_contents('php://input') 来获取前端提交的数据。

    {{ }} 的问题

    在页面初始化的时候,用户可能会看到 {{ }},然后闪烁一下才出现真正的内容。
    解决办法:

    1. 使用 ng-cloak directive 来隐藏它
    2. 使用 ng-bind 替代 {{ }}

    将界面与业务逻辑分离

    Controller 不应该直接引用 DOM,而应该控制 view 的行为。例如“如果用户操作了 X,应该发生什么事情”,“我从哪里可以获得 X?”

    Service 在大部分情况下也不应该直接引用 DOM,它应该是一个单例(singletons),独立于界面,与 view 的逻辑无关。它的角色只是“做 X 操作”。

    DOM 操作应该放在 directives 里面。

    尽量复用已有功能

    你所写的功能很可能 AngularJS 已经实现了,有一些代码是可以抽象出来复用的,使用更 Angular 的方式。总之就是很多 jQuery 的繁琐代码可以被替代。

    1. ng-repeat

    ng-repeat 很有用。当 Ajax 从服务器获得数据后,我们经常使用 jQuery (比如上面讲过的例子) 向某些 HTML 容器节点中添加更多的元素,这在 AngularJS 里是不好的做法。有了 ng-repeat 一切就变得非常简单了。在你的 $scope 中定义一个数组 (model) 来保存从服务器拉取的数据,然后使用 ng-repeat 将它与 DOM 绑定即可。下面的例子初始化定义了 friends 这个 model

    <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
        I have {{friends.length}} friends. They are:
        <ul>
            <li ng-repeat="friend in friends">
                [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
            </li>
        </ul>
    </div>
    

      显示结果

    I have 2 friends. They are:
      [1] John who is 25 years old.
      [2] Mary who is 28 years old.
    

      

    2. ng-show

    ng-show 也很有用。使用 jQuery 来根据条件控制界面元素的显示隐藏,这很常见。但是 Angular 有更好的方式来做到这一点。ng-show (以及 ng-hide) 可以根据布尔表达式来决定隐藏和显示。在 $scope 中定义一个变量:

    <div ng-show="!loggedIn">
        点击 <a href="#/login">这里</a> 登录
    </div>
    

      

    类似的内置 directives 还有 ng-disabled, ng-switch 等等,用于条件控制,语法简洁,都很强大。

    3. ng-class

    ng-class 用于条件性地给元素添加 class,以前我们也经常用 jQuery 来实现。Angular 中的 ng-class 当然更好用了,例子:

    <div ng-class="{ errorClass: isError, warningClass: isWarning, okClass: !isError && !isWarning }">...</div>
    

      

    在这里 ng-class 接受一个 object 对象,key 为 CSS class 名,值为 $scope 变量控制的条件表达式,其他类似的内置 directives 还有 ng-class-even 和 ng-class-odd,很实用。

    $watch 和 $apply

    AngularJS 的双向数据绑定是最令人兴奋的特性了,然而它也不是全能的魔法,在某些情况下你需要做一些小小的修正。

    当你使用 ng-model, ng-repeat 等等来绑定一个元素的值时, AngularJS 为那个值创建了一个 $watch,只要这个值在 AngularJS 的范围内有任何改变,所有的地方都会同步更新。而你在写自定义的 directive 时,你需要定义你自己的 $watch 来实现这种自动同步。

    有时候你在代码中改变了 model 的值,view 却没有更新,这在自定义事件绑定中经常遇到。这时你就需要手动调用 scope.$apply() 来触发界面更新。上面 datepicker 的例子已经说明了这一点。第三方插件可能会有 call back,我们也可以把回调函数写成匿名函数作为参数传入$apply()中。

    将 ng-repeat 和其他 directives 结合起来

    ng-repeat 很有用,不过它和 DOM 绑定了,很难在同一个元素上使用其他 directives (比如 ng-show, ng-controller 等等)。

    如果你想对整个循环使用某个 directive,你可以在 repeat 外再包一层父元素把 directive 写在那儿;如果你想对循环内部的每一个元素使用某个 directive,那么把它放到 ng-repeat 的一个子节点上即可。

    Scope

    Scope 在 templates 模板中应该是 read-only 的,而在 controller 里应该是 write-only 的。Scope 的目的是引用 model,而不是成为 model。model 就是我们定义的 JavaScript 对象。

    $rootScope 是可以用的,不过很可能被滥用

    Scopes 在 AngularJS 中形成一定的层级关系,树状结构必然有一个根节点。通常我们用不到它,因为几乎每个 view 都有一个 controller 以及相对应的自己的 scope。

    但偶尔有一些数据我们希望全局应用在整个 app 中,这时我们可以将数据注入 $rootScope。因为其他 scope 都会继承 root scope,所以那些注入的数据对于 ng-show 这类 directive 都是可用的,就像是在本地 $scope 中的变量一样。

    当然,全局变量是邪恶的,你必须很小心地使用 $rootScope。特别是不要用于代码,而仅仅用于注入数据。如果你非常希望在 $rootScope 写一个函数,那最好把它写到 service 里,这样只有用到的时候它才会被注入,测试起来也方便些。

    相反,如果一个函数的功能仅仅是存储和返回一些数据,就不要把它创建成一个 service。

    三、AngularJS 项目的目录结构

    怎样组织代码文件和目录?这恐怕是初学者一开始就会遇到的问题。AngularJS 应用开发的官方入门项目angular-seed,其文件结构是这样的:

    • css/
    • img/
    • js/
      • app.js
      • controllers.js
      • directives.js
      • filters.js
      • services.js
    • lib/
    • partials/

    这种结构对于一个简单的单页 app 来说是可行的,只是一旦代码中存在多个 Controller 或者 Service,就很难找到想要寻找的对象了。我们可以对文件按照业务逻辑进行拆分,就像下面这样:

    • controllers/
      • LoginController.js
      • RegistrationController.js
      • ProductDetailController.js
      • SearchResultsController.js
    • directives.js
    • filters.js
    • models/
      • CartModel.js
      • ProductModel.js
      • SearchResultsModel.js
      • UserModel.js
    • services/
      • CartService.js
      • UserService.js
      • ProductService.js

    这种结构把不同的业务功能拆分为独立的文件,条理清晰,但是仍有一定的局限性。最大的问题是一个业务功能的代码分布在controllers, models, servers 三个不同目录下,要从中挑出正确的文件,建立起代码关联,还是有些麻烦。按照功能进行模块化划分目录结构,应该要更为合理一些:

    • cart/
      • CartModel.js
      • CartService.js
    • common/
      • directives.js
      • filters.js
    • product/
      • search/
        • SearchResultsController.js
        • SearchResultsModel.js
      • ProductDetailController.js
      • ProductModel.js
      • ProductService.js
    • user/
      • LoginController.js
      • RegistrationController.js
      • UserModel.js
      • UserService.js

    这样也是适合 RequireJS 等模块加载器的自然直观的代码组织方式。

     http://tutorialzine.com/2013/08/learn-angularjs-5-examples/

  • 相关阅读:
    SpringMVC的DispatcherServlet加载过程
    SpringMVC-HandlerMapping和HandlerAdapter
    FactoryBean简介
    Spring IOC过程
    redis基本数据类型和对应的底层数据结构
    工厂模式
    springmvc工作原理
    Java虚拟机系列-Java类加载机制
    2019年10月21日 数据库sql只取最新一条的数据
    洛谷 P2606 [ZJOI2010]排列计数
  • 原文地址:https://www.cnblogs.com/jifeng/p/3474757.html
Copyright © 2011-2022 走看看