Que sont les filtres ?

Les données et AngularJS

La particularité d'un framework est de proposer d'imposer une méthode pour coder proprement. On a déjà vu lors du chapitre sur la logique d'AngularJS comment s'articulent la vue, le modèle et le contrôleur pour générer puis afficher une page au visiteur. De la même façon, les données — data en anglais — sont traitées via différents outils par AngularJS :

Les services
Les services sont des objets instanciés par AngularJS pour réaliser des tâches nécessaires au bon déroulement de l'application. Celles-ci peuvent être des requêtes plus ou moins prédéfinies ou encore des méthodes qui seront utilisées par des contrôleurs, des directives, des filtres ou d'autres services. Les services permettent donc d'ajouter à n'importe quel autre élément des fonctionnalités bien précises.
Voir le chapitre dédié aux services
Les contrôleurs
Leur rôle est de rendre les données disponibles pour les scopes qui en ont besoin. Si vous vous rappelez du paragraphe sur le modèle, les scopes sont des domaines dans lesquels les variables peuvent exister. Autrement dit les contrôleurs se chargent d'intégrer les données utiles là où on en a besoin.
Relire le paragraphe sur les contrôleurs du chapitre 1.4
Les directives
Les directives se chargent de la présentation des informations recueillies. Plus précisément, elles créent des blocs valides HTML5 (par exemple des <div></div>) et qui réagiront de façon prédéfinie à partir de balises personnalisées (pourquoi pas <modal class="dark"></modal>).
Voir le chapitre dédié aux directives
Les filtres
Ils formatent les données exclusivement pour l'affichage. Ainsi, les données ne sont absolument pas modifiées par les filtres, mais simplement présentées d'une certaine façon correspondant à un modèle prédéfini.
Date française JJ-MM-AAAA (ex. : 14-02-2010)
Date américaine MM-JJ-AA (ex. : 02-14-10)
Deux modèles de date différents

Pourquoi utiliser les filtres ?

Les filtres sont une pierre angulaire :) de ce système et il serait dommage de ne pas s'en servir. Pour ceux qui se demanderaient à quoi cela peut bien servir, voici quelques bonnes raisons de vous familiariser avec les filtres :

La portabilité
Pour être clair, prenons un exemple : imaginez que vous développiez une application de paiement et que vous souhaitiez la décliner dans différents pays et devises. Non seulement il faudra changer le symbole de la monnaie, mais il faudra aussi adapter le format des tarifs (5 000,00 € en France — £ 5,000.00 en Grande-Bretagne)
La lisibilité
Si quelqu'un (ou vous-même après avoir laissé un projet de côté) récupère votre code AngularJS, les filtres ont une syntaxe très claire qui permet de très facilement comprendre les opérations qu'ils sont censés réaliser. Ils offrent donc un gain de temps considérable !

Il existe des filtres prédéfinis par AngularJS mais si cela ne vous suffit pas (et ça risque de vite arriver si vous développez une application poussée) vous pouvez évidemment en coder d'autres.

Comment utiliser les filtres ?

On peut faire appel à un filtre depuis le fichier correspondant au template de la page, pour rappel, c'est un fichier HTML (ex. : partials/home.html) ou depuis le code AngularJS directement (que ce soit un contrôleur, un service, une directive ou même un autre filtre).

Dans un template

Pour utiliser un filtre depuis un template, il suffit de signaler à AngularJS que l'on va évaluer une expression avec les doubles accolades et d'y spécifier à l'intérieur tous les éléments nécessaires :

{{ donnees | filtre : param1 : param2 }}
donnees
Les données peuvent être de différents types selon les filtres utilisés (ex. : number, String, array, ...)
filtre
Vous vous en doutez, on précise ici le nom du filtre utilisé... logique :)
param1, param2, ...
Les paramètres à passer au filtre. Vous pouvez n'en passer aucun ou autant que vous voulez, cela dépend encore une fois du filtre utilisé... toujours logique :D

Si vous le souhaitez, il est tout à fait possible d'enchaîner des filtres dans une expression AngularJS. Il suffit de mettre les filtres à la suite les uns des autres avec leurs paramètres respectifs le cas échéant, en les séparant avec un |.
{{ donnees | filtre1 : param1 : param2 | filtre2 | filtre3 : param1 : param2 }}
L'ordre d'exécution sera logiquement : filtre1 puis filtre2 puis filtre3

Dans le code Javascript

Bien que ce cas d'utilisation soit plus rare, surtout quand on débute avec AngularJS, on peut tout à fait employer les filtres directement dans le code Javascript.
En reprenant les éléments vus plus haut, la syntaxe est la suivante (rien de sorcier) :

var donneesFiltrees = filtre(donnees, param1, param2);
donneesFiltrees
Comme on est cette fois au sein d'un code, il s'agit de récupérer la valeur retournée par notre filtre pour pouvoir s'en servir par la suite.


Simplement, pour pouvoir utiliser un filtre, il faut bien sûr se placer dans un module AngularJS, que ce soit dans un contrôleur, une directive ou autre afin que le système d'injection de dépendances (Relire le paragraphe sur la Dependency Injection) puisse justement appeler le filtre demandé. Il y a alors 2 façons différentes de procéder :

Avec le service $filter

En intégrant le service $filter dans les dépendances du module concerné, on peut ensuite utiliser n'importe quel filtre :

var demo = angular.module('demo', [])
  .controller('demoCtrl', ['$scope', '$filter',
    function($scope, $filter){
        
        // définition du tableau 'voitures'
        $scope.voitures = [
            {marque: 'Renault' , prix: 10000},
            {marque: 'Peugeot' , prix: 15000},
            {marque: 'Citroen' , prix: 20000},
            {marque: 'BMW'     , prix: 35000},
            {marque: 'Volvo'   , prix: 30000}
        ];

        // Récupération du filtre 'orderBy'
        var orderBy = $filter('orderBy');

        // utilisation du filtre 'orderBy'
        $scope.voituresTrieesParMarque = orderBy(voitures, marque);
    }
  ]);

En appelant directement le filtre concerné

Pour signaler qu'un module a besoin d'un filtre précis, on peut l'ajouter dans les dépendances mais en ajoutant au nom du filtre le suffixe Filter :

var demo = angular.module('demo', [])
  .controller('demoCtrl', ['$scope', 'orderByFilter',
    function($scope, orderByFilter){
        
        // définition du tableau 'voitures'
        $scope.voitures = [
            {marque: 'Renault' , prix: 10000},
            {marque: 'Peugeot' , prix: 15000},
            {marque: 'Citroen' , prix: 20000},
            {marque: 'BMW'     , prix: 35000},
            {marque: 'Volvo'   , prix: 30000}
        ];

        // utilisation du filtre 'orderBy'
        $scope.voituresTrieesParMarque = orderByFilter(voitures, marque);
    }
  ]);

Ces deux syntaxes fonctionnent parfaitement (même après minification si les dépendances sont bien signalées).

Évidemment, on utilisera la première syntaxe lorsqu'on a besoin de plusieurs filtres dans le même module plutôt que de tous les lister dans les dépendances. Si vous le souhaitez, vous pouvez utiliser la seconde syntaxe pour éviter d'appeler le service $filter lorsque vous ne faîtes appel qu'à un filtre précis mais ce n'est pas du tout obligatoire.

Pour être sûr de ne pas se tromper, le plus simple semble encore d'utiliser toujours la même version, et donc de privilégier l'utilisation du service $filter.

Les filtres natifs

Pour illustrer les différents filtres natifs, je vous propose à chaque fois les liens des exemples intéractifs proposés par la documentation officielle. Ils sont hébergés par Plunker, ce qui permet de les modifier et de voir comment fonctionne le code. Donc n'hésitez pas à tout changer ! ;)

lowercase / uppercase

Ces deux filtres sont certainement les plus faciles à comprendre : ils transforment le texe donné minuscules (lowercase) ou en majuscules (uppercase)

{{ input | lowercase }}
     -- OU --
{{ input | uppercase }}
input
String : La chaîne de caractères à transformer.

number

Le filtre number permet de formater un nombre donné à la mode anglo-saxonne, c'est-à-dire avec des virgules "," pour séparer les groupes de centaines — "5,784,387,644.2478".
Pour information, il retourne une chaîne de caractères (sans quoi il serait impossible d'ajouter des virgules évidemment).

{{ input | number : fractionSize}}
input
number : Le nombre que vous souhaitez formater. Si vous passez quoi que ce soit d'autre, la chaîne de caractères retournée sera vide.
fractionSize
optionnel
number : Le nombre de chiffres après la "virgule" (au sens francophone, donc bien après le chiffre des unités).
Valeur par défaut : 3

Exemple intéractif de la documentation officielle

currency

Il reprend exactement le filtre number ci-dessus et y ajoute simplement le symbole que vous souhaitez pour afficher un prix à partir d'un simple nombre.

{{ input | currency : symbol : fractionSize}}
input
number : Le nombre que vous souhaitez formater. Si vous passez quoi que ce soit d'autre, la chaîne de caractères retournée sera vide.
symbol
optionnel
String : Le symbole de la monnaie à afficher. Par exemple, "€"
Valeur par défaut : valeur correspondant à la monnaie définie par le navigateur.
fractionSize
optionnel
number : Le nombre de chiffres après la "virgule".
Valeur par défaut : valeur correspondant à la monnaie définie par le navigateur.

Exemple intéractif de la documentation officielle

json

Comme le dit la documentation, ce filtre sert principalement pour déboguer votre application, à moins que vous teniez un site sur le développement bien entendu :). Il permet de transformer n'importe quel objet Javascript en une chaîne de caractères conforme JSON.
Explications de la norme JSON

{{ input | json : spacing }}
input
L'objet à transformer. Tout objet JavaScript peut être passé.
spacing
optionnel
number : Le nombre d'espace correspondant à une indentation.
Valeur par défaut : 2

Exemple intéractif de la documentation officielle

limitTo

Très utile pour limiter les entrées d'un tableau ou découper une chaîne de caractères, ce filtre va vite se rendre indispensable. Il est assez simple d'utilisation mais reste néanmoins très souple !

{{ input | limitTo : limit : begin }}
input
L'entrée à limiter. Elle peut être de différents types :
  • Array : Si vous mettez un tableau en entrée, le filtre se contentera de retourner un tableau extrait de celui-ci.
  • String : En passant une chaîne de caractères, le filtre vous retournera une chaîne de caractères extraite de celle-ci.
  • number : Le filtre agit avec les nombres exactement comme avec les chaînes de caractères. Petit détail, l'objet retourné sera bien une chaîne de caractères !
limit
number ou String : La longueur du tableau ou de la chaîne à retourner. Logiquement si la limite donnée est plus grande que la taille de l'input, elle ne sera pas prise en compte. D'autre part, vous pouvez donner un nombre négatif pour sélectionner les entrées du tableau (ou de la chaîne bien entendu) en partant de la fin.
Si vous n'entrez pas de limit, le filtre retourne exactement la même chose que l'entrée.
begin
optionnel
number ou String : Indice à partir duquel le filtre commence à extraire les entrées du tableau. Encore une fois, vous pouvez passer un nombre négatif pour compter à rebours en partant de la fin de l'entrée.
Valeur par défaut : 0

Exemple intéractif de la documentation officielle

date

Ce filtre est indispensable lorsque vous souhaitez afficher une date. Inutile de vous embêter à la formater à la main, ne passez plus que par ce filtre ! De cette façon, vous serez assuré d'avoir toutes vos dates enregistrées uniformément en base de données et vous pourrez les adapter à vos visiteurs sans effort. Voici comment l'utiliser :

{{ input | date : format : timezone }}
input
La date à formater. Elle peut être donnée sous trois formes différentes (ici, tous les exemples donnent exactement la même date) :
  • number en UTC milliseconds1424955830796
    Pour rappel, c'est le nombre de millisecondes écoulées depuis le 1er janvier 1970 00:00:00 exprimé en UTC.
  • String
    • UTC milliseconds"1424955830796"
    • Une date au format ISO 8601"2015-02-26T13:03:51Z"
  • Datenew Date(2015,02,26,13,03,51)
    Pour pouvoir utiliser un objet de ce type, il faut évidemment passer par du Javascript, que ce soit en amont via le contrôleur ou en appelant le filtre directement dans un module.

Ici on compte bien les millisecondes alors que souvent on parle de dates UTC en secondes. Il faut donc faire particulièrement attention aux éventuelles conversions lors de la communication avec la BDD. Par exemple, les Timestamp UNIX sont bien en secondes.
Obtenir la date UTC actuelle en ligne (et bien plus) (EN)

format
optionnel
String : Le modèle selon lequel la date sera formatée.
Pour voir toutes les possibilités de modèle, je vous renvoie à la documentation officielle. Même si elle est en anglais, on peut parfaitement comprendre les explications grâce aux exemples.
Valeur par défaut : "MMM d, y"
timezone
optionnel
String : La zone horaire de la date à formater. Ce paramètre est obligatoirement du type.
Il suffit de préciser le décalage horaire par rapport au méridien de Greenwich, par exemple '-0230' pour 2 heures 30 minutes de moins.
Valeur par défaut : Zone du navigateur du client

Exemple intéractif de la documentation officielle

orderBy

{{ input | orderBy : expression : reverse }}
input
Array : Le tableau à trier.
expression
Le prédicat permettant d'ordonnancer le tableau. ;)
Autrement dit, la logique à suivre pour trier les entrées du tableau. Elle peut être exprimée de différentes façons :

Ces deux derniers filtres (orderBy et filter) étant plus difficiles à maîtriser, je vous ai directement intégré les exemples. Regardez le code qui se cache derrière à l'aide des onglets code > index.html/script.js ! Sans quoi ces exemples ne sont pas très utiles... :)
Pour l'ensemble de ces exemples, j'ai utilisé exactement le même tableau amis.

  • String : Dans le cas, relativement courant, où le tableau est un ensemble d'objets homogènes avec des attributs, vous pouvez passer pour prédicat un des attributs avec l'ordre de tri en même temps.
    Ici on a trié les amis par âge décroissant : '-age'.

    Autre exemple un peu plus évolué de l'utilisation du filtre avec une chaîne de caractères comme prédicat :

  • function : Pour opérer un tri, vous pouvez aussi utiliser une fonction personnalisée. Elle doit retourner un nombre pour chaque entrée. Celui-ci sera ensuite comparé aux autres nombres obtenus avec les différentes valeurs afin de trier le tableau.
    Par exemple, on peut imaginer une fonction qui trie les amis en fonction de la longueur de leur nom et de façon décroissante:
  • Array [ String | function ] : Un tableau composé de prédicats comme expliqué au-dessus. Le premier sera utilisé d'abord puis si il y a équivalence pour deux entrées, le second prédicat sera invoqué et ainsi de suite.
    Par exemple, le tri précédent n'étant pas très judicieux (tous ont un nom de 4 lettres sauf Julie...), on peut trier d'abord par longueur de nom (décroissante) puis par âge (croissant) :
    On remarquera que pour obtenir le premier tri par longueur de nom décroissante, on ajoute un moins dans la fonction customFunction
reverse
optionnel
boolean : Pour inverser l'ordre final du tableau.
Valeur par défaut : false

filter

{{ input | filter : expression : comparator }}
input
Array : Le tableau dont les entrées doivent être filtrées.
expression
Le prédicat permettant de filtrer les entrées du tableau. ;)
Comme pour le filtre orderBy, la logique à suivre pour filtrer les entrées du tableau. Encore une fois, elle peut être exprimée de différentes façons :
  • String : Toutes les entrées du tableau ayant une propriété au sein de laquelle la chaîne de caractères peut être trouvée sont retournées.

    Pour inverser la recherche et donc afficher tous les résultats ne contenant pas la chaîne de caractères entrée, il suffit d'ajouter un !. Essayez avec l'exmple ci-dessous.

  • Object : À la place d'une chaîne de caractères, vous pouvez aussi passer un objet comme prédicat. L'intérêt est de pouvoir préciser quel attribut doit avoir une valeur correspondant à votre recherche. Bien entendu, vous pouvez toujours utiliser le ! pour inverser les résultats.

    Pour rechercher une valeur parmi tous les attributs des entrées, et ce quelle que soit le niveau, vous pouvez utiliser le $. Si vous ne précisez pas de parent autre que la racine avant le $, cela revient à filtrer avec une chaîne de caractères.

  • function : Pour pouvoir filtrer les entrées de façon plus spécifique, il est possible d'utiliser une fonction personnalisée. Pour chaque entrée, celle-ci devra retourner un boolean (true ou false) pour savoir si l'entrée en question doit être gardée.
    Par exemple, on peut imaginer une fonction qui ne retient que les amis ayant un âge supérieur à celui exigé :
comparator
optionnel
Le comparateur permet de savoir si la valeur requise par l'expression et celle de l'entrée étudiée doivent être considérées comme identiques.
Autrement dit, ce paramètre permet de préciser l'exigence requise pour appliquer le filtre. Il peut être de différents types :
  • undefined : Si vous ne passez aucun paramètre. Cela équivaut au boolean false.
  • function(actual, expected) : Un fonction qui prend pour paramètres les deux valeurs à comparer et retourne un boolean pour savoir si l'entrée doit être préservée.
  • boolean :
    • true : équivaut à utiliser la fonction angular.equals(actual, expected).
    • false : pour une recherche insensible à la casse.

Il ne faut pas confondre le filtre filter proposé par AngularJS avec la méthode native de Javascript .filter() que nous avons déjà utilisée pour la Todo List au chapitre Votre premier module AngularJS dans la méthode .clearCompletedTodos().
Documentation sur la méthode .filter()

Créer son propre filtre

Bien que tous ces filtres couvrent déjà beaucoup de cas d'utilisation, AngularJS laisse évidemment le développeur libre de créer ses propres filtres correspondant à des situations plus spécifiques.

Si vous avez regardé l'exemple intéractif proposé par la documentation officielle pour le filtre natif number, vous aurez sûrement remarqué que le symbole de la devise est placé directement avant le montant du prix. Et ce, même si l'on passe en argument le symbole .

Présentation du filtre et du contexte

Nous allons donc créer notre propre filtre permettant l'affichage d'un prix à la façon francophone"51.12 €" — que nous appellerons (par manque d'imagination) currencyFR.
Celui-ci recevra en entrée un nombre (il faudra vérifier que la donnée soit effectivement du type number), puis il retournera une chaîne de caractères composée du montant arrangé à la façon des prix (avec deux décimales toujours exposées) et enfin le symbole de la devise, séparé du montant par un simple espace. De plus, on fera en sorte que notre filtre accepte un paramètre facultatif : le symbole. Si aucun argument n'est passé, il utilisera le symbole , sinon il utilisera celui demandé.

Comme nous appliquerons ce filtre à une petite liste de smartphones avec leur prix respectif, il nous faut, pour commencer, créer la vue et le contrôleur nécessaires.

Bien sûr, cette mini-application est plus que rudimentaire, mais elle a pour seule but de montrer comment construire et utiliser ses propres filtres.

La vue

Rien de bien sorcier, un simple fichier HTML avec un tableau pour afficher les résultats :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Exemple de filtre créé : currencyFR</title>        
        <script src="js/vendor/angular.js"></script>
        <script src="js/script.js"></script>
    </head>
    <body ng-app="customFilterApp">
        <div ng-controller="customFilterController">
            <h4>Smartphones disponibles</h4>
            <table>
                <tbody>
                <tr>
                    <th>Modèle</th>
                    <th>Prix (euros)</th>
                    <th>Prix (francs)</th>
                </tr>
                <tr ng-repeat="smartphone in smartphones">
                    <td>{{smartphone.modele}}</td>
                    <td>{{smartphone.prix | currencyFR}}</td>
                    <td>{{smartphone.prix*6.55957 | currencyFR:"FF"}}</td>
                </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

On remarque bien entendu l'utilisation du filtre aux lignes 21 et 22 : encore une fois, rien de spécial jusqu'ici, on appelle le filtre comme on l'a vu avec les filtres natifs.

Le contrôleur

On crée les modules habituels et on initialise le tableau contenant les smartphones :

'use strict';
angular.module('customFilterExample', [])
    .controller('customFilterController', ['$scope', function($scope) {
        $scope.smartphones = 
          [{modele:'Wiko Wax', prix:110},
           {modele:'Galaxy SIII', prix:300},
           {modele:'Galaxy SV', prix:500},
           {modele:'iPhone 5', prix:499.99},
           {modele:'iPhone 6', prix:699.99}];
    }]);

Le filtre currencyFR

Maintenant que tout est prêt, on peut s'attaquer à ce qui nous intéresse : le code de currencyFR.

Tout d'abord, il faut dire à AngularJS que l'on va ajouter un filtre au module customFilterExample. Pour cela, on utilise la méthode .filter() tout comme lorsque l'on souhaite déclarer un contrôleur avec la méthode .controler().

      {modele:'iPhone 6', prix:699.99}]; // fin du code du contrôleur 
  }])
    .filter('currencyFR', ['numberFilter', function(numberFilter){
        return function(input, symbol){
            // le code de notre filtre
        };
    }]);

Quelques explications :

ligne 10
On enlève le point-virgule ; qui était à la ligne 10 pour pouvoir continuer à agir sur l'objet angular.module('customFilterExample', [])
ligne 11
On déclare notre filtre en lui donnant son nom. D'autre part, comme on l'a vu lors du paragraphe sur l'Injection de Dépendances (Relire le paragraphe sur la DI du chapitre 1.4), en même temps que l'on déclare un élément de AngularJS, on doit signaler ses dépendances. Or ici, pour nous simplifier la vie, on va réutiliser le filtre number pour afficher correctement le montant du prix.
ligne 12
La fonction du filtre renvoie... une fonction ! Eh oui, tout filtre doit renvoyer une fonction. C'est elle qui sera exécutée par AngularJS pour formater les données. Un peu étrange, mais logique. Et cette fonction prend au moins un argument : l'entrée du filtre (qu'on appelle communément input). Puis elle peut prendre un ou plusieurs autres arguments. Ce sont les paramètres du filtre. Ici on veut que notre filtre soit capable de changer le symbole de la devise, d'où le deuxième et dernier argument symbol.

Encore une fois, une confusion est possible. On appelle une méthode .filter() pour ajouter un filtre au module. Or on vient de revoir qu'une autre méthode .filter() native de JavaScript existait déjà. Rassurez-vous, comme nous travaillons sur un objet créé par AngularJS, le navigateur saura quelle méthode utiliser et nous n'aurons pas de problème pour créer notre filtre.

Maintenant que le plus dur est fait (du moins la partie spécifique à AngularJS), il ne nous reste plus qu'à coder le cœur du filtre :

    .filter('currencyFR', ['numberFilter', function(numberFilter){
        return function(input, symbol){
            if(isNaN(input)){                   // on vérifie que l'entrée soit effectivement un nombre
                return input;                   // sinon on renvoie tout simplement l'entrée
            } else{
                symbol = symbol || "€" ;        // € par défaut, sinon, on récupère le symbole donné
                input = numberFilter(input,2);  // utilisation du filtre number pour afficher le montant
                return input + ' ' + symbol;    // on retourne la chaîne entière
            }
        };
    }]);

Rien de spécial dans cette fonction. Petite précision tout de même : si vous ne la connaissiez pas, la fonction isNan() native de JavaScript permet de dire si une valeur n'est pas un nombre (Not A Number en anglais). Elle renvoie donc le boolean false si l'entrée est un nombre ou une chaîne de caractères contenant exclusivement un nombre (comme "32.17" par exemple). Attention cependant, cette fonction peut avoir des comportements étranges, notamment avec les chaînes de caractère vides ou ne contenant que des espaces.
Documentation Mozilla Developer Network : isNaN() (FR)

Version finale et démo

Finalement, voici la version finale du JavaScript de notre mini-application avec notre filtre currencyFR :

'use strict';
angular.module('customFilterExample', [])
    .controller('customFilterController', ['$scope', function($scope) {
        $scope.smartphones = 
          [{modele:'Wiko Wax', prix:110},
           {modele:'Galaxy SIII', prix:300},
           {modele:'Galaxy SV', prix:500},
           {modele:'iPhone 5', prix:499.99},
           {modele:'iPhone 6', prix:699.99}];
    }])
    .filter('currencyFR', ['numberFilter', function(numberFilter){
        return function(input, symbol){
            if(isNaN(input)){                   // on vérifie que l'entrée soit effectivement un nombre
                return input;                   // sinon on renvoie tout simplement l'entrée
            } else{
                symbol = symbol || "€" ;        // € par défaut, sinon, on récupère le symbole donné
                input = numberFilter(input,2);  // utilisation du filtre number pour afficher le montant
                return input + ' ' + symbol;    // on retourne la chaîne entière
            }
        };
    }]);

Et la live-demo que vous pouvez modifier comme vous le souhaitez via Plunker :

Ici notre application customFilterExample est excessivement simple. C'est pourquoi je n'ai pas pris la peine de créer différents modules. Cependant, lorsque vous créerez de vraies applications, il est vivement recommandé d'organiser vos éléments AngularJS. Par exemple, on créerait ici un module customFilterFilters contenant tous nos filtres et dont dépenderait l'application principale customFilterExample. Notre filtre currencyFR serait donc intégré à ce sous-module.
Nous reverrons toutes ses subtilités dans la partie 3 où nous développerons une véritbale application.

Une fois votre filtre correctement codé et intégré à votre projet, vous pouvez vous en servir depuis n'importe quel fichier et à n'importe quel niveau. Que ce soit dans la vue — {{ donnees | monFiltre : param1 }} — ou dans le JavaScript. Pour ce faire, vous n'avez qu'à procéder exactement de la même façon qu'avec les filtres natifs : en utilisant le service $filter ou en l'invoquant avec le suffixe Filter.

Conclusion

On a de quoi être heureux, notre filtre de monnaie française fonctionne à merveille !
Seulement, je ne sais pas si vous avez remarqué, mais il y a encore une légère différence entre notre affichage des prix et celui que l'on peut effectivement voir dans les boutiques françaises, dans les magasins ou sur internet : pour séparer le chiffre des unités des décimales, en France, mais aussi en Allemagne ou dans d'autres pays d'Europe, on utilise une virgule et non un point.

Mais comment changer ce caractère qui est directement inséré par le filtre number de AngularJS ? À moins d'aller changer le code source du framework (ce qui est stricement déconseillé bien entendu), il semble impossible de modifier le filtre natif. Et pourant, cela semblerait bête de perdre du temps à recoder un filtre qui marche très bien. On risquerait de le faire moins efficient et cela ajouterait du code inutile, etc.. Bref, ce n'est vraiment pas une solution.

i18n et l10n

Heureusement, les développeurs du framework ont déjà pensé à ce problème et AngularJS supporte les concepts d'internationalisation et localisation (respectivement abrégés i18n et l10n). Assez simplement, cela revient à dire que AngularJS peut (très) facilement s'adapter aux langues, régions et cultures.
Wikipédia : Internationalisation (informatique)

En l'occurence, si le développeur spécifie une localisation, AngularJS changera les paramètres par défaut de 3 filtres :

date
Le format par défaut de la date francophone est "d MMM y" :
"2 oct. 2010"
number
Les séparateurs entre unités et décimales est alors bien une virgule et le séparateur des groupes de milliers un simple espace :
"354 236,47"
currency
Le format par défaut est la combinaison des nombres francophones avec le symbole € après le montant, séparé d'un espace. Bref, exactement ce qu'on veut :
"54 236,47 €"

AngularJS prend aussi en charge les différents pluriels (dans certaines langues, il existe jusqu'à 5 formes différentes de pluriels !) et d'autres points très pointus. Le service gérant ces variations est $locale.
Documentation officielle sur l'internationalisation et la localisation (EN)
Documentation officielle : service $locale (EN)

Mise en place des i18n et l10n

Pour spécifier les paramètres d'internationalisation et localisation, rien de plus simple : il suffit d'appeler le fichier JavaScript correspondant à la région que vous avez choisie juste après celui de AngularJS dans le index.html.

<!DOCTYPE html>
<html lang="fr" ng-app="myFrancophoneApp">
    <head>
        <meta charset="utf-8" />
        <title>Mon application francophone</title>        
        <link rel="stylesheet" href="web/css/style.css">
        <script src="js/vendor/angular.js"></script>
        <script src="js/vendor/angular-locale_fr-fr.js"></script>
        <script src="js/script.js"></script>
    </head>
    <body>
        
    </body>
</html>

Comme lorsqu'on appelle le framework, on a le choix d'héberger le fichier directement sur notre serveur ou de passer par un CDN.

Sources AngularJS
Vous pouvez trouver le fichier dont vous avez besoin parmi les sources du projet AngularJS, sur Github. L'identifiant de chaque fichier correspond à l'identifiant de la langue en deux lettres puis à l'identifiant de la région / du pays aussi en deux lettres (dans la langue concernée). Par exemple, pour l'allemand d'Allemagne, ce sera "de-de" pour "deutsch - Deutschland".
Fichiers angular-locale directement proposés par AngularJS
CDN
Le site CDNJS met gracieusement à disposition l'ensemble des fichiers angular-locale, autant en profiter !
CDN pour les fichiers angular-locale

Ressources disponibles

Comme on vient de le voir, il existe de nombreuses ressources permettant d'atteindre nos objectifs plus rapidement, proprement et surtout simplement. Voici une petite liste non exhaustive des liens pouvant vous faciliter la vie en termes de filtres :

ngmodules.org
La plus grosse base de données en terme de modules AngularJS. Vous y trouverez toute sorte de modules mais aussi de filtres.
angular-filter
Un dépôt Github recensant de nombreux filtres AngularJS pouvant s'avérer très utiles. Vous pouvez installer tout le module avec l'intégralité des filtres ou tout simplement copier-coller celui dont vous avez besoin pour votre projet ;)
angularjs-filters
Autre dépôt en tout point semblable au précédent. À vous de voir ce qu'il vous faut.

Mot de la fin

En conclusion, il vaut mieux rechercher quelque peu les ressources disponibles et les travaux qui ont déjà été réalisés avant de se lancer dans le code la tête la première. En bon développeur, on cherche souvent à tout réinventer soi-même. Considérant notre code programme comme notre bébé, on voudrait l'avoir conçu de bout en bout — je parle d'expérience :)
Mais ce serait passer à côté de chances incroyables de gagner du temps ! D'autant que les projets disponibles sur internet sont souvent repris et améliorés par une communauté qui vise une portabilité et une efficience de code que vous n'obtiendrez pas si aisément.

Tirez pleinement partie de ces ressources et lorsque vous vous en sentirez capable, rien ne vous empêche d'apporter votre contribution en proposant vos modules, filtres, directives, etc. ou en aidant à améliorer ceux déjà existants.

Vos commentaires

comments powered by Disqus