Почему ngModel.$ SetViewValue(...) не работает с

Я пишу директиву, которая нуждается в изолированной области видимости, но я хочу связать ее с родительской областью действия через ngModel.

Здесь проблема в том, что значение области видимости родителя не меняется.

наценка

<form name="myForm" ng-app="customControl">
    <div ng-init="form.userContent"></div>
    <div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>
    <span ng-show="myForm.myWidget.$error.required">Required!</span>
    <hr />
    <textarea ng-model="form.userContent"></textarea>
</form>

JS

angular.module('customControl', []).directive('contenteditable', function() {
    return {
        restrict : 'A', // only activate on element attribute
        require : '?ngModel', // get a hold of NgModelController
        scope: {},
        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.bind('blur keyup change', function() {
                        scope.$apply(read);
                    });
            read(); // initialize

            // Write data to the model
            function read() {
                ngModel.$setViewValue(element.html());
            }
        }
    };
});

Демо: скрипка

Это прекрасно работает, если я не использую изолированную область видимости для директивы

Демо: скрипка

3 ответа

Решение

Причина в том, что, так как вы создаете изолированную область для вашего contenteditable директива, ng-model директива на тот же элемент получает и эту изолированную область видимости. Это означает, что у вас есть две разные области, которые не связаны друг с другом, которые обе имеют form.userContent свойство, которое меняется отдельно. Я думаю, вы могли бы проиллюстрировать это следующим кодом:

<!doctype html>
<html ng-app="myApp">
<head>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
    <script src="http://code.angularjs.org/1.0.5/angular.min.js"></script>
    <script>
    angular.module('myApp', []).controller('Ctrl', function($scope) {

    })
    .directive('contenteditable', function() {
        return {
            restrict : 'A', // only activate on element attribute
            require : '?ngModel', // get a hold of NgModelController
            scope: {},
            link : function(scope, element, attrs, ngModel) {
                if (!ngModel)
                    return; // do nothing if no ng-model

                setInterval(function() {
                    if (angular.element('#contenteditable').scope().form)
                        console.log(angular.element('#contenteditable').scope().form.userContent);

                    if (angular.element('#textarea').scope().form)
                        console.log(angular.element('#textarea').scope().form.userContent);
                }, 1000);

                // Specify how UI should be updated
                ngModel.$render = function() {
                    element.html(ngModel.$viewValue || '');
                };

                // Listen for change events to enable binding
                element.bind('blur keyup change', function() {
                            scope.$apply(read);
                        });
                read(); // initialize

                // Write data to the model
                function read() {
                    ngModel.$setViewValue(element.html());
                }
            }
        };
    });
    </script>
</head>
<body ng-controller="Ctrl">
    <form name="myForm">
        <div ng-init="form.userContent"></div>
        <div contenteditable name="myWidget" ng-model="form.userContent" id="contenteditable" required>Change me!</div>
        <span ng-show="myForm.myWidget.$error.required">Required!</span>
        <hr />
        <textarea ng-model="form.userContent" id="textarea"></textarea>
    </form>
</body>
</html>

Как вы увидите в своей консоли, есть две разные области видимости и form.userContent они изменяются отдельно, если вы изменяете текст в текстовой области или если вы изменяете текст в вашем contenteditable div.

Бьюсь об заклад, вы думаете, "хватит объяснений и покажи мне решение!". Ну, нет (насколько мне известно) красивого решения для этого, но есть одно, которое работает. То, что вы хотите сделать, - это ввести ссылку на модель в вашу изолированную область и убедиться, что она имеет то же имя в вашей изолированной области, что и в родительской области.

Вот что вы делаете, вместо создания пустой области видимости:

...
scope: {}
...

Вы связываете модель следующим образом:

...
scope: {
    model: '=ngModel'
}
....

Теперь у вас есть model свойство в вашей изолированной области, которая является ссылкой на form.userContent на вашей родительской области. Но ng-model не ищет model собственность, она ищет form.userProperty который все еще не существует в нашей изолированной области. Чтобы исправить это, мы добавим это в нашу функцию связывания:

scope.$watch('model', function() {
    scope.$eval(attrs.ngModel + ' = model');
});

scope.$watch(attrs.ngModel, function(val) {
    scope.model = val;
});

Первые часы синхронизируют изменения на form.userContent что приходит извне нашей директивы к нашей изолированной form.userContentи вторые часы гарантируют, что мы распространяем любые изменения на нашем изолированном form.userContent до родительской области.

Я понимаю, что это длительный ответ, и, возможно, не очень легко следовать. Поэтому я был бы рад прояснить все, что вы считаете размытым.

Первый ответ хорошо объясняет проблему, я думаю, у меня есть более простое решение, которое позволяет избежать лишних часов.

Подводя итог, ответ 1. ngModel не может работать в изолированной области видимости, потому что элементы, к которым вы хотели привязать его, не входят в его область действия. они находятся в родительской области.

Решение 1, привязать к свойству родителя

<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>

становится

<div contenteditable name="myWidget" ng-model="$parent.form.userContent" required>Change me!</div>

Решение 2, переместите ngModel за пределы изолированной области

require : '?ngModel', становится require : '?^ngModel', ^ говорит вашей директиве искать в родительских элементах ngModel

<div contenteditable name="myWidget" ng-model="form.userContent" required>Change me!</div>

становится

<div ng-model="form.userContent">
    <div contenteditable name="myWidget" required>Change me!</div>
</div>

Я не уверен, будет ли это работать для вас. Однако вы можете попробовать.

HTML:

<form name="myForm" ng-app="customControl">
    <div ng-init="form.userContent"></div>
    <div contenteditable name="myWidget" ng-model="form" required>Change me!</div>
    <span ng-show="myForm.myWidget.$error.required">Required!</span>
    <hr />
    <textarea ng-model="form.content"></textarea>
</form>

JS

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.bind('blur keyup change', function() {
                        scope.$apply(read);
                    });
            read(); // initialize

            // Write data to the model
            function read() {
                ngModel.$setViewValue({'content': element.html()});
            }
        }
    };
});
Другие вопросы по тегам