Come eseguire il filtraggio a due vie in AngularJS?


124

Una delle cose interessanti che AngularJS può fare è applicare un filtro a una particolare espressione di associazione di dati, che è un modo conveniente per applicare, ad esempio, la valuta specifica della cultura o la formattazione della data delle proprietà di un modello. È anche bello avere proprietà calcolate sull'ambito. Il problema è che nessuna di queste funzionalità funziona con scenari di associazione di dati bidirezionale, solo associazione di dati unidirezionale dall'ambito alla vista. Questa sembra essere un'omissione lampante in una biblioteca altrimenti eccellente - o mi sto perdendo qualcosa?

In KnockoutJS , ho potuto creare una proprietà calcolata di lettura / scrittura, che mi ha permesso di specificare una coppia di funzioni, una chiamata per ottenere il valore della proprietà e una che viene chiamata quando la proprietà è impostata. Questo mi ha permesso di implementare, ad esempio, input sensibile alla cultura, consentendo all'utente di digitare "$ 1,24" e di analizzarlo in un float nel ViewModel, e avere le modifiche nel ViewModel riflesse nell'input.

La cosa più simile che ho trovato simile a questa è l'uso di $scope.$watch(propertyName, functionOrNGExpression);Questo mi permette di avere una funzione invocata quando una proprietà $scopecambia. Ma questo non risolve, ad esempio, il problema dell'input consapevole della cultura. Nota i problemi quando provo a modificare la $watchedproprietà all'interno del $watchmetodo stesso:

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

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

L'elemento di input diventa molto confuso quando l'utente inizia a digitare. L'ho migliorato suddividendo la proprietà in due proprietà, una per il valore non analizzato e una per il valore analizzato:

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

Si trattava di un miglioramento rispetto alla prima versione, ma è un po 'più dettagliato e nota che c'è ancora un problema di parsedValueproprietà delle modifiche dell'ambito (digita qualcosa nel secondo input, che cambia parsedValuedirettamente. Nota che l'input superiore non lo fa aggiornare). Ciò potrebbe accadere da un'azione del controller o dal caricamento dei dati da un servizio dati.

C'è un modo più semplice per implementare questo scenario utilizzando AngularJS? Manca qualche funzionalità nella documentazione?

Risposte:


231

Si scopre che c'è una soluzione molto elegante a questo, ma non è ben documentata.

La formattazione dei valori del modello per la visualizzazione può essere gestita |dall'operatore e da un angolare formatter. Si scopre che ngModel che ha non solo un elenco di formattatori ma anche un elenco di parser.

1. Utilizzare ng-modelper creare l'associazione dati a due vie

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

2. Creare una direttiva nel modulo angolare che verrà applicata allo stesso elemento e che dipende dal ngModelcontroller

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

3. All'interno del linkmetodo, aggiungere i convertitori personalizzati al ngModelcontroller

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

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

4. Aggiungere la nuova direttiva allo stesso elemento che ha già l'estensione ngModel

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

Ecco un esempio funzionante che trasforma il testo in minuscolo nel modello inpute di nuovo in maiuscolo nel modello

La documentazione API per Model Controller contiene anche una breve spiegazione e una panoramica degli altri metodi disponibili.


C'è qualche motivo per cui hai usato "ngModel" come nome per il quarto parametro nella tua funzione di collegamento? Non è solo un controller generico per la direttiva che praticamente non ha nulla a che fare con l'attributo ngModel? (Sto ancora imparando l'angolazione qui, quindi potrei sbagliarmi completamente.)
Drew Miller

7
A causa di "require: 'ngModel'", il quarto parametro della funzione di collegamento sarà il controller della direttiva ngModel, cioè il controller di foo.bar, che è un'istanza di ngModelController . Puoi nominare il 4 ° parametro come preferisci. (Lo ngModelCtrl
chiamerei

8
Questa tecnica è documentata in docs.angularjs.org/guide/forms , nella sezione Convalida personalizzata.
Nikhil Dabas

1
@ Mark Rajcok nel violino fornito, mentre si fa clic su Carica dati - tutto minuscolo, mi aspettavo che il valore del modello fosse TUTTO MAIUSCOLO, ma il valore del modello era in piccolo. Potresti pls. spiegare perché e come rendere il modello sempre IN MAIUSCOLO
Rajkamal Subramanian

1
@rajkamal, poiché loadData2 () modifica $scopedirettamente, questo è ciò su cui verrà impostato il modello ... finché l'utente non interagisce con la casella di testo. A quel punto, qualsiasi parser può quindi influenzare il valore del modello. Oltre a un parser, potresti aggiungere un $ watch al tuo controller per trasformare il valore del modello.
Mark Rajcok
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.