Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - ng-model fails on contenteditable <span>

I'm learning AngularJS. I've come across something I can't explain, nor can I find any explanation for (or solution).

I have a simple AngularJS app and I am attempting to bind a <span contenteditable="true"> to a value, but it doesn't work. EG:

<!-- Works as expected -->
<input data-ng-model="chunk.value"></input>

<!-- Shows value, but doesn't bind - changes not reflected in model -->
<span contenteditable="true">{{chunk.value}}</span>

<!-- This is empty -->
<span contenteditable="true" data-ng-model="chunk.value"></span>

How can I make the last span use 2-way binding, such that editing its value updates chunk.value and vice versa?

like image 561
Alex McMillan Avatar asked May 07 '14 21:05

Alex McMillan


2 Answers

ng-bind! Use ng-bind for one-way binding in 'span'.

Please refer here for an example: https://docs.angularjs.org/api/ng/directive/ngBind

So your line would be: <span contenteditable="true" ng-bind="chunk.value"></span>

Hope this help

like image 136
Siti Farah Avatar answered Nov 14 '22 19:11

Siti Farah


To make ng-model work with contenteditable <span> elements, use a custom directive:

app.directive('contenteditable', ['$sce', function($sce) {
    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($sce.getTrustedHtml(ngModel.$viewValue || ''));
        };

        // Listen for change events to enable binding
        element.on('blur keyup change', function() {
          scope.$evalAsync(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);
        }
      }
    };
}]);

Usage:

<span contenteditable ng-model="userContent">Change me!</span>
<p>{{userContent}}</p>

For more infomation, see

  • AngularJS ngModelController API Reference - Custom Control Example
  • AngularJS Developer Reference - Creating Custom Directives

The DEMO

angular.module('customControl', ['ngSanitize'])
.directive('contenteditable', ['$sce', function($sce) {
    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($sce.getTrustedHtml(ngModel.$viewValue || ''));
        };

        // Listen for change events to enable binding
        element.on('blur keyup change', function() {
          scope.$evalAsync(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);
        }
      }
    };
  }]);
[contenteditable] {
  border: 1px solid black;
  background-color: white;
  min-height: 20px;
}
<script src="//unpkg.com/angular/angular.js"></script>
<script src="//unpkg.com/angular-sanitize/angular-sanitize.js"></script>
<body ng-app="customControl">
 <span contenteditable ng-model="userContent">Change me!</span>
 <hr>
 Content={{userContent}}
</body>
like image 1
georgeawg Avatar answered Nov 14 '22 18:11

georgeawg