Angular $compile 导致 Datepicker 弹出框出现轻微异常

Angular $compile causes Datepicker popup box to misbehave slightly

更新 1:添加更多详细信息。
更新 2:添加了 plunker 代码以重现问题。请参阅下面的 link。
更新 3:我在 github 上收到了 angular 团队的回复。检查一下 here.
更新 4:根据 AngularJS 团队的要求,在 github 上发布更新。此外,我之前添加的建议解决方案结果证明它正在创建一个新的视觉问题。请参阅下面的详细信息。
更新 5:从 Angular 团队获得另一个反馈。我认为我们已经接近找到解决方案的 80%。

我在我的项目中实现了Angular UI datepicker,过了一会儿,我注意到当我打开弹出框,点击月份更改为另一个月份时,另一个弹出框window 显示在现有弹出窗口的顶部。请参阅下面的快照以获取更多详细信息:

最初,当我点击月份时,当前弹出窗口应该消失,另一个弹出窗口应该显示 select 新的月份。

但是,当我在月份(下面以黄色突出显示)上单击两次时,弹出窗口将消失,并且可以正常工作。但是,在我 select 约会之后,问题又会回来。

我正在使用来自 angular ui datepicker 官方演示网站的相同示例代码。在下面找到相关网站和plunker代码:

http://angular-ui.github.io/bootstrap/versioned-docs/1.3.3/#/datepickerPopup http://plnkr.co/edit/lEgJ9eC9SzBWsgVhhQkq?p=preview

我的代码与上面的 plunker 示例中的代码完全相同。唯一的区别是我使用 $compile 服务来动态添加 required 字段验证。

经过广泛的故障排除后,发现 $compile() 服务导致了此行为。我做了一些研究,发现 $compile 服务也会导致下拉列表或 select 元素中出现重复项。我使用了建议的解决方法并且有效。请参阅下面的代码:

$compile(child)(elmScope, function (clone) {
    angular.element(child).after(clone);     
    angular.element(child).remove();
});

我使用 $compile 的原因是使用这种方法从数据库向元素添加动态验证规则 here

我得到后reply from angular team on github, I found that they suggested this fix:

.directive('checkIfRequired', ['$compile', function ($compile) {
    return {
      priority: 1000,
      terminal: true,
        /*require: '?ngModel',*/
        //JIRA: NE-2535 - inject the current 'ngForm' controller with this directive
        //      This is required to add automatice error message when the Form Field is invalid.
        require: '?^form',
      link: function (scope, el, attrs, ngForm) {
        el.removeAttr('check-if-required');
        el.attr('ng-required', 'true');
        $compile(el, 1000)(scope);
      }
    };
}]);

当我尝试应用建议的修复程序时,出现了大量错误。似乎使用 terminal=true 时 'ng-init' 下的内部元素中的所有代码都没有执行。我注意到许多范围变量变成 "undefined".

这是final update on github试图找到解决办法。请参阅下面的代码:

app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
  return {
    priority: 2000,
    terminal: true,
    /*link: function (scope, el, attrs) {
      el.removeAttr('check-if-required');
      var children = $(':input', el);
      children.each(function(key, child) {
        if (child && child.id === 'test_me') {
            angular.element(child).attr('ng-required', 'true');
                }
      });
      $compile(children)(scope);
    },*/
    compile: function (el, attrs) {
        el.removeAttr('check-if-required');
        var children = $(':input', el);
        children.each(function(key, child) {
            if (child && child.id === 'test_me') {
                angular.element(child).attr('ng-required', 'true');
                    }
            });
        var compiled = $compile(children, null, 2000);
        return function( scope ) {
            compiled( scope );
        };
    }
  };
}]);

感谢您帮助我在项目中以正确的方式应用修复程序。 See the related parts of the code here.


我之前发布过一个解决方案,但后来我删除了它。后来我注意到它导致了一个有趣的叠加效应,比我之前报告的还要糟糕。

这里是 github 的更新,包含完整的细节:

https://github.com/angular/angular.js/issues/15956#issuecomment-300324812

收到 AngularJS 团队的回复后会继续更新。

请耐心等待我找到永久解决方案。


旧的建议解决方案:

为了避免这个问题一定不能使用$compile服务,或者如果你必须使用这个代码示例来解决这个问题:

$compile(child)(elmScope, function (clone) {
    angular.element(child).after(clone);     
    angular.element(child).remove();
});

希望这对面临同样问题的其他人有用。

这是为了进行修正。之前添加的解决方案没有达到预期效果。

有关完整的详细信息,请查看 github 上的更新:

https://github.com/angular/angular.js/issues/15956#issuecomment-300324812

现在我可以提出一种不同的方法来使用动态验证规则,这些规则存储在数据库中并以 AngularJS 形式加载。

参考此处的代码部分作为此建议解决方案的基础:

Angular dynamic required validation of group of fields with field highlight if invalid

不要在指令 check-if-required 中使用 $compile,而是为字段 child.id 创建一个新的 scope 变量,如下所示:

if (scope.isFieldRequired(child.id)) {
    //angular.element(child).attr('ng-required', "true");
    //$compile(child)(elmScope);
    scope.RootController.ngRequired[child.id] = true;
} else {
    scope.RootController.ngRequired[child.id] = false;
}

定义元素时,确保它类似于以下内容:

<input id="customer_id" name="customer_id" ng-model="customer_id" ng-required="RootController.ngRequired.customer_id"/>

基本上,所有验证规则都必须链接到必须在根 Angular 控制器中定义的范围变量 RootController'. For simplicity, the required validation will be underRootController.ngRequired`。

有必要在最外层控制器使用controller as符号定义RootController。否则,上述解决方案将不起作用。以下是根控制器的示例元素定义:

<body ng-app="myApp" ng-controller="formMainController as RootController">
...
</body>

如果Angular团队对使用$compile发现的问题没有得到满意的答复,我将改用上述方法。我几乎可以肯定上述方法会奏效。

如果您有任何反馈,请提出。

塔雷克

这是我从 github 的 AngularJS 团队得到的另一个解决方案。 Click here to see the answer at github.

参考此处的代码部分作为此建议解决方案的基础:

Angular dynamic required validation of group of fields with field highlight if invalid

基本上,您可以使用以下代码示例来避免在指令 link 函数中使用 $compile 时出现双重编译问题:

app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
    return {
        priority: 2000,
        terminal: true,
        link: function (scope, el, attrs) {
            el.removeAttr('check-if-required');
            $(':input', el).each(function(key, child) {
                if (child && child.id === 'test_me') {
                    angular.element(child).attr('ng-required', 'true');
                }
            });
            $compile(el, null, 2000)(scope);
        }
    };
}]);

如果您有一个循环遍历与指令相关的父元素的子元素,则这是必需的 check-if-required