angularjs 的一些小技巧和实现
angular
关于 angular
angular 的实用和方便性就不说了, 说说要注意的一些地方.
尽量不要用
{{}}`做绑定数据, 使用`ng-bind`. 原因是当浏览器卡的时候页面会显示出`{{item.name}}
这样的代码(也可用ng-cloak
).对于在 onchange 触发的 input, 可以使用
ng-model-options="{ debounce: 500 }"
使事件触发不会那么频繁, 提高性能(在用于给比较大 table 做 filter 的时候性能提升明显).少用
$watch
. 有一种说法是超过 1000 个 watcher 以后, 整个系统的卡顿就会特别明显, 多使用自定义事件回调机制, 对于指令的作用和功能也更清晰.同 3 类似, 对于那些只有展示一次作用, 不会再次改变的数据, 使用 oncebind 命令
::
, 如ng-bind="::item.name"
. 在各种 table 和 repeat 里面比较常用. 有个小技巧是: 如果你的 repeat 里面的数据不会变化, 把 repeat 的传入值也改成一次性的ng-repeat="item in ::dataList"
.用好
$compile
.$compile
是 angular 暴露出的内部转换 html 模版到编译函数的功能, 虽然各种ng-if ng-show ng-class
已经为动态展示元素提供了很好的帮助, 但是对于一些复杂的页面元素(比如根据后台条件生成不同的验证指令控件), 直接写一个 htmlHelperServer 方法生成正确的 html, 然后使用$element.append($html)), $compile($element.contents())($scope)
加入到当前指令中.service 的使用. 如果你的指令应该传入一个 tree 形数据, 而后台拿到的是 list, 那么这个指令所在的模块的 service 里应该有写好的如何把 list 转换成有效的 tree 数据方法. 这样其他人使用这个指令时可以注入这个 service, 使用已有的转换方法.
scope
angular 有个快速获取某个元素所在 scope 的方法:
$(ele).scope()
, 结合$.fn.closest
可以很方便的拿到上级 scope. 但是并不推荐在代码里使用, 会打乱层级关系和造成代码混乱. 正确的方式是依赖上级指令require: '^parent'
, 通过link
的ctrls
参数拿到上级的相关方法.$scope.$apply()
有时候在指令里面不得不使用jquery
或者其他方法改变绑定到 angular 里的对象属性, 这时候通常会使用$scope.$apply()
来把改变同步到界面上. 存在问题是如果这个时候是处于$digest
循环中, 那么apply
会报错. 推荐做法是封装一个safeApply
在rootScope
上, 需要使用的地方使用$scope.$root.$safeApply();
.
1 | $rootScope.$safeApply = function () { |
directive
指令可以分成两种类型: 容器指令和展示指令, 前者保存数据和提供操作函数以及界面的组合, 后者只做部分特殊的展示和提供回调. 数据应该跟随指令保存, 而不是
controller
.普通指令应该全部支持
A
属性, 容器指令支持E
属性且replace
设置为true
, 在写 html 的时候不能出现自定义的闭合标签, 因为部分浏览器对闭合标签支持不友好. 普通指令通过attribute
的方式使用, 方便组合以及自定义样式. 特别是那些纯功能性不提供 dom 的指令, 使用transclude: true,replace: true,scope: true
来做到低耦合.对于组合式指令, 多个指令用于一个元素时, 可以设置这些指令的优先级, 加载顺序是由
priority
参数定义的. 还可以设置 terminal, 停止低于某个指令的优先级的其他指令运行. 对于这些指令, 都不应该有自己的 scope 作用域, 即只能设置scope: false
或者不设置. 如果一个元素上多个指令都有自己的scope
, 会报错.指令的销毁. 在指令从界面上移除的时候(包括
ng-if=false
), 会触发$destroy
事件, 在指令中设置$scope.$on('$destroy',func)
, 可以接收到此事件进行一些操作, 比如清除生成的watch
, 释放$timeout
, 清除jquery
动态添加的全局元素等, 回收内存.指令的事件广播. 只推荐在全局中使用广播, 因为指令可选择接收或不接收, 属于被动接收全局控制, 而且要定义好广播命名和规范的接收文档. 在容器指令中发送广播会影响全局代码的混乱. 如果需要主动发送全局事件, 请使用
require
和ctrls
来调用相关父级指令函数.指令的
controller
. 正常情况下应该只有容器指令和最外层的指令有controller
, 里面的下级指令可以依赖该指令拿到外层的相关属性和方法.controllerAs
是个很令人迷惑的属性, 简单点说就是把 as 的 name 绑定到 scope 上, 即在controller
里面返回的 this 作为一个对象返回, 在scope[name]
里面可以取到这个 this 对象. 还可以使用bindToController
选择需要的属性自动绑定到 this 上. 一般情况下controller
都只定义一些方法, 供其他指令使用. 也可以做一些初始化的操作, 毕竟controller
运行在 link 前面, 不过注意不能使用 dom, 这时候 dom 还没有加载到界面上.父级调用下级的方法. 有时候父级作为容器组件, 下级是纯展示组件, 而且还有很多个. 那么父级上有个事件需要改变下级的状态, 怎么处理呢? 通常我们会在下级的 link 里面最后加上以下方法暴露出去所提供的方法.
当然指令里面需要接收scope``initComplete: '=?'
, 父级在调用的时候需要写一个接收方法的回调并保存下级的方法, 在需要的时候调用. 这样就实现了展示组件和容器组件的解耦. 当然下级调用父级的就是很简单的 require 然后通过 ctrls 调用了.
1 | if ($scope.initComplete) { |
controller
- 尽量少使用独立的
controller
, 一般都是作为和指令关联的controller
才需要用到. controller 不要用于构建界面, 只有指令才作为界面的元素.controller
只定义方法. - 系统中只需要少数几个
controller
, 一个是runController
, 在最开始的入口执行,angular.module(appName, [...commonModules]).directive('myApp', myAppDirective.myApp).run(runCtrl);
定义一些自定义的方法和初始化值, 比如我们的highChart
的配置就是写在这里. 上面说的$safeApply
也是在这里定义的. 还有几个controller
是在路由切换的时候需要提供不同的方法加载路由. controller
作为指令的定义是一个字符串的时候, 只是把那个方法里的内容移到以那个字符串命名的controller
里面, 在指令的 link 里面第四个参数能拿到那个controller
, 其他没有什么区别.
provider
在系统里只推荐使用两种provider
, constant
和service
.
constant
constant
定义常量, 比如各种服务地址, 通过生成一个config
对象, 供全局使用.
service
service
完全可替代factory
, 当返回值是对象时, 和factory
没有区别(原理是通过 new 调用的方法, 无返回值时可以设定this
). 由于是单例模式, 也可以当作全局变量使用, 如注册一个可改变的configService
到全局, 则全局可读写并动态配置.
把所有与后台接口交互的方法均定义到service
里面, service
提供的方法应该与后台同步, 命名相同, 这样就保证后台方法修改的时候, 前端只用修改一个service
里的调用. 所有的请求应该通过一个自定义的 ajax 发出去, 即在service
里只有参数和配置, 没有 url 和解析.
1 | (function (define) { |
promise 和 onComplete
为了兼容性和减少开发人员入门学习成本, 学习jquery
提供两种方式的回调, 在有onComplete
参数的时候, 调用onComplete
, 同时返回promise
对象, 可以在后面自由添加[then|catch]
方法. 同时在ajaxService.post
里面请求失败触发回调时, 也检测标准 http 错误(500)并记录日志到后台.
decorator
decorator
可以注入constant
来改变service
的行为, 不过目前没有碰到需要使用的情况, 按需使用即可.
module
使用require
构建模块化的 angularJs 项目.
在每个模块里声明自己的directive
和service
以及modName
, 再在系统的总入口里面注册该模块, 这样每个 mod 都是独立的, 不会互相干扰. 在异步加载 mod 的时候也方便操作.
区分模块是创建还是获取是根据第二个参数是否传入有关, 如果传入数组, 则表示是创建模块, 否则是根据模块名获取模块.
1 | //commonMod.js |
angularjs 的一些小技巧和实现