AngularJS et contentEditable bind binding ne fonctionne pas comme prévu

Pourquoi dans l'exemple suivant, la première rendus valeur est {{ person.name }} plutôt que David? Comment voulez-vous résoudre ce problème?

Live exemple ici

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());
    }
  };
});
42
demandé sur Misha Moroshko 2013-01-28 16:06:50

5 réponses

le problème est que vous mettez à jour la valeur de la vue lorsque l'interpolation n'est pas encore terminée.

donc enlever

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

ou le remplacer par

ctrl.$render();

résoudra le problème.

45
répondu asgoth 2013-01-28 12:37:51

courte réponse

vous initialisez le modèle à partir du DOM en utilisant cette ligne:

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

vous n'avez évidemment pas besoin de l'initialiser depuis le DOM, puisque vous définissez la valeur dans le controller. Il suffit de supprimer cette ligne d'initialisation.

réponse Longue (et probablement pour l'autre question)

C'est effectivement un problème connu: https://github.com/angular/angular.js/issues/528

voir un exemple officiel de docs ici

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

14
répondu Vanuan 2013-09-12 14:23:03

voici ma compréhension des directives personnalisées.

Le code ci-dessous est aperçu de base de la liaison bidirectionnelle.

vous pouvez le constater ici.

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>

j'Espère, il aide quelqu'un là-bas.!!

1
répondu Vikas Gautam 2015-12-21 21:37:39
0
répondu Karthik Balu 2015-09-29 09:53:10

vous pouvez rencontrer des problèmes en utilisant le code de @Vanaun si un scope.$s'appliquent déjà en cours. Dans ce cas, j'utilise $timeout à la place qui résout le problème:

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);
        }
      }
    };
  });

Exemple: Plunkr

0
répondu William Hampshire 2017-06-07 15:27:06