Jak zrobić dwukierunkowe filtrowanie w AngularJS?

124

Jedną z interesujących rzeczy, które może zrobić AngularJS, jest zastosowanie filtru do określonego wyrażenia powiązania danych, co jest wygodnym sposobem zastosowania, na przykład, waluty specyficznej dla kultury lub formatowania daty we właściwościach modelu. Miło jest również mieć obliczone właściwości w zakresie. Problem polega na tym, że żadna z tych funkcji nie działa w scenariuszach dwukierunkowego wiązania danych - tylko jednokierunkowe wiązanie danych z zakresu do widoku. Wydaje się, że jest to rażący brak w skądinąd doskonałej bibliotece - czy może czegoś mi brakuje?

W KnockoutJS mogłem stworzyć obliczoną właściwość do odczytu / zapisu, która pozwoliła mi określić parę funkcji, jedną wywoływaną w celu uzyskania wartości właściwości, a drugą, która jest wywoływana, gdy właściwość jest ustawiona. Pozwoliło mi to na przykład zaimplementować dane wejściowe uwzględniające kulturę - zezwolenie użytkownikowi na wpisanie „$ 1,24” i przeanalizowanie tego w zmiennoprzecinkowym modelu ViewModel oraz odzwierciedlenie zmian w ViewModel w danych wejściowych.

Najbliższą rzeczą, jaką mogłem znaleźć podobną do tego, jest użycie $scope.$watch(propertyName, functionOrNGExpression);To pozwala mi wywołać funkcję, gdy właściwość w $scopezmianach. Ale to nie rozwiązuje na przykład problemu z danymi wejściowymi uwzględniającymi kulturę. Zwróć uwagę na problemy, gdy próbuję zmodyfikować $watchedwłaściwość w $watchsamej metodzie:

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

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

Element input jest bardzo zdezorientowany, gdy użytkownik zaczyna pisać. Poprawiłem to, dzieląc właściwość na dwie właściwości, jedną dla wartości nieprzetworzonej i jedną dla wartości przeanalizowanej:

$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/ )

To było ulepszenie w stosunku do pierwszej wersji, ale jest trochę bardziej rozwlekłe i zauważ, że nadal występuje problem z parsedValuewłaściwością zmian zakresu (wpisz coś w drugim wejściu, co zmienia parsedValuebezpośrednio. Zauważ, że górne wejście nie zmienia aktualizacja). Może się to zdarzyć w wyniku akcji kontrolera lub załadowania danych z usługi danych.

Czy jest jakiś łatwiejszy sposób na wdrożenie tego scenariusza przy użyciu AngularJS? Czy brakuje mi jakiejś funkcjonalności w dokumentacji?

Jeremy Bell
źródło

Odpowiedzi:

231

Okazuje się, że jest na to bardzo eleganckie rozwiązanie, ale nie jest to dobrze udokumentowane.

Formatowanie wartości modelu do wyświetlania może być obsługiwane przez |operatora i kątowego formatter. Okazuje się, że ngModel, który ma nie tylko listę elementów formatujących, ale także listę parserów.

1. Użyj, ng-modelaby utworzyć dwukierunkowe powiązanie danych

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

2. Utwórz dyrektywę w swoim module kątowym, która zostanie zastosowana do tego samego elementu i zależy od ngModelkontrolera

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

3. W ramach linkmetody dodaj własne konwertery do ngModelkontrolera

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

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

4. Dodaj nową dyrektywę do tego samego elementu, który ma już rozszerzenie ngModel

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

Oto działający przykład, który przekształca tekst na małe litery w modelu inputiz powrotem na wielkie litery

Dokumentacja API dla kontrolera model posiada również krótkie wyjaśnienie i przegląd innych dostępnych metod.

phaas
źródło
Czy jest jakiś powód, dla którego użyłeś „ngModel” jako nazwy czwartego parametru w funkcji łączenia? Czy to nie jest tylko ogólny kontroler dla dyrektywy, który nie ma w zasadzie nic wspólnego z atrybutem ngModel? (Wciąż uczę się kątów, więc mogę się całkowicie mylić.)
Drew Miller,
7
Ze względu na „require: 'ngModel'” czwartym parametrem funkcji łączącej będzie kontroler dyrektywy ngModel - tj. Kontroler foo.bar , który jest instancją ngModelController . Możesz nazwać czwarty parametr, jak chcesz. ( ngModelCtrl
Nazwałbym
8
Ta technika jest udokumentowana na docs.angularjs.org/guide/forms , w sekcji Custom Validation.
Nikhil Dabas
1
@Mark Rajcok w podanych skrzypcach, klikając opcję Załaduj dane - wszystkie małe litery, spodziewałem się, że wartość modelu będzie WIELKIMI LITERAMI, ale wartość modelu była mała. Czy mógłbyś, proszę. wyjaśnij, dlaczego i jak sprawić, by model był zawsze W
CAPSU
1
@rajkamal, ponieważ loadData2 () modyfikuje $scope bezpośrednio, tak będzie ustawiony model ... dopóki użytkownik nie wejdzie w interakcję z polem tekstowym. W tym momencie wszelkie parsery mogą wpływać na wartość modelu. Oprócz parsera możesz dodać zegarek $ watch do swojego kontrolera, aby przekształcić wartość modelu.
Mark Rajcok,