Transformation avec ngModel

Transformer des données entre un élément HTML et un objet javascript avec AngularJS n'est pas aussi simple qu'on pourrait le croire au premier abord. Le développeur débutant utilisera souvent un attribut de directive dédié à la valeur alors que ngModel est là pour ce genre de besoin. Cependant, ngModel n'est pas des plus simples à comprendre et on peut facilement se retrouver à faire n'importe quoi. Nous allons découvrir par l'exemple une directive qui prend du texte en minuscule et qui va donner à ngModel une version modifiée en majuscule.

Dans cet exemple, nous avons un textarea qui possède une directive to-upper visant à transformer le texte tapé en majuscule et à le stocker dans le modèle donné. On affiche bêtement la valeur du modèle en dessous, et l'input final sert à constater ce qu'il se passe quand on change la valeur du modèle sans contrainte. On voit bien que la valeur passe bien des majuscule aux minuscules sans soucis. Pour arriver à ce résultat, il nous faudra utiliser la méthode $render et ajouter un $parser à notre ngModel. Mais commençons par le début : une directive qui utilise le controller ngModel parent.

Nous partirons du template suivant :

<div>  
  <textarea
    to-upper
    ng-model="text"></textarea>
  {{ text }}
  <input ng-model="text">
</div>  

Il nous faut donc écrire la directive to-upper :

angular.module('hadrien.eu')  
.directive('toUpper', function () {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function (scope, element, attrs, ngModel) {
        // …
    }
  };
};

La première étape consiste donc à appeller le controller ngModel de la balise. Nous faisons ça à l'aide de require auquel nous passons le nom de la directive, ou un array de noms de directive, si on en veut d'autre. Nous ne voulons que ngModel. Ce controller est alors injecté en tant que quatrième paramètre de la fonction link. Si vous aviez requis un array de controlleurs, vous auriez alors un array ici aussi.

angular.module('hadrien.eu')  
.directive('toUpper', function () {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function (scope, element, attrs, ngModel) {
        ngModel.$render = function () {
            element.val(ngModel.$viewValue.toLowerCase());
        };
        // …
    }
  };
};

Nous allons spécifier une méthode $render pour notre ngModel. Cette méthode sera appellée à chaque fois que la valeur du modèle changera. Ici, nous prenons la valeur, nous la transformons en minuscule et nous la plaçons en tant que valeur de notre textarea. Chaque fois que le modèle sera changé par un autre composant de l'app, le textarea sera mis à jour avec une valeur transformée. On peut l'essayer avec l'input.

angular.module('hadrien.eu')  
.directive('toUpper', function () {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function (scope, element, attrs, ngModel) {
        ngModel.$render = function () {
            element.val(ngModel.$viewValue.toLowerCase());
        };
        // …

        element.bind('keyup', function () {
          ngModel.$setViewValue(element.val());
        });
    }
  };
};

C'est l'étape la plus simple : on écoute les évenement de l'UI, le keyup dans mon exemple, mais ça pourrait être le click, le change, etc et on envoie la valeur du textarea dans le modèle grâce à $setViewValue. On ne fait pas encore de transformation.

angular.module('hadrien.eu')  
.directive('toUpper', function () {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function (scope, element, attrs, ngModel) {
        ngModel.$render = function () {
            element.val(ngModel.$viewValue.toLowerCase());
        };

        ngModel.$parsers.push(function (value) {
          return value.toUpperCase();
        });

        element.bind('keyup', function () {
          ngModel.$setViewValue(element.val());
        });
    }
  };
};

C'est avec $parsers que nous transformons le modèle. C'est important d'opérer dans cet ordre, car le textarea pourrait avoir plusieurs directives opérant sur le même modèle, chacune y allant de sa propre transformation.

Et c'est tout ! Il ne reste plus qu'à écrire des méthode de transformation dans un sens et dans l'autre (car je doute que vous ayez besoin de transformer du texte en majuscule) dans un controller afin de pouvoir les tester facilement.

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu