AngularJS material 升级后自定义指令双向绑定中断
AngularJS custom directive bidirectional binding broken after material upgrade
我写了一个在 Angular 1.6.1 和 material 1.1.1 下工作的小指令。
这是一个简单的 lock/unlock 按钮图标。
我必须将 material 更新到 1.1.3(对于日期选择器),但从那以后该指令不再起作用。
我不明白为什么 material 更新会这样做....
下面的 plunker 可以工作,但是如果您将 material 版本更改为 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;
});
在询问堆栈社区之前,我已经尽可能多地进行了调查,有没有人对此有所了解?
我不确定您在不同版本之间的确切问题,但请检查此 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-Style
中做了更多
避免在自定义指令中使用 ng-model
作为属性。如果这样做,请不要对它使用隔离作用域双向绑定。使用该属性实例化的 ngModelController API。
主要问题是 jqLite click handler needs to notify the AngularJS framework of changes to scope with scope().$apply():
element.on('click', function (ev) {
scope.bindModel = !scope.bindModel;
//USE $apply
scope.$apply();
});
Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context. Only operations which are applied in Angular execution context will benefit from Angular data-binding, exception handling, property watching, etc... You use $apply() to enter Angular execution context from JavaScript.
Keep in mind that in most places (controllers, services) $apply
has already been called for you by the directive which is handling the event. An explicit call to $apply
is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
— AngularJS Developer Guide - Integration with the browser event loop
也不是操纵 DOM 来更改锁定和解锁图标。它可以通过 ng-show and 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');
});
*/
}
};
})
我写了一个在 Angular 1.6.1 和 material 1.1.1 下工作的小指令。 这是一个简单的 lock/unlock 按钮图标。
我必须将 material 更新到 1.1.3(对于日期选择器),但从那以后该指令不再起作用。
我不明白为什么 material 更新会这样做.... 下面的 plunker 可以工作,但是如果您将 material 版本更改为 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;
});
在询问堆栈社区之前,我已经尽可能多地进行了调查,有没有人对此有所了解?
我不确定您在不同版本之间的确切问题,但请检查此 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-Style
中做了更多避免在自定义指令中使用 ng-model
作为属性。如果这样做,请不要对它使用隔离作用域双向绑定。使用该属性实例化的 ngModelController API。
主要问题是 jqLite click handler needs to notify the AngularJS framework of changes to scope with scope().$apply():
element.on('click', function (ev) {
scope.bindModel = !scope.bindModel;
//USE $apply
scope.$apply();
});
Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context. Only operations which are applied in Angular execution context will benefit from Angular data-binding, exception handling, property watching, etc... You use $apply() to enter Angular execution context from JavaScript.
Keep in mind that in most places (controllers, services)
$apply
has already been called for you by the directive which is handling the event. An explicit call to$apply
is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
— AngularJS Developer Guide - Integration with the browser event loop
也不是操纵 DOM 来更改锁定和解锁图标。它可以通过 ng-show and 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');
});
*/
}
};
})