AngularJs : Où sont mes models ?

Lorsqu’on commence à développer une nouvelle application avec le Framework Angular, il est très aisé d’obtenir un résultat rapide et fonctionnel. C’est l’un des atouts majeurs d’AngularJs vis-à-vis de ses concurrents.

Même si AngularJs semble plus proche du pattern MVVM (MVW selon Igor Mina pour ModelViewWhatever) que MVC, on fait vite l’analogie avec les frameworks MVC backends classiques que la plupart des développeurs ont l’habitude d’utiliser (Symfony, Spring, …).

The $scope object could be considered the ViewModel that is being decorated by a function that we call a Controller.

MVC. Modèle, Vue, Contrôleur… Mais au fait, où est le modèle sur Angular (angular models) ? Lorsque j’ai découvert AngularJs, ayant eu auparavant l’occasion de développer quelques grosses applications avec EmberJs, je m’attendais à hériter d’un type NgModel quelconque. Mais la philosophie Angular est radicalement différente de celle d’Ember (http://emberjs.com/guides/models) à ce sujet :

Unlike other frameworks, there is no need to inherit from proprietary types in order to wrap the model in accessors methods. Angular models are plain old JavaScript objects. This makes your code easy to test, maintain, reuse, and again free from boilerplate.

Donc en gros, un modèle, c’est un objet javascript tout simple. Voici donc un article de blog :

{
  title: "Article 1",
  description: "Gouzigouza",
  content: "The content"
}

Cela tombe bien car cela ressemble étrangement à ce que je pourrais récupérer directement depuis mon backend via une API REST en faisant quelque chose de ce genre via $http ou grâce à $resource :

function MonController($http) {

  $http.get('url/vers/mon/api/articles/1').then(function(articleData) {
    // Injection dans le scope pour afficher mon article dans une vue
    $scope.article = articleData.data;

  }).then(null, function(rejection) {
    // manage error
  });
}

Notez que la logique de récupération d’un article ici implémentée dans un controller aurait tout intérêt à être elle-même extraite dans un service dédié :

angular.module('monModule').factory('ArticlesService', function($http) {

  return {
    getArticle: function(id) {

      return $http.get('url/vers/mon/api/articles/' + id).then(function(articleData) {
        return articleData.data;
      });
    }
  }
}

Un problème simple

Tout cela est très bien mais hormis dans un contexte de consultation simple de la donnée, il est peu probable qu’il n’y ait pas un peu de traitement à faire sur cet objet pour l’utiliser. Par exemple, si je veux gérer une description courte à afficher dans ma vue.

Logique dans le controller ?

Une première approche un peu naïve serait de faire quelque chose du genre :

function MonController($http) {
  // ...
  $scope.shortDescription = function(description) {
    return description.substring(0, 100);
  };
}

Pas terrible de laisser cette logique dans notre controller. Face à ce problème, différents points de vues s’affrontent et il est parfois difficile de s’y retrouver.

La logique au sein du modèle

Une première solution serait peut-être d’implémenter notre logique justement au niveau de notre modèle en faisant quelque chose du style :

function Article (data) {

  this.title = data.title;
  this.description = data.description;
  // ...
}

Article.getShortDescription = function() {
  return this.description.substring(0, 100);
}

Ou plus simplement avec un angular.extend par exemple :

function Article (data) {

  angular.extend(this, {
    title: "",
    description: "",

    getShortDescription: function() {
      return this.description.substring(0, 100);
    }
  }, data);
}

Pour rendre ce bout de code exploitable et injectable proprement, il suffit d’utiliser une nouvelle Factory :

angular.module('monModule').factory('Article', function() {

  var Article = function (data) {
    angular.extend(this, {
      title: "",
      description: "",

      getShortDescription: function() {
        return this.description.substring(0, 100);
      }
    }, data);
  }

  return Article;
}

Il nous suffit alors de modifier notre service de récupération d’un article pour faire quelque chose de ce type :

angular.module('monModule').factory('ArticlesService', function($http, Article) {

  return {
    getArticle: function(id) {

      return $http.get('url/vers/mon/api/articles/' + id).then(function(articleData) {
        return new Article(articleData.data);
      });
    }
  }
}

Et depuis un controller par exemple :

angular.module('monModule').controller('ArticleController', function(ArticlesService) {

  ArticlesService.getArticle(100).then(function(article) {
    $scope.shortDescription = article.getShortDescription();
  }).then(null, function(article) {
    // handle error
  };
}

Une autre approche : les filtres Angular

Certains militent contre le besoin de créer des services spécifiques pour représenter un modèle. L’argument qui prévaut est le suivant :

Your model lives on the server

Grosso modo, aucune logique business ne doit être implémentée au sein d’un projet Angular et la seule logique qui appartient au front, est la logique de présentation.

L’auteur de cet article préfère traiter notre problème via l’utilisation d’un filtre Angular.

A filter formats the value of an expression for display to the user. They can be used in view templates, controllers or services and it is easy to define your own filter.

Pour répondre différemment à notre problème on peut alors implémenter un filtre de la manière suivante :

angular.module('monModule', []).filter('summerizeArticle', function() {

  return function(article) {
    return article.description.length > 100 ? article.description.substring(0, 100) + "..." : article.description;
  };
});

Et si la problématique est suffisamment générique pour être valable hors du contexte d’un article, on peut alors modifier notre filtre de la sorte :

angular.module('monModule', []).filter('summerize', function() {

  return function(input) {
    return input.length > 100 ? input.substring(0, 100) + "..." : input;
  };
});

Quelques ressources intéressantes pour aller plus loin :

Cette entrée a été publiée dans Non classé le par .

À propos Olivier Balais

Jeune ingénieur logiciel basé à Lyon (@overnetcity) passionné par les NTIC et le développement Web, je suis actuellement salarié chez Reputation VIP et effectue en parallèle des missions ponctuelles en temps que Freelance. Passionné depuis toujours par l'informatique et le développement, suite à une formation solide à l'INSA de Lyon, je me suis spécialisé dans la réalisation de bout en bout de projets web complexes.

6 réflexions au sujet de « AngularJs : Où sont mes models ? »

  1. Ping : AngularJs logicless templates : non à la logique au sein des templates

  2. Ping : AngularJs logicless templates : non à la logique au sein des templates

  3. Soullivaneuh

    Je me lance réellement depuis peu dans le monde des frameworks JS en commençant par Angular.

    Je suis tombé sur ton article en farfouillant le net pendant que je me posais la même question.

    Superbe résumé très objectifs qui décrit les deux méthodes et visions qu’on peut adopter.

    Mais je reste sur ma faim sur un point : Quel est le mieux ? Cela dépends de quoi ? Quel est ton point de vue personnel ? 🙂

    Au plaisir de te lire ! 😉

    Répondre
    1. Olivier Balais Auteur de l’article

      Bonjour @Soullivaneuh,

      En ce qui me concerne, j’ai tendance à implémenter la solution « Logique au sein du modèle » dès que possible. L’objectif étant de limiter au maximum la taille des controllers et de centraliser cette logique là où elle appartient, dans la couche model.
      J’utilise les filtres pour des rendus très génériques, complètement dissocié du modèle.

      Il faut également prendre en compte les cas où l’application développée est sans backend. Dans ce cas, la logique métier est implémentée côté client et le point de vue « Your model lives on the server » n’est plus valable.
      Je pense notamment aux applications mobiles (exemple avec : ionicframework.com).

      Merci pour ton retour.

      A bientôt

      Répondre
  4. Augustin

    Merci pour cet article.
    Je continue de penser que c’est une pierre indispensable manquante à l’édifice d’Angular. J’y ai toujours été allergique pour cette principale raison, peut-être que tu me feras changer d’avis !

    Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *