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

Почему 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());
            }
        }
    };
});

Демо: Fiddle.

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

Демо: Fiddle.

4b9b3361

Ответ 1

Причина в том, что, поскольку вы создаете изолированную область для своей директивы 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 до родительской области.

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

Ответ 2

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

чтобы суммировать ответ 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>

Ответ 3

Не знаю, что именно вы хотите, и можете попробовать это.

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