J'ai une directive avec un modèle comme
<div>
<div ng-repeat="item in items" ng-click="updateModel(item)">
<div>
Ma directive est déclarée comme:
return {
templateUrl: '...',
restrict: 'E',
require: '^ngModel',
scope: {
items: '=',
ngModel: '=',
ngChange: '&'
},
link: function postLink(scope, element, attrs)
{
scope.updateModel = function(item)
{
scope.ngModel = item;
scope.ngChange();
}
}
}
Je voudrais avoir ng-change
appelé lorsque vous cliquez sur un élément et que la valeur de foo
a déjà été modifiée.
Autrement dit, si ma directive est mise en œuvre comme:
<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive>
Je m'attendrais à appeler bar
lorsque la valeur de foo
aura été mise à jour.
Avec le code donné ci-dessus, ngChange
est appelé avec succès, mais avec l'ancienne valeur de foo
à la place de la nouvelle valeur mise à jour.
Une façon de résoudre le problème consiste à appeler ngChange
dans un délai d’exécution afin de l’exécuter ultérieurement, lorsque la valeur de foo
aura déjà été modifiée. Mais cette solution me fait perdre le contrôle de l'ordre dans lequel les choses sont censées être exécutées et je suppose qu'il devrait y avoir une solution plus élégante.
Je pourrais aussi utiliser un observateur sur foo
dans la portée parente, mais cette solution ne donne pas vraiment une méthode ngChange
à implémenter et on m'a dit que les observateurs sont d'excellents consommateurs de mémoire.
Existe-t-il un moyen de faire exécuter ngChange
de manière synchrone sans délai d'attente ni observateur?
Exemple: http://plnkr.co/edit/8H6QDO8OYiOyOx8efhyJ?p=preview
Si vous avez besoin de ngModel
, vous pouvez simplement appeler $setViewValue
sur ngModelController
, qui évalue implicitement ng-change
. Le quatrième paramètre de la fonction de liaison devrait être ngModelCtrl. Le code suivant fera ng-change
travaillez pour votre directive.
link : function(scope, element, attrs, ngModelCtrl){
scope.updateModel = function(item) {
ngModelCtrl.$setViewValue(item);
}
}
Pour que votre solution fonctionne, supprimez ngChange et ngModel de la portée isolée de myDirective.
En voici un exemple: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview
tl; dr
D'après mon expérience, il vous suffit d'hériter de ngModelCtrl . l'expression ng-change
sera automatiquement évaluée lorsque vous utiliserez la méthode ngModelCtrl.$setViewValue
angular.module("myApp").directive("myDirective", function(){
return {
require:"^ngModel", // this is important,
scope:{
... // put the variables you need here but DO NOT have a variable named ngModel or ngChange
},
link: function(scope, elt, attrs, ctrl){ // ctrl here is the ngModelCtrl
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically eval your ng-change
};
}
};
});
Plus précisément
ng-change
Est évalué pendant la ngModelCtrl.$commitViewValue()
[~ # ~] si [~ # ~] la référence de l'objet de votre ngModel a changé. la méthode $commitViewValue()
est appelée automatiquement par $setViewValue(value, trigger)
si vous n'utilisez pas l'argument de déclencheur ou si vous n'avez pas précisé de ngModelOptions .
J'ai précisé que le ng-chage
Serait automatiquement déclenché si la référence du $viewValue
Était modifiée. Lorsque votre ngModel
est un string
ou un int
, vous n'avez pas à vous en préoccuper. Si votre ngModel
est un objet et que vous ne modifiez que certaines de ses propriétés, alors $setViewValue
N'évalue pas ngChange
.
Si nous prenons l'exemple de code depuis le début de l'article
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically evalyour ng-change
};
scope.updateValue = function(prop1Value){
var vv = ctrl.$viewValue;
vv.prop1 = prop1Value;
ctrl.$setViewValue(vv); // this line won't eval the ng-change expression
};
Après quelques recherches, il semble que la meilleure approche consiste à utiliser $timeout(callback, 0)
.
Il lance automatiquement un $digest
cycle juste après l'exécution du rappel.
Donc, dans mon cas, la solution consistait à utiliser
$timeout(scope.ngChange, 0);
Ainsi, peu importe la signature de votre rappel, celui-ci sera exécuté exactement comme vous l'avez défini dans la portée parente.
Voici le plunkr avec de tels changements: http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview
Les réponses de Samuli Ulmanen et de lucienBertin le montrent bien, bien qu’un peu plus loin, dans la documentation d’AngularJS, donne des conseils supplémentaires sur la façon de gérer cela (voir https://docs.angularjs.org/api/ng/type/ngModel). NgModelController ).
Spécifiquement dans les cas où vous passez des objets à $ setViewValue (myObj). AngularJS Documentatation déclare:
Lorsqu'elle est utilisée avec des entrées standard, la valeur de vue sera toujours une chaîne (qui est dans certains cas analysée dans un autre type, tel qu'un objet Date pour input [date].) Toutefois, les contrôles personnalisés peuvent également transmettre des objets à cette méthode. Dans ce cas, nous devrions faire une copie de l'objet avant de le passer à $ setViewValue. En effet, ngModel n'effectue pas une surveillance approfondie des objets, il recherche uniquement un changement d'identité. Si vous ne modifiez que la propriété de l'objet, ngModel ne réalisera pas que l'objet a changé et n'appellera pas les pipelines $ Parsers et $ validators. Pour cette raison, vous ne devriez pas modifier les propriétés de la copie une fois qu'elle a été passée à $ setViewValue. Sinon, la valeur du modèle sur l'étendue risque de ne pas être modifiée correctement.
Dans mon cas particulier, mon modèle est un objet date/moment. Je dois donc tout d'abord cloner l'objet avant d'appeler setViewValue. J'ai de la chance ici car moment fournit une méthode de clonage simple: var b = moment(a);
link : function(scope, elements, attrs, ctrl) {
scope.updateModel = function (value) {
if (ctrl.$viewValue == value) {
var copyOfObject = moment(value);
ctrl.$setViewValue(copyOfObject);
}
else
{
ctrl.$setViewValue(value);
}
};
}