J'écris une directive qui a besoin d'une portée isolée, mais je veux la lier à la portée parent via ngModel .
Ici, le problème est que la valeur de portée du parent n'est pas modifiée.
Markup
<form name="myForm" ng-app="customControl">
<div ng-init="form.userContent"></div>
<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr />
<textarea ng-model="form.userContent"></textarea>
</form>
JS
angular.module('customControl', []).directive('contenteditable', function() {
return {
restrict : 'A', // only activate on element attribute
require : '?ngModel', // get a hold of NgModelController
scope: {},
link : function(scope, element, attrs, ngModel) {
if (!ngModel)
return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.bind('blur keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
ngModel.$setViewValue(element.html());
}
}
};
});
Démo: Fiddle .
Cela fonctionne bien si je n'utilise pas un champ d'application isolé pour la directive
Démo: Fiddle .
La raison en est que puisque vous créez une étendue isolée pour votre directive contenteditable
, la directive ng-model
Sur le même élément obtient également cette étendue isolée. Ce qui signifie que vous avez deux étendues différentes qui ne sont pas connectées l'une à l'autre, qui ont toutes deux une propriété form.userContent
Qui change séparément. Je suppose que vous pouvez l'illustrer par ce code:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://code.angularjs.org/1.0.5/angular.min.js"></script>
<script>
angular.module('myApp', []).controller('Ctrl', function($scope) {
})
.directive('contenteditable', function() {
return {
restrict : 'A', // only activate on element attribute
require : '?ngModel', // get a hold of NgModelController
scope: {},
link : function(scope, element, attrs, ngModel) {
if (!ngModel)
return; // do nothing if no ng-model
setInterval(function() {
if (angular.element('#contenteditable').scope().form)
console.log(angular.element('#contenteditable').scope().form.userContent);
if (angular.element('#textarea').scope().form)
console.log(angular.element('#textarea').scope().form.userContent);
}, 1000);
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.bind('blur keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
ngModel.$setViewValue(element.html());
}
}
};
});
</script>
</head>
<body ng-controller="Ctrl">
<form name="myForm">
<div ng-init="form.userContent"></div>
<div contenteditable name="myWidget" ng-model="form.userContent" id="contenteditable" required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr />
<textarea ng-model="form.userContent" id="textarea"></textarea>
</form>
</body>
</html>
Comme vous le verrez dans votre console, il existe deux étendues différentes et form.userContent
Sur elles changent séparément si vous changez le texte dans la zone de texte ou si vous changez le texte dans votre div contentable.
Je parie donc que vous pensez "assez avec l'explication et montrez-moi une solution!". Eh bien, il n'y a pas (à ma connaissance) une jolie solution pour cela, mais il y en a une qui fonctionne. Ce que vous voulez faire, c'est introduire une référence du modèle dans votre portée isolée et vous assurer qu'il porte le même nom dans votre portée isolée que dans la portée parent.
Voici ce que vous faites, au lieu de créer une portée vide comme celle-ci:
...
scope: {}
...
Vous liez le modèle comme ceci:
...
scope: {
model: '=ngModel'
}
....
Vous avez maintenant une propriété model
sur votre portée isolée qui est une référence à form.userContent
Sur votre portée parent. Mais ng-model
Ne cherche pas une propriété model
, il cherche un form.userProperty
Qui n'existe toujours pas dans notre portée isolée. Donc, pour résoudre ce problème, nous ajoutons cela dans notre fonction de liaison:
scope.$watch('model', function() {
scope.$eval(attrs.ngModel + ' = model');
});
scope.$watch(attrs.ngModel, function(val) {
scope.model = val;
});
La première montre synchronise les changements sur form.userContent
Qui viennent de l'extérieur de notre directive vers notre form.userContent
Isolé, et la deuxième montre s'assure que nous propageons tous les changements sur notre form.userContent
Isolé à la portée parent.
Je me rends compte que c'est une longue réponse, et peut-être pas très facile à suivre. Je serais donc heureux de clarifier tout ce qui vous semble flou.
la première réponse explique bien le problème, je crois avoir une solution plus simple qui évite les montres supplémentaires.
pour résumer la réponse 1. ngModel ne peut pas fonctionner à l'intérieur de la portée d'isolement car les éléments auxquels vous vouliez le lier ne sont pas dans sa portée. ils sont dans la portée parent.
solution 1, liez à la propriété du parent
<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
devient
<div contenteditable name="myWidget" ng-model="$parent.form.userContent" required>Change me!</div>
solution 2, déplacez ngModel hors de la portée de l'isolat
require : '?ngModel',
devient require : '?^ngModel',
le ^ indique à votre directive de chercher dans les éléments parents pour ngModel
<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
devient
<div ng-model="form.userContent">
<div contenteditable name="myWidget" required>Change me!</div>
</div>