angularjs 的一些小技巧和实现

angular

关于 angular

angular 的实用和方便性就不说了, 说说要注意的一些地方.

  1. 尽量不要用{{}}`做绑定数据, 使用`ng-bind`. 原因是当浏览器卡的时候页面会显示出`{{item.name}}这样的代码(也可用ng-cloak).

  2. 对于在 onchange 触发的 input, 可以使用ng-model-options="{ debounce: 500 }"使事件触发不会那么频繁, 提高性能(在用于给比较大 table 做 filter 的时候性能提升明显).

  3. 少用$watch. 有一种说法是超过 1000 个 watcher 以后, 整个系统的卡顿就会特别明显, 多使用自定义事件回调机制, 对于指令的作用和功能也更清晰.

  4. 同 3 类似, 对于那些只有展示一次作用, 不会再次改变的数据, 使用 oncebind 命令::, 如ng-bind="::item.name". 在各种 table 和 repeat 里面比较常用. 有个小技巧是: 如果你的 repeat 里面的数据不会变化, 把 repeat 的传入值也改成一次性的ng-repeat="item in ::dataList".

  5. 用好$compile. $compile是 angular 暴露出的内部转换 html 模版到编译函数的功能, 虽然各种ng-if ng-show ng-class已经为动态展示元素提供了很好的帮助, 但是对于一些复杂的页面元素(比如根据后台条件生成不同的验证指令控件), 直接写一个 htmlHelperServer 方法生成正确的 html, 然后使用$element.append($html)), $compile($element.contents())($scope)加入到当前指令中.

  6. service 的使用. 如果你的指令应该传入一个 tree 形数据, 而后台拿到的是 list, 那么这个指令所在的模块的 service 里应该有写好的如何把 list 转换成有效的 tree 数据方法. 这样其他人使用这个指令时可以注入这个 service, 使用已有的转换方法.

scope

  1. angular 有个快速获取某个元素所在 scope 的方法:$(ele).scope(), 结合$.fn.closest可以很方便的拿到上级 scope. 但是并不推荐在代码里使用, 会打乱层级关系和造成代码混乱. 正确的方式是依赖上级指令require: '^parent', 通过linkctrls参数拿到上级的相关方法.

  2. $scope.$apply()有时候在指令里面不得不使用jquery或者其他方法改变绑定到 angular 里的对象属性, 这时候通常会使用$scope.$apply()来把改变同步到界面上. 存在问题是如果这个时候是处于$digest循环中, 那么apply会报错. 推荐做法是封装一个safeApplyrootScope上, 需要使用的地方使用$scope.$root.$safeApply();.

1
2
3
4
5
$rootScope.$safeApply = function () {
if ($rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest') {
$rootScope.$apply();
}
};

directive

  1. 指令可以分成两种类型: 容器指令和展示指令, 前者保存数据和提供操作函数以及界面的组合, 后者只做部分特殊的展示和提供回调. 数据应该跟随指令保存, 而不是controller.

  2. 普通指令应该全部支持A属性, 容器指令支持E属性且replace设置为true, 在写 html 的时候不能出现自定义的闭合标签, 因为部分浏览器对闭合标签支持不友好. 普通指令通过attribute的方式使用, 方便组合以及自定义样式. 特别是那些纯功能性不提供 dom 的指令, 使用transclude: true,replace: true,scope: true来做到低耦合.

  3. 对于组合式指令, 多个指令用于一个元素时, 可以设置这些指令的优先级, 加载顺序是由priority参数定义的. 还可以设置 terminal, 停止低于某个指令的优先级的其他指令运行. 对于这些指令, 都不应该有自己的 scope 作用域, 即只能设置scope: false或者不设置. 如果一个元素上多个指令都有自己的scope, 会报错.

  4. 指令的销毁. 在指令从界面上移除的时候(包括ng-if=false), 会触发$destroy事件, 在指令中设置$scope.$on('$destroy',func), 可以接收到此事件进行一些操作, 比如清除生成的watch, 释放$timeout, 清除jquery动态添加的全局元素等, 回收内存.

  5. 指令的事件广播. 只推荐在全局中使用广播, 因为指令可选择接收或不接收, 属于被动接收全局控制, 而且要定义好广播命名和规范的接收文档. 在容器指令中发送广播会影响全局代码的混乱. 如果需要主动发送全局事件, 请使用requirectrls来调用相关父级指令函数.

  6. 指令的controller. 正常情况下应该只有容器指令和最外层的指令有controller, 里面的下级指令可以依赖该指令拿到外层的相关属性和方法. controllerAs是个很令人迷惑的属性, 简单点说就是把 as 的 name 绑定到 scope 上, 即在controller里面返回的 this 作为一个对象返回, 在scope[name]里面可以取到这个 this 对象. 还可以使用bindToController 选择需要的属性自动绑定到 this 上. 一般情况下controller都只定义一些方法, 供其他指令使用. 也可以做一些初始化的操作, 毕竟controller运行在 link 前面, 不过注意不能使用 dom, 这时候 dom 还没有加载到界面上.

  7. 父级调用下级的方法. 有时候父级作为容器组件, 下级是纯展示组件, 而且还有很多个. 那么父级上有个事件需要改变下级的状态, 怎么处理呢? 通常我们会在下级的 link 里面最后加上以下方法暴露出去所提供的方法.
    当然指令里面需要接收scope``initComplete: '=?', 父级在调用的时候需要写一个接收方法的回调并保存下级的方法, 在需要的时候调用. 这样就实现了展示组件和容器组件的解耦. 当然下级调用父级的就是很简单的 require 然后通过 ctrls 调用了.

1
2
3
4
5
6
if ($scope.initComplete) {
var exports = {
fun: $scope.fun,
};
$scope.initComplete(exports, $scope, $element);
}

controller

  1. 尽量少使用独立的controller, 一般都是作为和指令关联的controller才需要用到. controller 不要用于构建界面, 只有指令才作为界面的元素. controller只定义方法.
  2. 系统中只需要少数几个controller, 一个是runController, 在最开始的入口执行, angular.module(appName, [...commonModules]).directive('myApp', myAppDirective.myApp).run(runCtrl);定义一些自定义的方法和初始化值, 比如我们的highChart的配置就是写在这里. 上面说的$safeApply也是在这里定义的. 还有几个controller是在路由切换的时候需要提供不同的方法加载路由.
  3. controller作为指令的定义是一个字符串的时候, 只是把那个方法里的内容移到以那个字符串命名的controller里面, 在指令的 link 里面第四个参数能拿到那个controller, 其他没有什么区别.

provider

在系统里只推荐使用两种provider, constantservice.

constant

constant定义常量, 比如各种服务地址, 通过生成一个config对象, 供全局使用.

service

service完全可替代factory, 当返回值是对象时, 和factory没有区别(原理是通过 new 调用的方法, 无返回值时可以设定this). 由于是单例模式, 也可以当作全局变量使用, 如注册一个可改变的configService到全局, 则全局可读写并动态配置.
把所有与后台接口交互的方法均定义到service里面, service提供的方法应该与后台同步, 命名相同, 这样就保证后台方法修改的时候, 前端只用修改一个service里的调用. 所有的请求应该通过一个自定义的 ajax 发出去, 即在service里只有参数和配置, 没有 url 和解析.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(function (define) {
'use strict';
define([], function () {
var demoService = function ($q, ajaxService) {
var functionName = function (content, info, onComplete) {
var data = {
CommandName: 'ClassName$FunctionName',
params: {
content: content,
info: info,
},
};
return ajaxService.post(data, onComplete);
};
return {
functionName: functionName,
};
};
return ['$q', 'App.ajaxService', demoService];
});
})(define);

promise 和 onComplete

为了兼容性和减少开发人员入门学习成本, 学习jquery提供两种方式的回调, 在有onComplete参数的时候, 调用onComplete, 同时返回promise对象, 可以在后面自由添加[then|catch]方法. 同时在ajaxService.post里面请求失败触发回调时, 也检测标准 http 错误(500)并记录日志到后台.

decorator

decorator可以注入constant来改变service的行为, 不过目前没有碰到需要使用的情况, 按需使用即可.

module

使用require构建模块化的 angularJs 项目.
在每个模块里声明自己的directiveservice以及modName, 再在系统的总入口里面注册该模块, 这样每个 mod 都是独立的, 不会互相干扰. 在异步加载 mod 的时候也方便操作.
区分模块是创建还是获取是根据第二个参数是否传入有关, 如果传入数组, 则表示是创建模块, 否则是根据模块名获取模块.

1
2
3
4
5
6
7
8
9
//commonMod.js
(function (define, angular) {
'use strict';
define(['common/configMod', 'common/commonService', 'common/commonDirective'], function (configMod, commonService, commonDirective) {
var modName = 'App.commonMod';
angular.module(modName, [configMod]).service('App.commonService', commonService).directive('myDirective', commonDirective.myDirective);
return modName;
});
})(define, angular);
作者

Mosby

发布于

2016-12-20

更新于

2016-12-22

许可协议

评论