AngularJs项目实践总结

今年3月接触AngularJs,并且在6月的项目中开始应用,从踩坑到填坑花了不少时间,根据项目中的实际应用情况总结了一些经验,如下:

一.UI控件选择

Angularjs是不缺控件的,Github里现成的控件非常丰富,基本上足以应付一个普通管理系统中常见的控件需求。但是控件的丰富会带来选择的困难。选择控件要满足几个原则:
原则1:符合业务场景
原则2:控件持续更新
原则3:满足性能要求

举几个例子。首先是上传附件的控件。项目中要用到附件上传,谷歌上搜到了三个控件,分别是
https://github.com/leon/angular-upload
https://github.com/danialfarid/ng-file-upload
https://github.com/nervgh/angular-file-upload

因为项目需要兼容IE9,就重点关注了这三个控件对浏览器的兼容性。第一个控件没有任何说明,第二个控件支持IE9,但是前提是要安装flash,第三个控件支持IE8和9,但是只支持部分功能。从浏览器兼容性的角度考虑,最终选择了控件三。

再举一个例子,下拉框控件。html原生的select功能比较单一,并且option的样式很难修改,在前端各个框架所用的下拉框基本上都是重新实现的。Angularjs也不例外。项目中刚开始选用了ui-select2。后来在ui-select2的介绍中看到这句话:
This directive is now obsolete. A new initiative, more active, and 100% angular is available at https://github.com/angular-ui/ui-select.
发现ui-select2已经有3年没有更新了,果断弃坑选用ui-select。

最后谈谈原则3,还是说ui-select吧,它虽然是ui-select2的改进版,但是性能上是存在问题的,根据stackoverflow上的问答,一个ui-select里包含过多选择项或者一个页面包含过多ui-select控件时,性能有明显降低。因为这一点,曾考虑用其他控件替换掉ui-select,不过项目中并不存在大数据量和过多控件的情况,最后仍然保留了它。在满足原则1和2和前提下,只能尽量满足原则3。

一个新项目在开发前,最好能根据需求调研可能用到的UI控件,并尝试写一些demo,尤其对复杂的UI控件。比如ui-grid,有太多的指令和api,花费在阅读文档和官方实例代码的时间也是一笔不小的投入。

二.自定义指令

自定义的指令要加命名空间(前缀),防止全局指令名污染,就像javascript中防止全局变量污染一样。在项目中,某个页面出现了这样的错误:
Error: [$compile:multidir] Multiple directives [refresh, uiSelectChoices] asking for template on: <ui-select-choices repeat="searchRes in searchRes" refresh="searchMedia($select)">

但是新建一个测试用的解决方案,ui-select却是正常的。几经搜索,后来才发现在项目公共的directive里定义了一个叫refresh的指令,它和ui-select的refresh指令重名了。
解决办法很简单,重命名自定义的refresh指令。

三.页面防抖动

在页面初始化的时候,用户可能会先看到 {{ }},然后闪烁一下才出现真正的内容。这是因为Angularjs会在dom加载完后才会解析{{ }}中的内容,在这之前,{{ }}不是Angularjs的插值表达式,而是文本。
解决办法:
1. 在要显示的内容上使用 ng-cloak指令,并且添加如下样式

<style>
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak
{
display: none !important;
}
</style>

2. 使用 ng-bind 替代 {{ }}

四.模块化思维

Angularjs推荐模块化开发,module的本意即是模块化,但是在Angularjs 1.X中,module更多体现出来的是一个“命名空间”的概念,而不是真正意义上的模块化。比如A模块依赖B模块,B模块应该是一个独立的东西,但是被A引用之后,A、B模块中的指令,服务,控制器全部混在了一起,使用指令、控制器时甚至不需要指定是哪个模块中的指令和控制器。在Angularjs2中已经抛弃了module,ES6也引入了javascript底层的模块化。在模块化这方面,不必按照Angularjs 1.X铺好的路走。

在项目中,我们的做法是,整个单页面应用是一个module,每个页面对应一个controller和view,公共的功能写在service中(这点还需要改进,呵呵)。
    controller的定位应该是一个完整且尽可能小的功能模块。比如一个列表页,点击某条记录后弹窗显示该记录的详细信息,那么列表页写一个controller,详细页写另一个controller。controller之间是低耦合的,如果有一定的依赖关系,使用事件广播与接收进行通讯($emit, $broadcast, $on)。controller是不可复用的。
    公共的逻辑写在service中,并且按功能分类,满足单一职责。使用时用依赖注入。避免写成全局的公共方法。

五.懒加载

单页面应用如果体量较小,完全不需要懒加载,把所有脚本打包压缩在一起是更好的解决方案。但是体量较大,加载的脚本又多又大,就需要考虑懒加载的方式了。按需加载文件,而不是一次性加载整个应用所需要的全部文件。懒加载的方式推荐用ocLazyLoad,而不要用requireJs。因为requireJs只能加载文件,无法注册module,controller,directive,而ocLazyLoad不但可以加载文件,也可以完成注册。配合ui-route的路由管理,实现懒加载非常方便。

六.性能

Angularjs采用脏检测的方式来检查对象的变化,与其他框架相比——比如同为MVVM模式的vue.js或者使用了虚拟dom的React——Angularjs的性能并不出众。但是,开发一个单页面应用或者一个管理系统,性能完全不是瓶颈。造成Angularjs性能降低的关键因素往往是添加了太多的watcher,超过2000(经验数值)个watcher时,会明显降低性能。所以提高性能的基本方法就是尽量减少watcher数量,比如使用ng-repeat时限制数组的长度并使用track by,以及一次性数据绑定{{::x}}

七.安全

AngularJs本身不允许不安全的代码,比如controller中定义一段带有html标签的字符串:
$scope.html="<p>text</p>";
在页面上显示的是原始的字符串,而不是一个段落。这是Angularjs自身的防XSS攻击机制。除此另外,作为开发人员,应该注意不要在模版中动态签入用户输入的数据。当然,如果需要显示html编码后的内容,也是可以的,使用$sanitize或$sce服务即可。$sanitize会按照Angularjs自身设置的白名单来净化html,$sce服务包含有$sce.trustAs,$sce.trustAsHtml,$sce.trustAsUrl,$sce.trustAsResourceUrl,$sce.trustAsJs等方法,用于编码可信任的html标签。

总结:Angularjs作为当前流行的MVVM框架,开发管理类的CRUD系统真的太合适了。Javascript中一切皆对象,而在Angularjs中可以说一切皆数据,以数据驱动的方式解决dom的更新,提升不少开发效率。本次项目只是Angularjs的首次应用,只接触到了框架本身的部分功能,希望以后能有更多项目应用Angularjs。另外,Angularjs2已经在9月15日正式发布了,也许某天可以用Angularjs2来开发新的项目~

 
分类: AngularJs

AngularJs项目的更多相关文章

  1. 使用Yeoman快速启动AngularJS项目开发

    本博客停止更新,请访问新个人博客:owenchen.net 前言 博客迁移到了BAE上,http://owenchen.net/,以后的文章会首发在自己的博客上,随后在博客园发布. 很久没有写文章了, ...

  2. 【转】Yeoman自动构建 Angularjs 项目

    Yeoman是什么? Yeoman按照官方说法,它不只是一个工具,还是一个工作流.它其实包括了三个部分yo.grunt.bower,分别用于项目的启动.文件操作.包管理. Yo: Yo是一个项目初始化 ...

  3. angularJs项目实战!02:前端的页面分解与组装

    自从上一篇文章到现在已经有将近一个月的时间,我将精力放在了前端页面分解与组装,和angularjs如何与jquery.bootstrap.D3等一系列其他类库结合使用的经验总结上.由于公司新招了一些员 ...

  4. angularJs项目实战!01:模块划分和目录组织

    近日来我有幸主导了一个典型的web app开发.该项目从产品层次来说是个典型的CRUD应用,故而我毫不犹豫地采用了grunt + boilerplate + angularjs + bootstrap ...

  5. 使用Spring Boot和Gradle创建AngularJS项目

    Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的 ...

  6. AngularJS进阶(三十六)AngularJS项目开发技巧之利用Service&Promise&Resolve解决图片预加载问题(后记)

    AngularJS项目开发技巧之利用Service&Promise&Resolve解决图片预加载问题(后记) 前言 在"AngularJS项目开发技巧之图片预加载" ...

  7. AngularJS进阶(三十一)AngularJS项目开发技巧之获取模态对话框中的组件ID

    AngularJS项目开发技巧之获取模态对话框中的组件ID 需求 出于项目开发需求,需要实现的业务逻辑是:药店端点击查看"已发货""已收货"订单详情时,模块弹出 ...

  8. AngularJS进阶(三十)AngularJS项目开发技巧之图片预加载

    AngularJS项目开发技巧之图片预加载 绪 项目(移动端采用Ionic 框架)开发完毕,测试阶段发现移动APP首页的广告图片(图片由服务器端返回相应url地址)很难加载,主要原因还是网速.如下图左 ...

  9. AngularJS进阶(二十九)AngularJS项目开发技巧之localStorage存储

    AngularJS项目开发技巧之localStorage存储       注: localStorage深度学习 绪 项目开发完毕,测试阶段发现后台管理端二维码生成有问题,问题在于localStora ...

随机推荐

  1. Python 基础【第九篇】运算

    Python的运算符比较通俗和其他语言的几乎没什么区别 一.算数运算符 " + " 加法运算 >>> 1+3 4 加法运算扩展 "增强赋值操作" ...

  2. 关于常用的git命令列表

    我博客园中所写的git内容几乎都是看的蒋鑫老师的<git权威指南>这本书实在太好了. 常用的Git命令. git add  添加到暂存区 git add interactive  交互式添 ...

  3. 最大公约数与欧几里得(Euclid)算法

    ---恢复内容开始--- 记a, b的最大公约数为gcd(a, b).显然, gcd(a,b)=gcd(|a|,|b|). 计算最大公约数的Euclid算法基于下面定理: [GCD递归定理]对于任意非 ...

  4. 面试学到的css布局,细节影响了我的面试成绩

    这几天的面试很纠结,也让我注意到我的前端知识确实不行,从两个小细节总结: 1:body体的居中样式. 这个在IE和非IE Firefox Chrome Opera下面的差别 IE下text-align ...

  5. 预算oracle

    select * from ( )) CODE_VERSION from ( SELECT tb_cube_fc05.pk_entity pk_org,/*主体pk*/ org_orgs.code o ...

  6. SQL Server 负载均衡集群方案之Moebius

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 架构原理(Architecture) 测试环境(Environment) 安装Moebius( ...

  7. [jquery]高级篇--获取div子元素

    参考: http://zhidao.baidu.com/link?url=IfeQQBn1xMLqWvwdkKbQYJ8mC6ciGi_8M1NYkm6iQ-kXBMX2f2ylN-ckzFLiynn ...

  8. C#基础总复习02

    继续更新第二篇: 1:一元运算符:++ -- ++:不管是前加加还是后加加,变量的值最终都会自身加一. 前加加和后加加的区别体现在参与运算的时候,如果是后加加,则首先拿原值参与运算, 运算完成后再自身 ...

  9. ios - cordova 简介

    Cordova 是一个可以让 JS 与原生代码(包括 Android 的 java,iOS 的 Objective-C 等)互相通信的一个库,并且提供了一系列的插件类,比如 JS 直接操作本地数据库的 ...

  10. java新手笔记25 日期格式化

    1.系统时间 package com.yfs.javase; import java.sql.Time; import java.sql.Timestamp; import java.util.Cal ...