Blog

Adoptez la syntaxe « Controller as » d’AngularJS

La nouvelle syntaxe Controller as d’AngularJS est une manière différente de déclarer les contrôleurs qui nous permet de rendre bien plus lisible notre code grâce à l’utilisation d’espaces de nom.

Un scope pour mon contrôleur

La version 1.2 d’AngularJS a apporté une nouvelle méthode afin de faire interagir nos contrôleurs avec nos vues. Jusque là c’était l’objet $scope, une fois injecté dans notre contrôleur qui avait la responsabilité de propager les modifications entre les modèles et les vues.

La méthode « classique » permettant de créer un contrôleur avec AngularJS est de lui injecter $scope, ce qui donne :

app.controller('IndexCtrl', function($scope) {
    $scope.message = '$scope est injecté';
});

Si nous ne l’injectons pas, le contrôleur est séparé de l’objet $scope. Nous pouvons désormais utiliser le this de notre contrôleur à la place du mot-clef $scope afin de déclarer le contenu de notre contexte :

app.controller('ButtonCtrl', function() {
    this.clickCount = 0;
    this.buttonClick = function() {
        this.clickCount++;
    };

    this.buttonName = 'Cliquez moi !';
});

Pour déclarer notre contrôleur dans la vue, nous allons utiliser la nouvelle syntaxe "Controller as". Celle-ci va instancier ButtonCtrl qui sera accessible à travers le mot-clef : button

<div ng-controller="ButtonCtrl as button">
    <button ng-click="button.buttonClick()">
        {{ button.buttonName }}
    </button>

    <span> Count : {{ button.clickCount }} </span>
</div>

Cette syntaxe permet de se rapprocher de l’instanciation d’une classe JavaScript, "ButtonCtrl as button" pouvant être assimilé à var button = new ButtonCtrl. Ensuite, nous pouvons accéder aux méthodes et propriétés de ButtonCtrl en utilisant l’instance button.

La propriété controllerAs

Une directive peut avoir son propre contrôleur, pour le déclarer dynamiquement, il suffit d’utiliser la propriété controllerAs :

app.directive('myDirective', function() {
    return {
        restrict: 'E',
        template: '{{ mydir.message }}',
        controller: function() {
            this.message = 'Un peu de sucre dans votre $scope ?'
        },
        controllerAs: 'mydir'
    };
});

$routeProvider permet également l’utilisaton du mot clef controllerAs :

app.config(function($routeProvider){
    $routeProvider
    .when('/', { 
         templateUrl: 'home.html', 
         controller: 'HomeCtrl', 
         controllerAs:'home' 
    })
    .otherwise({ redirectTo: '/' });
});

ui-router le permet également avec la syntaxe suivante :

app.config(function($stateProvider){
    $stateProvider
    .state('home', {
        template: 'home.html',
        controller: 'HomeCtrl as home'
   })
});

Les contrôleurs imbriqués

Cette syntaxe permet d’imbriquer facilement des contrôleurs, c’est également possible avec $scope et $parent, mais pour des questions de lisibilité, la syntaxe "Controller as" est plus adaptée puisque elle nous permet de voir très rapidement l’héritage de nos objets.
Un exemple avec $scope :

<div ng-controller="GrandPaCtrl">
    {{ text }}
    <div ng-controller="ParentCtrl">
        {{ text }} - {{ $parent.text }}
        <div ng-controller="ChildCtrl">      
            {{ text }} - {{ $parent.text }} - {{ $parent.$parent.text }}
        </div>
    </div>
</div>

Et maintenant avec "Controller as"

<div ng-controller="GrandPaCtrl as grandpa">
    {{ grandpa.text }}
    <div ng-controller="ParentCtrl as parent">
        {{ parent.text }} - {{ grandpa.text }}
        <div ng-controller="ChildCtrl as child">      
            {{ child.text }} - {{ parent.text }} - {{ grandpa.text }}
        </div>
    </div>
</div>

Avec la version « Controller as », les choses sont tout de suite plus claires non ? De plus l’ajout d’un nouveau contrôleur dans notre DOM ne causera pas la modification des séquences de $parent.$parent…

L’object $scope est toujours très utile pour $watch, $apply, $on, etc. Néanmoins en utilisant la nouvelle syntaxe, nous lions notre contrôleur au contexte d’exécution courant au lieu d’utiliser un objet $scope. Cela permet de séparer nos actions contrôleurs des fonctionnalités spéciales de $scope qui seront accessibles uniquement en l’injectant.

Le "Controller as" peut sembler au premier abord n’être qu’un sucre syntaxique pour gérer le scope de notre contrôleur. Néanmoins, le rapprochement de nos contrôleurs à une classe JavaScript standard, la séparation avec l’objet $scope, la disparition des variables volantes comme {{ message }} grâce aux instances nommés (salvatrice pour la lecture de notre html) permettent de rendre notre code bien plus clair et lisible.

Pour jouer avec, nous vous avons préparé un jsFiddle exécutable reprenant les exemples de l’article.

Si vous voulez en savoir plus, je vous recommande de lire l’excellent article (en anglais) : Digging into Angular’s “Controller as” syntax.