Почему ngModel.$ SetViewValue(...) не работает с
Я пишу директиву, которая нуждается в изолированной области видимости, но я хочу связать ее с родительской областью действия через ngModel.
Здесь проблема в том, что значение области видимости родителя не меняется.
наценка
<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());
}
}
};
});
Демо: скрипка
Это прекрасно работает, если я не использую изолированную область видимости для директивы
Демо: скрипка
3 ответа
Причина в том, что, так как вы создаете изолированную область для вашего contenteditable
директива, ng-model
директива на тот же элемент получает и эту изолированную область видимости. Это означает, что у вас есть две разные области, которые не связаны друг с другом, которые обе имеют form.userContent
свойство, которое меняется отдельно. Я думаю, вы могли бы проиллюстрировать это следующим кодом:
<!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>
Как вы увидите в своей консоли, есть две разные области видимости и form.userContent
они изменяются отдельно, если вы изменяете текст в текстовой области или если вы изменяете текст в вашем contenteditable div.
Бьюсь об заклад, вы думаете, "хватит объяснений и покажи мне решение!". Ну, нет (насколько мне известно) красивого решения для этого, но есть одно, которое работает. То, что вы хотите сделать, - это ввести ссылку на модель в вашу изолированную область и убедиться, что она имеет то же имя в вашей изолированной области, что и в родительской области.
Вот что вы делаете, вместо создания пустой области видимости:
...
scope: {}
...
Вы связываете модель следующим образом:
...
scope: {
model: '=ngModel'
}
....
Теперь у вас есть model
свойство в вашей изолированной области, которая является ссылкой на form.userContent
на вашей родительской области. Но ng-model
не ищет model
собственность, она ищет form.userProperty
который все еще не существует в нашей изолированной области. Чтобы исправить это, мы добавим это в нашу функцию связывания:
scope.$watch('model', function() {
scope.$eval(attrs.ngModel + ' = model');
});
scope.$watch(attrs.ngModel, function(val) {
scope.model = val;
});
Первые часы синхронизируют изменения на form.userContent
что приходит извне нашей директивы к нашей изолированной form.userContent
и вторые часы гарантируют, что мы распространяем любые изменения на нашем изолированном form.userContent
до родительской области.
Я понимаю, что это длительный ответ, и, возможно, не очень легко следовать. Поэтому я был бы рад прояснить все, что вы считаете размытым.
Первый ответ хорошо объясняет проблему, я думаю, у меня есть более простое решение, которое позволяет избежать лишних часов.
Подводя итог, ответ 1. ngModel не может работать в изолированной области видимости, потому что элементы, к которым вы хотели привязать его, не входят в его область действия. они находятся в родительской области.
Решение 1, привязать к свойству родителя
<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
становится
<div contenteditable name="myWidget" ng-model="$parent.form.userContent" required>Change me!</div>
Решение 2, переместите ngModel за пределы изолированной области
require : '?ngModel',
становится require : '?^ngModel',
^ говорит вашей директиве искать в родительских элементах ngModel
<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
становится
<div ng-model="form.userContent">
<div contenteditable name="myWidget" required>Change me!</div>
</div>
Я не уверен, будет ли это работать для вас. Однако вы можете попробовать.
HTML:
<form name="myForm" ng-app="customControl">
<div ng-init="form.userContent"></div>
<div contenteditable name="myWidget" ng-model="form" required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr />
<textarea ng-model="form.content"></textarea>
</form>
JS
angular.module('customControl', []).directive('contenteditable', function() {
return {
restrict : 'A', // only activate on element attribute
require : '?ngModel', // get a hold of NgModelController
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({'content': element.html()});
}
}
};
});