web-dev-qa-db-fra.com

AngularJS et contentEditable bidirectionnel ne fonctionne pas comme prévu

Pourquoi dans l'exemple suivant, la valeur de rendu initiale est {{ person.name }} plutôt que David? Comment régleriez-vous cela?

exemple en direct 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
Misha Moroshko

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

Donc, retirer

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

ou le remplacer par

ctrl.$render();

va résoudre le problème.

45
asgoth

Réponse courte

Vous initialisez le modèle à partir du DOM à l'aide de cette ligne:

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

Vous n'avez évidemment pas besoin de l'initialiser à partir du DOM, car vous définissez la valeur dans le contrôleur. Supprimez simplement cette ligne d'initialisation.

Réponse longue (et probablement à la question différente)

Il s'agit en fait d'un problème connu: https://github.com/angular/angular.js/issues/528

Voir un exemple officiel de documents 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
Vanuan

Voici ma compréhension des directives personnalisées.

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

vous pouvez également le voir fonctionner 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 que ça aide quelqu'un là-bas. !!

2
Vikas Gautam

Vous pouvez rencontrer des problèmes en utilisant le code de @ Vanaun si une portée. $ Apply est déjà en cours. Dans ce cas, j'utilise $ timeout à la place, ce 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 de travail: Plunkr

0
0
Karthik Balu