Wie wird in AngularJS in beide Richtungen gefiltert?

124

AngularJS kann unter anderem einen Filter auf einen bestimmten Datenbindungsausdruck anwenden. Dies ist eine bequeme Möglichkeit, um beispielsweise die kulturspezifische Währung oder die Datumsformatierung der Eigenschaften eines Modells anzuwenden. Es ist auch schön, berechnete Eigenschaften für den Bereich zu haben. Das Problem ist, dass keine dieser Funktionen mit Zwei-Wege-Datenbindungsszenarien funktioniert - nur Ein-Wege-Datenbindung vom Bereich bis zur Ansicht. Dies scheint eine krasse Auslassung in einer ansonsten hervorragenden Bibliothek zu sein - oder fehlt mir etwas?

In KnockoutJS konnte ich eine berechnete Lese- / Schreib-Eigenschaft erstellen, mit der ich ein Funktionspaar angeben konnte, eine, die aufgerufen wird, um den Wert der Eigenschaft abzurufen, und eine, die aufgerufen wird, wenn die Eigenschaft festgelegt wird. Auf diese Weise konnte ich beispielsweise kulturbewusste Eingaben implementieren. Der Benutzer konnte "$ 1.24" eingeben und diese in einen Float im ViewModel analysieren und Änderungen im ViewModel in der Eingabe widerspiegeln.

Das Nächste, was ich ähnlich finden könnte, ist die Verwendung von $scope.$watch(propertyName, functionOrNGExpression);Dies ermöglicht es mir, eine Funktion aufzurufen, wenn sich eine Eigenschaft in den $scopeÄnderungen ändert. Dies löst jedoch beispielsweise nicht das kulturbewusste Eingabeproblem. Beachten Sie die Probleme, wenn ich versuche, die $watchedEigenschaft innerhalb der $watchMethode selbst zu ändern :

$scope.$watch("property", function (newValue, oldValue) {
    $scope.outputMessage = "oldValue: " + oldValue + " newValue: " + newValue;
    $scope.property = Globalize.parseFloat(newValue);
});

( http://jsfiddle.net/gyZH8/2/ )

Das Eingabeelement wird sehr verwirrt, wenn der Benutzer mit der Eingabe beginnt. Ich habe es verbessert, indem ich die Eigenschaft in zwei Eigenschaften aufgeteilt habe, eine für den nicht analysierten Wert und eine für den analysierten Wert:

$scope.visibleProperty= 0.0;
$scope.hiddenProperty = 0.0;
$scope.$watch("visibleProperty", function (newValue, oldValue) {
    $scope.outputMessage = "oldValue: " + oldValue + " newValue: " + newValue;
    $scope.hiddenProperty = Globalize.parseFloat(newValue);
});

( http://jsfiddle.net/XkPNv/1/ )

Dies war eine Verbesserung gegenüber der ersten Version, ist jedoch etwas ausführlicher. Beachten Sie, dass die parsedValueEigenschaft der Bereichsänderungen immer noch ein Problem aufweist (geben Sie etwas in die zweite Eingabe ein, wodurch sich die parsedValuedirekt ändert . Beachten Sie, dass die oberste Eingabe dies nicht tut aktualisieren). Dies kann durch eine Controller-Aktion oder durch das Laden von Daten von einem Datendienst geschehen.

Gibt es eine einfachere Möglichkeit, dieses Szenario mit AngularJS zu implementieren? Vermisse ich einige Funktionen in der Dokumentation?

Jeremy Bell
quelle

Antworten:

231

Es stellt sich heraus, dass es dafür eine sehr elegante Lösung gibt, die jedoch nicht gut dokumentiert ist.

Das Formatieren von Modellwerten für die Anzeige kann vom |Bediener und einem Winkel übernommen werden formatter. Es stellt sich heraus, dass das ngModel nicht nur eine Liste von Formatierern, sondern auch eine Liste von Parsern enthält.

1. Verwenden Sie ng-modeldiese Option , um die bidirektionale Datenbindung zu erstellen

<input type="text" ng-model="foo.bar"></input>

2. Erstellen Sie in Ihrem Winkelmodul eine Direktive, die auf dasselbe Element angewendet wird und von der ngModelSteuerung abhängt

module.directive('lowercase', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attr, ngModel) {
            ...
        }
    };
});

3. linkFügen Sie innerhalb der Methode Ihre benutzerdefinierten Konverter zum ngModelController hinzu

function fromUser(text) {
    return (text || '').toUpperCase();
}

function toUser(text) {
    return (text || '').toLowerCase();
}
ngModel.$parsers.push(fromUser);
ngModel.$formatters.push(toUser);

4. Fügen Sie Ihre neue Direktive demselben Element hinzu, das bereits über die verfügt ngModel

<input type="text" lowercase ng-model="foo.bar"></input>

Hier ist ein Arbeitsbeispiel , das Text inputim Modell in Kleinbuchstaben und zurück in Großbuchstaben umwandelt

Die API-Dokumentation für den Model Controller enthält auch eine kurze Erläuterung und einen Überblick über die anderen verfügbaren Methoden.

Phaas
quelle
Gibt es einen Grund, warum Sie "ngModel" als Namen für das vierte Parametet in Ihrer Verknüpfungsfunktion verwendet haben? Ist das nicht nur ein generischer Controller für die Direktive, der im Grunde nichts mit dem Attribut ngModel zu tun hat? (Ich lerne hier immer noch eckig, damit ich mich völlig irren kann.)
Drew Miller
7
Aufgrund von "require: 'ngModel'" ist der vierte Parameter der Verknüpfungsfunktion der Controller der ngModel-Direktive, dh der Controller von foo.bar , der eine Instanz von ngModelController ist . Sie können den 4. Parameter beliebig benennen. (Ich würde es nennen ngModelCtrl.)
Mark Rajcok
8
Diese Technik ist unter docs.angularjs.org/guide/forms im Abschnitt Benutzerdefinierte Validierung dokumentiert .
Nikhil Dabas
1
@ Mark Rajcok in der bereitgestellten Geige, während ich auf Daten laden klickte - alles in Kleinbuchstaben, erwartete ich, dass der Modellwert in ALL CAPS sein würde, aber der Modellwert war klein. Könnten Sie bitte. Erklären Sie, warum und wie Sie das Modell immer
Rajkamal Subramanian
1
@rajkamal, da loadData2 () $scopedirekt geändert wird, wird das Modell auf ... gesetzt, bis der Benutzer mit dem Textfeld interagiert. Zu diesem Zeitpunkt können alle Parser den Modellwert beeinflussen. Zusätzlich zu einem Parser können Sie Ihrem Controller eine $ watch hinzufügen, um den Modellwert zu transformieren.
Mark Rajcok