125 votes

Comment faire du filtrage à double sens dans AngularJS ?

L'une des choses intéressantes qu'AngularJS peut faire est d'appliquer un filtre à une expression de liaison de données particulière, ce qui est un moyen pratique d'appliquer, par exemple, un formatage de la monnaie ou de la date spécifique à une culture aux propriétés d'un modèle. Il est également agréable de disposer de propriétés calculées sur la portée. Le problème est qu'aucune de ces fonctionnalités ne fonctionne avec des scénarios de liaison de données à double sens - seulement une liaison de données à sens unique de l'étendue à la vue. Cela semble être une omission flagrante dans une bibliothèque par ailleurs excellente - ou est-ce que je manque quelque chose ?

Sur KnockoutJS Dans le cas d'une propriété calculée, je pouvais créer une propriété calculée en lecture/écriture, ce qui me permettait de spécifier une paire de fonctions, l'une étant appelée pour obtenir la valeur de la propriété et l'autre étant appelée lorsque la propriété est définie. Cela m'a permis d'implémenter, par exemple, une entrée tenant compte de la culture - en laissant l'utilisateur taper "$1.24" et en l'analysant pour obtenir une valeur flottante dans le ViewModel, et en faisant en sorte que les changements dans le ViewModel soient reflétés dans l'entrée.

La chose la plus proche que j'ai pu trouver de similaire à cela est l'utilisation de $scope.$watch(propertyName, functionOrNGExpression); Cela me permet d'avoir une fonction invoquée lorsqu'une propriété dans le fichier $scope des changements. Mais cela ne résout pas, par exemple, le problème de la saisie sensible à la culture. Remarquez les problèmes lorsque j'essaie de modifier le $watched dans le cadre du $watch 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 de saisie devient très confus lorsque l'utilisateur commence à taper. Je l'ai amélioré en divisant 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'agit d'une amélioration par rapport à la première version, mais elle est un peu plus verbeuse, et remarquez qu'il y a toujours un problème avec l'élément parsedValue de la portée change (tapez quelque chose dans la deuxième entrée, ce qui modifie la propriété parsedValue directement. remarquez que l'entrée supérieure ne se met pas à jour). Cela peut se produire à partir 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 de mettre en œuvre ce scénario en utilisant AngularJS ? Est-ce que je manque une fonctionnalité dans la documentation ?

231voto

phaas Points 1176

Il existe une solution très élégante à ce problème, mais elle n'est pas bien documentée.

Le formatage des valeurs du modèle pour l'affichage peut être géré par la fonction | et un opérateur angulaire formatter . Il s'avère que le ngModel qui a non seulement une liste de formateurs mais aussi une liste de parseurs.

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 angulaire qui sera appliquée à ce même élément et qui dépend de la directive ngModel contrôleur

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

3. Dans le cadre de la link ajoutez vos convertisseurs personnalisés à la méthode ngModel contrôleur

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 possède déjà la directive ngModel

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

Voici un exemple de travail qui transforme le texte en minuscules dans le input et retour aux majuscules dans le modèle

Le site Documentation de l'API pour le contrôleur de modèle contient également une brève explication et un aperçu des autres méthodes disponibles.

1 votes

Y a-t-il une raison pour laquelle vous avez utilisé "ngModel" comme nom pour le quatrième paramètre dans votre fonction de liaison ? N'est-ce pas simplement un contrôleur générique pour la directive qui n'a fondamentalement rien à voir avec l'attribut ngModel ? (J'apprends encore angular ici donc je peux me tromper totalement).

8 votes

En raison de "require : ngModel'", le 4e paramètre de la fonction de liaison sera le contrôleur de la directive ngModel, c'est-à-dire le contrôleur de foo.bar, qui est une instance de ngModelController . Vous pouvez nommer le 4ème paramètre comme vous le souhaitez. (Je le nommerais ngModelCtrl .)

9 votes

Cette technique est documentée à l'adresse suivante docs.angularjs.org/guide/forms dans la section Validation personnalisée.

-3voto

Andy Joslin Points 23231

EDIT : Oh. Je n'ai pas lu votre question assez attentivement :) désolé. Cela fonctionne : http://jsfiddle.net/L57Vp/1/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X