wake-up-neo.com

Warum funktioniert ngModel. $ SetViewValue (...) nicht?

Ich schreibe eine Direktive, die einen isolierten Gültigkeitsbereich benötigt, aber ich möchte ihn über ngModel an den übergeordneten Gültigkeitsbereich binden.

Hier besteht das Problem darin, dass der Bereichswert des übergeordneten Elements nicht geändert wird.

Markup

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

Demo: Geige .

Dies funktioniert gut, wenn ich keinen isolierten Bereich für die Direktive verwende

Demo: Geige .

29
Arun P Johny

Der Grund ist, dass, da Sie einen isolierten Bereich für Ihre contenteditable -Direktive erstellen, die ng-model - Direktive für dasselbe Element auch diesen isolierten Bereich erhält. Das bedeutet, dass Sie zwei verschiedene Bereiche haben, die nicht miteinander verbunden sind. Beide haben eine form.userContent - Eigenschaft, die separat geändert wird. Ich denke, Sie könnten es durch diesen Code veranschaulichen:

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

Wie Sie in Ihrer Konsole sehen werden, gibt es zwei verschiedene Bereiche, und form.userContent Ändert sich separat, wenn Sie den Text im Textbereich oder den Text in Ihrem inhaltsbearbeitbaren div ändern.

Ich wette, Sie denken "genug mit dem Erklären und zeigen mir eine Lösung!". Nun, meines Wissens gibt es keine hübsche Lösung dafür, aber es gibt eine, die funktioniert. Sie möchten lediglich eine Referenz des Modells in Ihren isolierten Bereich einfügen und sicherstellen, dass es in Ihrem isolierten Bereich denselben Namen hat wie im übergeordneten Bereich.

Folgendes tun Sie, anstatt einen leeren Bereich wie diesen zu erstellen:

...
scope: {}
...

Sie binden das Modell wie folgt:

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

Jetzt haben Sie eine model -Eigenschaft in Ihrem isolierten Bereich, die auf form.userContent In Ihrem übergeordneten Bereich verweist. Aber ng-model Sucht nicht nach einer model -Eigenschaft, sondern nach einer form.userProperty, Die in unserem isolierten Bereich noch nicht existiert. Um dies zu beheben, fügen wir dies in unsere Verknüpfungsfunktion ein:

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

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

Die erste Uhr synchronisiert Änderungen an form.userContent, Die von außerhalb unserer Direktive stammen, mit unserem isolierten form.userContent, Und die zweite Uhr stellt sicher, dass wir alle Änderungen an unserem isolierten form.userContent Weitergeben zum übergeordneten Bereich.

Mir ist klar, dass dies eine langwierige Antwort ist und vielleicht nicht so einfach zu befolgen ist. Also würde ich gerne alles klären, was Sie für unscharf halten.

43
Anders Ekdahl

die erste Antwort erklärt das Problem gut, ich glaube, ich habe eine einfachere Lösung, die zusätzliche Uhren vermeidet.

um die Antwort 1 zusammenzufassen: ngModel kann nicht innerhalb des Isolate-Bereichs arbeiten, da die Elemente, an die Sie binden wollten, nicht in seinem Bereich liegen. Sie befinden sich im übergeordneten Bereich.

lösung 1, binden Sie an das Eigentum der Eltern

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

wird

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

lösung 2: Verschieben Sie ngModel außerhalb des Isolationsbereichs

require : '?ngModel', wird require : '?^ngModel', das ^ weist Ihre Direktive an, in übergeordneten Elementen nach ngModel zu suchen

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

wird

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