AngularJS和contentEditable两种方式绑定不能按预期工作

为什么在下面的例子中,初始渲染值是{{ person.name }}而不是David ? 你将如何解决这个问题?

现场示例

HTML:

 <body ng-controller="MyCtrl"> <div contenteditable="true" ng-model="person.name">{{ person.name }}</div> <pre ng-bind="person.name"></pre> </body> 

JS:

 app.controller('MyCtrl', function($scope) { $scope.person = {name: 'David'}; }); app.directive('contenteditable', function() { return { require: 'ngModel', link: function(scope, element, attrs, ctrl) { // view -> model element.bind('blur', function() { scope.$apply(function() { ctrl.$setViewValue(element.html()); }); }); // model -> view ctrl.$render = function() { element.html(ctrl.$viewValue); }; // load init value from DOM ctrl.$setViewValue(element.html()); } }; }); 

问题是,当插值尚未完成时,您正在更新视图值。

所以删除

 // load init value from DOM ctrl.$setViewValue(element.html()); 

或用其replace

 ctrl.$render(); 

将解决这个问题。

简短的回答

您正在使用以下代码从DOM初始化模型:

 ctrl.$setViewValue(element.html()); 

您显然不需要从DOM初始化它,因为您正在设置控制器中的值。 只要删除这个初始化线。

很长的回答(也许对不同的问题)

这实际上是一个已知的问题: https : //github.com/angular/angular.js/issues/528

在这里看到一个官方文档的例子

HTML:

 <!doctype html> <html ng-app="customControl"> <head> <script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script> <script src="script.js"></script> </head> <body> <form name="myForm"> <div contenteditable name="myWidget" ng-model="userContent" strip-br="true" required>Change me!</div> <span ng-show="myForm.myWidget.$error.required">Required!</span> <hr> <textarea ng-model="userContent"></textarea> </form> </body> </html> 

JavaScript的:

 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.on('blur keyup change', function() { scope.$apply(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a <br> behind // If strip-br attribute is provided then we strip this out if( attrs.stripBr && html == '<br>' ) { html = ''; } ngModel.$setViewValue(html); } } }; }); 

Plunkr

这是我对Custom指令的理解。

下面的代码是双向绑定的基本概述。

你也可以看到它在这里工作。

http://plnkr.co/edit/8dhZw5W1JyPFUiY7sXjo

 <!doctype html> <html ng-app="customCtrl"> <head> <script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script> <script> angular.module("customCtrl", []) //[] for setter .directive("contenteditable", function () { return { restrict: "A", //A for Attribute, E for Element, C for Class & M for comment require: "ngModel", //requiring ngModel to bind 2 ways. link: linkFunc } //----------------------------------------------------------------------// function linkFunc(scope, element, attributes,ngModelController) { // From Html to View Model // Attaching an event handler to trigger the View Model Update. // Using scope.$apply to update View Model with a function as an // argument that takes Value from the Html Page and update it on View Model element.on("keyup blur change", function () { scope.$apply(updateViewModel) }) function updateViewModel() { var htmlValue = element.text() ngModelController.$setViewValue(htmlValue) } // from View Model to Html // render method of Model Controller takes a function defining how // to update the Html. Function gets the current value in the View Model // with $viewValue property of Model Controller and I used element text method // to update the Html just as we do in normal jQuery. ngModelController.$render = updateHtml function updateHtml() { var viewModelValue = ngModelController.$viewValue // if viewModelValue is change internally, and if it is // undefined, it won't update the html. That's why "" is used. viewModelValue = viewModelValue ? viewModelValue : "" element.text(viewModelValue) } // General Notes:- ngModelController is a connection between backend View Model and the // front end Html. So we can use $viewValue and $setViewValue property to view backend // value and set backend value. For taking and setting Frontend Html Value, Element would suffice. } }) </script> </head> <body> <form name="myForm"> <label>Enter some text!!</label> <div contenteditable name="myWidget" ng-model="userContent" style="border: 1px solid lightgrey"></div> <hr> <textarea placeholder="Enter some text!!" ng-model="userContent"></textarea> </form> </body> </html> 

希望它能帮助别人。

如果一个作用域$ apply已经在进行,你可能会遇到使用@ Vanaun代码的问题。 在这种情况下,我使用$超时代替它解决了这个问题:

HTML:

 <!doctype html> <html ng-app="customControl"> <head> <script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script> <script src="script.js"></script> </head> <body> <form name="myForm"> <div contenteditable name="myWidget" ng-model="userContent" strip-br="true" required>Change me!</div> <span ng-show="myForm.myWidget.$error.required">Required!</span> <hr> <textarea ng-model="userContent"></textarea> </form> </body> </html> 

JavaScript的:

 angular.module('customControl', []). directive('contenteditable', function($timeout) { 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.on('blur keyup change', function() { $timeout(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a <br> behind // If strip-br attribute is provided then we strip this out if( attrs.stripBr && html == '<br>' ) { html = ''; } ngModel.$setViewValue(html); } } }; }); 

工作示例: Plunkr