web-dev-qa-db-fra.com

Comment faire un filtrage bidirectionnel dans AngularJS?

AngularJS peut par exemple appliquer un filtre à une expression de liaison de données particulière, ce qui constitue un moyen pratique d’appliquer, par exemple, une mise en forme spécifique à la culture ou à la date des propriétés d’un modèle. Il est également agréable d’avoir des propriétés calculées sur l’étendue. Le problème est qu'aucune de ces fonctionnalités ne fonctionne avec des scénarios de liaison de données bidirectionnels - uniquement des liaisons de données unidirectionnelles de l'étendue à la vue. Cela semble être une omission flagrante dans une bibliothèque par ailleurs excellente - ou est-ce que quelque chose me manque?

Dans KnockoutJS , je pouvais créer une propriété calculée en lecture/écriture, qui me permettait de spécifier une paire de fonctions, une qui est appelée pour obtenir la valeur de la propriété et une qui est appelée lorsque la propriété est réglé. Cela m’a permis d’implémenter, par exemple, une entrée tenant compte de la culture: laisser l’utilisateur saisir "$ 1,24" et l’analyser dans un float dans le ViewModel, et faire en sorte que les modifications apportées à ViewModel soient reflétées dans l’entrée.

La chose la plus proche que je pourrais trouver similaire à ceci est l'utilisation de $scope.$watch(propertyName, functionOrNGExpression); Cela me permet d'avoir une fonction invoquée quand une propriété dans le $scope changements. Mais cela ne résout pas, par exemple, le problème des entrées tenant compte de la culture. Notez les problèmes lorsque j'essaie de modifier le $watched propriété dans la $watch méthode elle-même:

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

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

L'élément d'entrée devient très confus lorsque l'utilisateur commence à taper. Je l'ai amélioré en scindant la propriété en deux propriétés, une pour la valeur non analysée et une pour la valeur analysée:

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

Il s’agissait d’une amélioration par rapport à la première version, mais elle est un peu plus commentée et remarque qu’il existe toujours un problème avec la propriété parsedValue des modifications apportées à la portée (tapez quelque chose dans la deuxième entrée, ce qui modifie la propriété parsedValue directement, remarquez que la première entrée ne se met pas à jour). Cela peut provenir d'une action du contrôleur ou du chargement de données à partir d'un service de données.

Existe-t-il un moyen plus simple d'implémenter ce scénario avec AngularJS? Me manque-t-il des fonctionnalités dans la documentation?

123
Jeremy Bell

Il s'avère qu'il existe une solution très élégante à cela, mais ce n'est pas bien documenté.

Le formatage des valeurs de modèle pour l'affichage peut être géré par le | opérateur et un angular formatter. Il s’avère que le ngModel n’a pas seulement une liste de formateurs, mais également une liste d’analyseurs.

1. Utilisez ng-model pour créer la liaison de données bidirectionnelle

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

2. Créez une directive dans votre module angular qui sera appliqué au même élément et qui dépend du contrôleur ngModel

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

3. Dans la méthode link, ajoutez vos convertisseurs personnalisés au contrôleur ngModel.

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

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

4. Ajoutez votre nouvelle directive au même élément qui a déjà le ngModel

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

Voici un exemple de travail qui transforme le texte en minuscule dans le input et en majuscule dans le modèle.

Le Documentation API pour le contrôleur de modèle contient également une brève explication et une vue d'ensemble des autres méthodes disponibles.

230
phaas