Blog

Utiliser les modèles dans AngularJS

Dans cet article, nous allons effectuer une séparation efficace des contrôleurs et des modèles dans une application AngularJS afin de rendre notre code plus simple, lisible et maintenable.

Le contrôleur pas tout à fait modèle

La mauvaise interprétation du célèbre patron d’architecture MVC, utilisé par AngularJS, a souvent pour conséquence une utilisation abusive des contrôleurs en leur donnant également le comportement des modèles. Pourtant, comprendre comment les différentes parties du MVC intéragissent entre-elles dans un framework est important pour l’évolution et la maintenabilité d’une application.

Vous avez surement rencontré des exemples de contrôleurs qui gèrent l’état des données ou encore implémentent de la logique métier alors qu’ils ne devraient probablement pas.
La responsabilité d’un contrôleur est de communiquer avec le modèle et non d’être le modèle.

Par exemple :

App.controller('StockCtrl', function($location, $http) {
    this.products = [];

    this.showProduct = function(product) {
        $location.path('/product').search('id', product.id);
    };

    this.deleteProduct = function(product) {
        $http.post('/product/delete', product)
        .success(function(data) {
            this.products.splice(products.indexOf(product), 1);
        });
    };

    this.stockPrice = function() {
        return this.products.reduce(function(sum, product) {
            return (product.price * product.count) + sum;
        }, 0);
    };

    $http.get('/products')
    .success(products(data) {
        this.products = data;
    });

});

Ce contrôleur, par ailleurs semblable à ceux que l’on retrouve dans la documentation d’AngularJS : https://docs.angularjs.org/api/ng/directive/ngController, permet de gérer un stock de produits.

Pour un prototype, ce contrôleur est suffisant. Dans le cas d’une applications plus conséquente, c’est une mauvaise pratique. En effet avec ce code, comment ré-utiliser l’action de suppression d’un produit dans un autre contrôleur ? Cette implémentation va nous faire dupliquer du code ou utiliser des hacks pour arriver à nos fins.

Un modèle qui gère ses données

Pour remédier à ce problème, nous allons utiliser le Modèle du patron MVC et déplacer toute la logique métier et surtout confier la responsabilité de l’état des données à notre modèle.
Pour créer notre modèle, nous allons utiliser une Factory

App.factory('Product', function($http) {
    var Product = {};
    Product.products = [];

    Product.getAll = function() {
        return $http.get('/products')
        .success(function(data) {
            this.products = data;
        });
    };

    Product.delete = function(product) {
        $http.post('/post/delete', product)
        .success(function() {
            this.products.splice(this.products.indexOf(product), 1);
        });
    };

    Product.totalPrice = function() {
        return this.products.reduce(function(sum, product) {
            return (product.price * product.count) + sum;
        }, 0);
    };

    return Product;
});

Et voila, nous avons désormais un modèle injectable que nous pouvons facilement ré-utiliser dans le reste de notre application.

Simplifier le contrôleur

Ensuite nous allons faire subir une cure d’amaigrissement à notre contrôleur pour n’avoir plus aucune création ou modification de données dans celui-ci.

App.controller('StockCtrl', function($location, Product) {
    this.products = Product.products;

    this.showproduct = function(product) {
        $location.path('/product').search('id', product.id);
    };

    this.deleteproduct = function(product) {
        Product.delete(product);
    };

    this.stockPrice = function() {
        Product.totalPrice();
    };

    Product.getAll()
    .then(function() {
        this.products = Product.products;
    }.bind(this));

});

Le contrôleur est simplifié, plus lisible et n’est plus responsable de l’état des données. C’est important car les contrôleurs sont crées et détruits souvent dans le cycle de vie d’une application AngularJS.

Ré-utilisons notre modèle

Nous pouvons maintenant utiliser le modèle Product dans le reste de notre application :

App.controller('StatCtrl', function($scope, Product) {
    $scope.products = Product.products;

    $scope.watchCollection('products', function() {
        $scope.stockPrice = Product.totalPrice();
    });
});

Ce contrôleur peut par exemple permettre la création d’un widget qui affichera le prix total de notre stock.

Notre contrôleur a retrouvé son rôle initial d’intermédiaire entre la vue et le modèle. Le code est simple et respecte la séparation entre le modèle et le contrôleur.