材料升级后AngularJS自定义指令双向绑定中断

Posted

技术标签:

【中文标题】材料升级后AngularJS自定义指令双向绑定中断【英文标题】:AngularJS custom directive bidirectional binding broken after material upgrade 【发布时间】:2017-08-01 07:47:38 【问题描述】:

我写了一个在 Angular 1.6.1 和 material 1.1.1 下工作的小指令。 这是一个简单的锁定/解锁按钮图标。

我不得不将材料更新到 1.1.3(用于日期选择器),但此后该指令不再起作用。

我不明白为什么材料更新会这样做...... 下面的 plunker 可以工作,但是如果你将材料版本更改为 1.1.2,它就会停止工作。

http://plnkr.co/edit/ZamxN3WTXaOl5cTv4aWI?p=info

index.html:

<html lang="en" >
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.3/angular-material.css">
  <link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-animate.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-aria.js"></script>

  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.1/angular-material.js"></script>
  <!-- <script src="//cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.1/angular-material.js"></script> -->

  <script src="script.js"></script>

</head>
<body ng-app="app" ng-controller="controller as ctrl">
  <ju-lock ng-model="ctrl.lock"></ju-lock>|ctrl.lock|
</body>
</html>

script.js:

  angular
    .module('app', ['ngMaterial'])
    .directive('juLock', function()
        return 
            restrict: 'E',
            scope: 
                bindModel: '=ngModel'
            ,
            template: 
                '<md-button class="md-icon-button">'+
                '<md-icon class="material-icons">lock_open</md-icon>'+
                '</md-button>|bindModel',
            link: function(scope, element, attributes)
                element.on('click', function (ev) 
                    scope.bindModel = !scope.bindModel;
                );
                scope.$watch('bindModel', function()
                    angular.element(element[0].querySelector('.material-icons')).text(scope.bindModel ? 'lock' : 'lock_open');
                );
            
        ;
    )
    .controller('controller', function()
        var vm = this;
        vm.lock=true;
    );

在询问堆栈社区之前,我已经尽可能多地进行了调查,是否有人对此有所了解?

【问题讨论】:

避免在自定义指令中使用ng-model 作为属性。如果这样做,请不要对其使用隔离范围双向绑定。使用该属性实例化的ngModelController API。 【参考方案1】:

避免在自定义指令中使用ng-model 作为属性。如果这样做,请不要使用隔离范围双向绑定。使用该属性实例化的ngModelController API。

主要问题是jqLite click handler需要用scope().$apply()通知AngularJS框架范围的变化:

element.on('click', function (ev) 
    scope.bindModel = !scope.bindModel;
    //USE $apply
    scope.$apply();
);

Angular 通过提供自己的事件处理循环来修改正常的 javascript 流程。这将 JavaScript 拆分为经典和 Angular 执行上下文。只有在 Angular 执行上下文中应用的操作才能受益于 Angular 数据绑定、异常处理、属性监视等……您使用 $apply() 从 JavaScript 进入 Angular 执行上下文。

请记住,在大多数地方(控制器、服务)$apply 已经被处理事件的指令调用。 仅在实现自定义事件回调或使用第三方库回调时才需要显式调用 $apply

——AngularJS Developer Guide - Integration with the browser event loop

DEMO on PLNKR


也不是操纵 DOM 来更改锁定和解锁图标。它可以通过ng-show 和ng-hide 指令来完成:

app.directive('juLock', function()
    return 
        restrict: 'E',
        scope: 
            bindModel: '=myModel'
        ,
        template: 
            '<md-button class="md-icon-button">'+
            '<md-icon ng-show="bindModel" class="material-icons">lock</md-icon>'+
            '<md-icon ng-hide="bindModel" class="material-icons">lock_open</md-icon>'+
            '</md-button>|bindModel',
        link: function(scope, element, attributes)
            element.on('click', function (ev) 
                scope.bindModel = !scope.bindModel;
                //USE $apply
                scope.$apply();
            );
            /*
            scope.$watch('bindModel', function()
                angular.element(element[0].querySelector('.material-icons')).text(scope.bindModel ? 'lock' : 'lock_open');
            );
            */
        
    ;
)

【讨论】:

@glorfindel 你不需要使用 Wayback 机器。图片已移至docs.angularjs.org/img/guide/concepts-runtime.png 感谢您发现这一点,我的机器人无法检测到此类情况。我的浏览器甚至无法加载code.angularjs.org/1.1.5/docs/img/guide/concepts-runtime.png,而且我自己从未做过角度项目。【参考方案2】:

我不确定版本之间的确切问题,但请查看fiddle,此代码适用于两个版本。

关键是使用ng-click,它来自angularJs,而不是依赖element.on(),如果你注入jQuery或者你不注入,它可能会有所不同。另外这种方式更具声明性

我所做的唯一更改是在您的指令中

.directive('juLock', function()
        return 
            restrict: 'E',
            scope: 
                bindModel: '=ngModel'
            ,
            template: 
                '<md-button ng-click="bindModel = !bindModel" class="md-icon-button">'+
                  '<md-icon class="material-icons">lock_text</md-icon>'+
                '</md-button>|bindModel',
            link: function(scope, element, attributes)
              scope.lock_text = '';
                scope.$watch('bindModel', function()
                    scope.lock_text = scope.bindModel ? 'lock' : 'lock_open';
                );
            
        ;
    )

我还删除了您手表中的 dom 检查,并且我以 Angular 样式进行了更多操作

【讨论】:

以上是关于材料升级后AngularJS自定义指令双向绑定中断的主要内容,如果未能解决你的问题,请参考以下文章

玩转angularJs——通过自定义ng-model,不仅仅只是input可以有双向绑定

angularjs1.x版本,父子组件之间的双向绑定

Angularjs-指令

AngularJS指令进阶 -- ngModelController详解

浅谈AngularJS中的指令和指令间的相互通信

angularjs随笔01 数据双向绑定理解 自定义过滤器 时钟更新列子