angularjs - parameter - ¿Por qué ngModel. $ SetViewValue(...) no funciona desde
scope directive angularjs (3)
El motivo es que, dado que está creando un ámbito aislado para su directiva contenteditable
, la directiva ng-model
en el mismo elemento también obtiene ese ámbito aislado. Lo que significa que tiene dos ámbitos diferentes que no están conectados entre sí, y que ambos tienen una propiedad form.userContent
que cambia por separado. Supongo que podrías ejemplificarlo con este código:
<!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>
Como verá en su consola, hay dos ámbitos diferentes y form.userContent
en ellos cambia por separado si cambia el texto en el área de texto o si cambia el texto en su div contenteditable.
Así que apuesto a que estás pensando "basta con las explicaciones y muéstrame una solución". Bueno, no hay (que yo sepa) una solución bonita para esto, pero hay una que funciona. Lo que desea hacer es traer una referencia del modelo a su alcance aislado y asegurarse de que tenga el mismo nombre en su alcance aislado que en el alcance principal.
Esto es lo que haces, en lugar de crear un ámbito vacío como este:
...
scope: {}
...
Atas el modelo así:
...
scope: {
model: ''=ngModel''
}
....
Ahora tiene una propiedad model
en su ámbito aislado que es una referencia a form.userContent
en su ámbito primario. Pero ng-model
no está buscando una propiedad model
, está buscando una form.userProperty
que todavía no existe en nuestro ámbito aislado. Así que para solucionar esto, agregamos esto dentro de nuestra función de enlace:
scope.$watch(''model'', function() {
scope.$eval(attrs.ngModel + '' = model'');
});
scope.$watch(attrs.ngModel, function(val) {
scope.model = val;
});
El primer reloj sincroniza los cambios en form.userContent
que vienen de fuera de nuestra directiva a nuestro form.userContent
, y el segundo reloj se asegura de que form.userContent
cualquier cambio en nuestro form.userContent
aislado. form.userContent
hasta el ámbito principal.
Me doy cuenta de que esta es una respuesta larga, y quizás no muy fácil de seguir. Así que me encantaría aclarar cualquier cosa que consideren borrosa.
Estoy escribiendo una directiva que necesita un alcance aislado, pero quiero vincularlo al alcance principal a través de ngModel .
Aquí el problema es que el valor del alcance del padre no se modifica.
Margen
<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());
}
}
};
});
Demostración: Fiddle .
Esto funciona bien si no uso un ámbito aislado para la directiva
Demostración: Fiddle .
La primera respuesta explica bien el problema, creo que tengo una solución más simple que evita los relojes adicionales.
para resumir la respuesta 1. ngModel no puede funcionar dentro del ámbito de aislamiento porque los elementos a los que pretendía enlazar no están en su alcance. están en el ámbito principal.
solución 1, se unen a la propiedad del padre
<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
se convierte en
<div contenteditable name="myWidget" ng-model="$parent.form.userContent" required>Change me!</div>
solución 2, mueva ngModel fuera del alcance del aislamiento
require : ''?ngModel'',
convierte en require : ''?^ngModel'',
el ^ le dice a su directiva que busque elementos primarios para ngModel
<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
se convierte en
<div ng-model="form.userContent">
<div contenteditable name="myWidget" required>Change me!</div>
</div>
No estoy seguro de si esto funcionará para usted. Sin embargo, puedes darle una oportunidad.
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()});
}
}
};
});