Подтвердить что ты не робот

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());
    }
  };
});
4b9b3361

Ответ 1

Проблема заключается в том, что вы обновляете значение вида, когда интерполяция еще не завершена.

Поэтому удаление

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

или заменив его на

ctrl.$render();

решит проблему.

Ответ 2

Короткий ответ

Вы инициализируете модель из DOM, используя эту строку:

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

Вам явно не нужно инициализировать его из DOM, так как вы устанавливаете значение в контроллере. Просто удалите эту строку инициализации.

Длинный ответ (и, вероятно, на другой вопрос)

Это действительно известная проблема: https://github.com/angular/angular.js/issues/528

См. официальный документ docs здесь

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

Ответ 3

Вот мое понимание пользовательских директив.

Ниже представлен базовый обзор двухсторонней привязки.

вы также можете увидеть, как он работает здесь.

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 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>

Надеюсь, это помогает кому-то там.!!

Ответ 5

У вас могут возникнуть проблемы с использованием кода @Vanaun, если область применения. $apply уже выполняется. В этом случае вместо этого я использую $timeout, что устраняет проблему:

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