ichuan.net

自信打不死的心态活到老

解决 angular.js ngList 指令 bug

ngList 指令

在不使用 ngList 指令时,假如需要实现一个 tags 功能。用户在界面上用逗号分隔输入一些 tag,然后在提交时你需要先在 controller 中人工把该字串分隔成一个数组。类似:$scope.tags = $scope.tags.split(',')

假如在输入控件上使用了 ngList 指令,这件事就不用你操心了,angular 会随时把用户输出分隔后再保存到 $scope 中,所以你使用 $scope.tags 时它就已经是个数组了。

bug

ngList 有个悬而未决的 bug:UI => $scope 没问题,但 $scope => UI 会把变量数组用逗号合并后展示。ngList 保存用户输入到变量时可以指定分隔符(如:ngList="/;/"),但显示变量内容时却永远使用逗号拼接数组为字串,检查源码后发现是硬编码的:

enter image description here

可以看看这个 demo 。在 Test1 的文本区域输入内容,每个一行。可以看到 $scope.names 正确保存为了数组。但在 Test2 中,$scope.names2 原先有内容,显示时却以逗号分隔,而不是 ngList 指定的换行符。

解决方法

这个 bug 由来已久,官方一直不能确定最佳修改方式而留着它。在我们项目中,最简单的解决方法就是修改 angular 源码,但这种方法太暴力。最好能在外部修改。但 angular 源码打包后都隐藏了内部实现,没法 hack ngList 的代码;指令不支持继承,也无法基于 ngList 写个新的指令。

幸好 angular 指令支持 stacking,我们可以写个新的指令,和 ngList 放到同一控件上。由于这样就和 ngList 共享同一 controller,可以在 controller.$formatters 上做手脚,使浏览器显示出我们需要的格式。代码如下:

angular.module('testApp')
  .directive('myList', function () {
    return {
      restrict: 'A',
      require: 'ngModel', 
      link: function postLink(scope, element, attrs, ctrl) {
        var match = /\/(.*)\//.exec(attrs.ngList)
          , separator = match && match[1] || attrs.ngList || ','
          , escapes = {'\\n': '\n'}
          , val;
        separator = escapes[separator] || separator;
        ctrl.$formatters.unshift(function () {
          val = ctrl.$modelValue;
          return angular.isArray(val) && val.join(separator) || undefined;
        });
      }
    };
  });

说明:

  1. 使用 require: 'ngModel'可以使 link 函数获得第四个参数:共享的 controller
  2. ngList 使用的分隔符是正则,所以对换行、tab等这类转义符要特殊处理
  3. $formatters 会被 angular 从后往前调用,所以我们用 unshift 把自己加到第一个,也就最后一个执行了

再来看个例子:Test2 中可以正确以换行显示了;Test3 中换其它分隔符也可行

Comments