The reason is that since you're creating an isolated scope for your contenteditable
directive, the ng-model
directive on the same element gets that isolated scope as well. Which means that you have two different scopes that aren't connected to each other, which both have a form.userContent
property that changes separately. I guess you could exemplify it by this 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>
As you'll see in your console, there are two different scopes and form.userContent
on them change separately if you change the text in the textarea or if you change the text in your contenteditable div.
So I bet you're thinking "enough with the explaining and show me a solution!". Well, there aren't (to my knowledge) a pretty solution for this, but there is one that works. What you want to do is bring a reference of the model into your isolated scope, and make sure that it has the same name in your isolated scope as in the parent scope.
Here's what you do, instead of creating an empty scope like this:
...
scope: {}
...
You bind the model like this:
...
scope: {
model: '=ngModel'
}
....
Now you have a model
property on your isolated scope that is a reference to form.userContent
on your parent scope. But ng-model
isn't looking for a model
property, it's looking for a form.userProperty
which still doesn't exist in our isolated scope. So to fix this, we add this inside our linking function:
scope.$watch('model', function() {
scope.$eval(attrs.ngModel + ' = model');
});
scope.$watch(attrs.ngModel, function(val) {
scope.model = val;
});
The first watch syncs changes on form.userContent
that comes from outside of our directive to our isolated form.userContent
, and the second watch makes sure that we propagate any changes on our isolated form.userContent
up to the parent scope.
I realize that this is a lengthy answer, and perhaps not very easy to follow. So I'd be happy to clearify anything that you feel is blurry.