Penser à AngularJS&quot ; si j'ai une formation en jQuery ?

Supposons que j&#8217ai l&#8217habitude de développer des applications côté client avec [jQuery][2], mais que j&#8217aimerais maintenant commencer à utiliser [AngularJS][1]. Pouvez-vous me décrire le changement de paradigme qui est nécessaire ? Voici quelques questions qui pourraient vous aider à formuler une réponse :

  • Comment puis-je concevoir et architecturer différemment les applications Web côté client ? Quelle est la plus grande différence ?
  • Qu'est-ce que je devrais arrêter de faire/utiliser ; qu'est-ce que je devrais commencer à faire/utiliser à la place ?
  • Y a-t-il des considérations/restrictions du côté serveur ?

Je ne cherche pas une comparaison détaillée entre jQuery et AngularJS.

[1] : http://angularjs.org/ [2] : http://jquery.com/

Solution

1. Ne concevez pas votre page, puis modifiez-la avec des manipulations [DOM][1].

Avec jQuery, vous concevez une page, puis vous la rendez dynamique. C'est parce que jQuery a été conçu pour l'augmentation et s'est incroyablement développé à partir de cette simple prémisse. Mais dans AngularJS, vous devez commencer par la base en ayant votre architecture à l'esprit. Au lieu de commencer par penser "J'ai cette partie du DOM et je veux lui faire faire X", vous devez commencer par ce que vous voulez accomplir, puis concevoir votre application, et enfin concevoir votre vue.

2. N'augmentez pas jQuery avec AngularJS

De même, ne commencez pas avec l'idée que jQuery fait X, Y et Z, et que je vais donc ajouter AngularJS par-dessus pour les modèles et les contrôleurs. C'est très tentant lorsque l'on débute, c'est pourquoi je recommande toujours aux nouveaux développeurs AngularJS de ne pas utiliser du tout jQuery, du moins jusqu'à ce qu'ils s'habituent à faire les choses à la "manière Angular". J'ai vu de nombreux développeurs, ici et sur la liste de diffusion, créer ces solutions élaborées avec des plugins jQuery de 150 ou 200 lignes de code qu'ils collent ensuite dans AngularJS avec une collection de callbacks et de $apply qui sont confus et alambiqués ; mais ils finissent par les faire fonctionner ! Le problème est que dans la plupart des cas, ce plugin jQuery pourrait être réécrit en AngularJS en une fraction du code, où soudain tout devient compréhensible et simple. La conclusion est la suivante : lorsque vous cherchez une solution, commencez par "penser en AngularJS" ; si vous ne trouvez pas de solution, demandez à la communauté ; si, après tout cela, il n'y a pas de solution facile, alors n'hésitez pas à vous servir de jQuery. Mais ne laissez pas jQuery devenir une béquille ou vous ne maîtriserez jamais AngularJS.

3. Toujours penser en termes d'architecture

Sachez d'abord que les [applications à page unique][2] sont des applications. Ce ne sont pas des pages Web. Nous devons donc penser comme un développeur côté serveur en plus de penser comme un développeur côté client. Nous devons réfléchir à la manière de diviser notre application en composants individuels, extensibles et testables. Alors comment faire cela ? Comment " penser en AngularJS " ? Voici quelques principes généraux, contrastés par rapport à jQuery.

La vue est le "document officiel".

En jQuery, nous changeons la vue de manière programmatique. Nous pourrions avoir un menu déroulant défini comme un ul comme ceci :

<ul class="main-menu">
    <li class="active">
        <a href="#/home">Home</a>
    </li>
    <li>
        <a href="#/menu1">Menu 1</a>
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li>
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#/home">Menu 2</a>
    </li>
</ul>

En jQuery, dans notre logique d'application, nous l'activerions avec quelque chose comme :

$('.main-menu').dropdownMenu();

Lorsque nous regardons simplement la vue, il n'est pas immédiatement évident qu'il y ait une fonctionnalité ici. Pour les petites applications, c'est très bien. Mais pour les applications non triviales, les choses deviennent rapidement confuses et difficiles à maintenir. Dans AngularJS, cependant, la vue est l'enregistrement officiel de la fonctionnalité basée sur la vue. Notre déclaration `ul' ressemblerait plutôt à ceci :

<ul class="main-menu" dropdown-menu>
    ...
</ul>

Ces deux déclarations font la même chose, mais dans la version AngularJS, quiconque regarde le modèle sait ce qui est censé se passer. Chaque fois qu'un nouveau membre de l'équipe de développement arrive, il peut regarder ce modèle et savoir qu'une directive appelée dropdownMenu opère sur lui ; il n'a pas besoin d'avoir l'intuition de la bonne réponse ou de passer en revue le code. La vue nous a dit ce qui était censé se passer. Beaucoup plus propre. Les développeurs novices en AngularJS posent souvent une question du type : comment trouver tous les liens d'un type spécifique et y ajouter une directive. Le développeur est toujours sidéré lorsque nous lui répondons : vous ne le faites pas. Mais la raison pour laquelle vous ne le faites pas, c'est que c'est moitié JQuery, moitié AngularJS, et pas bon. Le problème ici est que le développeur essaie de "faire du jQuery" dans le contexte d'AngularJS. Cela ne va jamais bien fonctionner. La vue est le document officiel. En dehors d'une directive (plus d'informations à ce sujet ci-dessous), vous ne modifiez jamais, jamais, jamais le DOM. Et les directives sont appliquées dans la vue, l'intention est donc claire. Rappelez-vous : ne concevez pas, puis marquez. Vous devez architecturer, et ensuite concevoir.

Liaison de données

C'est de loin l'une des fonctionnalités les plus impressionnantes d'AngularJS et elle élimine une grande partie du besoin de faire le genre de manipulations du DOM que j'ai mentionné dans la section précédente. AngularJS met automatiquement à jour votre vue pour que vous n'ayez pas à le faire ! En jQuery, nous répondons aux événements et mettons ensuite le contenu à jour. Quelque chose comme :

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  }
});

Pour une vue qui ressemble à ceci :

<ul class="messages" id="log">
</ul>

Outre le mélange des préoccupations, nous avons également les mêmes problèmes de signification de l'intention que j'ai mentionnés précédemment. Mais plus important encore, nous avons dû référencer et mettre à jour manuellement un nœud DOM. Et si nous voulons supprimer une entrée de journal, nous devons coder contre le DOM pour cela aussi. Comment tester la logique en dehors du DOM ? Et que faire si nous voulons changer la présentation ? C'est un peu désordonné et un peu fragile. Mais dans AngularJS, nous pouvons le faire :

$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } );
});

Et notre vue peut ressembler à ceci :

<ul class="messages">
    <li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>

Mais d'ailleurs, notre vue pourrait ressembler à ça :

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div>
</div>

Et maintenant, au lieu d'utiliser une liste non ordonnée, nous utilisons des boîtes d'alerte Bootstrap. Et nous n'avons jamais eu à modifier le code du contrôleur ! Mais surtout, peu importe ou comment le journal est mis à jour, la vue change aussi. Automatiquement. Génial ! Bien que je ne l'ai pas montré ici, la liaison de données est bidirectionnelle. Ainsi, les messages du journal peuvent également être modifiés dans la vue en faisant simplement ceci : <input ng-model="entry.msg" />. Et il y a eu beaucoup de réjouissances.

Couche de modèle distincte

Dans jQuery, le DOM est en quelque sorte le modèle. Mais dans AngularJS, nous avons une couche de modèle distincte que nous pouvons gérer de la manière que nous voulons, complètement indépendamment de la vue. Cela aide à la liaison de données ci-dessus, maintient la [séparation des préoccupations][3], et introduit une bien meilleure testabilité. D'autres réponses ont mentionné ce point, je vais donc m'en tenir à cela.

Séparation des préoccupations

Tout ce qui précède est lié à ce thème primordial : gardez vos préoccupations séparées. Votre vue fait office d'enregistrement officiel de ce qui est censé se passer (pour l'essentiel) ; votre modèle représente vos données ; vous disposez d'une couche de services pour effectuer des tâches réutilisables ; vous effectuez des manipulations DOM et complétez votre vue avec des directives ; et vous collez le tout avec des contrôleurs. Cela a également été mentionné dans d'autres réponses, et la seule chose que j'ajouterais concerne la testabilité, dont je parle dans une autre section ci-dessous.

Injection de dépendances

Pour nous aider à séparer les préoccupations, il y a l'[injection de dépendances][4] (DI). Si vous venez d'un langage côté serveur (de [Java][5] à [PHP][6]), vous êtes probablement déjà familier avec ce concept, mais si vous êtes un gars côté client qui vient de jQuery, ce concept peut sembler tout à fait idiot, superflu ou branché. Mais il ne l'est pas :-) D'un point de vue général, DI signifie que vous pouvez déclarer des composants très librement et ensuite, à partir de n'importe quel autre composant, il suffit de demander une instance de celui-ci et elle sera accordée. Vous n'avez pas besoin de connaître l'ordre de chargement, l'emplacement des fichiers, ou quoi que ce soit d'autre. Le pouvoir n'est peut-être pas immédiatement visible, mais je vais vous donner un seul exemple (courant) : les tests. Disons que dans notre application, nous avons besoin d'un service qui met en œuvre le stockage côté serveur via une API [REST][7] et, selon l'état de l'application, le stockage local également. Lorsque nous exécutons des tests sur nos contrôleurs, nous ne voulons pas avoir à communiquer avec le serveur - nous testons le contrôleur, après tout. Nous pouvons simplement ajouter un service fictif du même nom que notre composant original, et l'injecteur veillera à ce que notre contrôleur reçoive automatiquement le faux service - notre contrôleur n'a pas besoin de connaître la différence. En parlant de test...

4. Développement piloté par les tests - toujours.

Ce point fait partie de la section 3 sur l'architecture, mais il est si important que j'en fais une section à part entière. Parmi tous les nombreux plugins jQuery que vous avez vus, utilisés ou écrits, combien d'entre eux étaient accompagnés d'une suite de tests ? Pas beaucoup, car jQuery ne s'y prête pas vraiment. Mais AngularJS l'est. En jQuery, la seule façon de tester est souvent de créer le composant indépendamment avec une page d'exemple/démo sur laquelle nos tests peuvent effectuer des manipulations du DOM. Nous devons donc développer un composant séparément et ensuite l'intégrer à notre application. Quel inconvénient ! La plupart du temps, lorsque nous développons avec jQuery, nous optons pour un développement itératif plutôt que pour un développement piloté par les tests. Et qui pourrait nous en vouloir ? Mais comme nous avons la séparation des préoccupations, nous pouvons faire du développement piloté par les tests de manière itérative dans AngularJS ! Par exemple, disons que nous voulons une directive super-simple pour indiquer dans notre menu quelle est notre route actuelle. Nous pouvons déclarer ce que nous voulons dans la vue de notre application :

<a href="/hello" when-active>Hello</a>

Ok, maintenant nous pouvons écrire un test pour la directive inexistante when-active :

it( 'should add "active" when the route changes', inject(function() {
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));

Et quand nous exécutons notre test, nous pouvons confirmer qu'il échoue. Ce n'est que maintenant que nous devons créer notre directive :

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});

Notre test passe maintenant et notre menu fonctionne comme prévu. Notre développement est à la fois itératif et piloté par les tests. Super cool.

5. Conceptuellement, les directives ne sont pas emballées dans jQuery.

Vous entendrez souvent "ne faire de la manipulation DOM que dans une directive". *C'est une nécessité, traitez-la avec respect ! Mais plongeons un peu plus profondément... Certaines directives ne font que décorer ce qui est déjà dans la vue (pensez à ngClass) et donc parfois font des manipulations de DOM directement et sont alors pratiquement terminées. Mais si une directive est comme un "widget" et a un modèle, elle devrait aussi respecter la séparation des préoccupations. En d'autres termes, le modèle doit lui aussi rester largement indépendant de son implémentation dans les fonctions de lien et de contrôleur. AngularJS est livré avec tout un ensemble d'outils pour rendre cela très facile ; avec ngClass, nous pouvons mettre à jour dynamiquement la classe ; ngModel permet de lier des données dans les deux sens ; ngShow et ngHide affichent ou cachent un élément de manière programmatique ; et bien d'autres encore - y compris ceux que nous écrivons nous-mêmes. En d'autres termes, nous pouvons faire toutes sortes de choses impressionnantes sans manipuler le DOM. Moins il y a de manipulation du DOM, plus les directives sont faciles à tester, plus elles sont faciles à styliser, plus elles sont faciles à modifier à l'avenir, et plus elles sont réutilisables et distribuables. Je vois beaucoup de développeurs novices en AngularJS qui utilisent les directives comme un endroit où jeter un tas de jQuery. En d'autres termes, ils pensent "puisque je ne peux pas faire de manipulation DOM dans le contrôleur, je vais prendre ce code et le mettre dans une directive". Bien que ce soit certainement beaucoup mieux, c'est souvent encore faux. Pensez au logger que nous avons programmé dans la section 3. Même si nous le mettons dans une directive, nous voulons encore le faire à la "manière Angular". Il n'est toujours pas nécessaire de manipuler le DOM ! Il y a beaucoup de cas où la manipulation du DOM est nécessaire, mais c'est beaucoup plus rare que vous ne le pensez ! Avant de faire de la manipulation du DOM partout* dans votre application, demandez-vous si vous en avez vraiment besoin. Il y a peut-être un meilleur moyen. Voici un exemple rapide qui montre le modèle que je vois le plus fréquemment. Nous voulons un bouton basculant. (Note : cet exemple est un peu artificiel et un peu verbeux pour représenter des cas plus compliqués qui se résolvent exactement de la même manière).

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                on = !on;
                $(element).toggleClass('active', on);
            });
        }
    };
});

Il y a plusieurs choses qui ne vont pas dans ce sens :

  1. Premièrement, jQuery n'a jamais été nécessaire. Il n'y a rien que nous ayons fait ici qui nécessitait jQuery du tout !
  2. Deuxièmement, même si nous avons déjà jQuery sur notre page, il n'y a aucune raison de l'utiliser ici ; nous pouvons simplement utiliser angular.element et notre composant fonctionnera toujours lorsqu'il sera déposé dans un projet qui n'a pas jQuery.
  3. Troisièmement, même en supposant que jQuery était nécessaire pour que cette directive fonctionne, jqLite (angular.element) utilisera toujours jQuery s'il a été chargé ! Donc nous n'avons pas besoin d'utiliser le $ - nous pouvons simplement utiliser angular.element.
  4. Quatrièmement, étroitement lié au troisième, les éléments jqLite n'ont pas besoin d'être enveloppés dans des $ - le element qui est passé à la fonction link serait déjà un élément jQuery !
  5. Et cinquièmement, ce que nous avons mentionné dans les sections précédentes, pourquoi mélangeons-nous des trucs de template dans notre logique ? Cette directive peut être réécrite (même pour des cas très compliqués !) beaucoup plus simplement comme ceci :
.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;

            scope.toggle = function () {
                scope.on = !scope.on;
            };
        }
    };
});

Encore une fois, le contenu du modèle est dans le modèle, donc vous (ou vos utilisateurs) pouvez facilement le remplacer par un modèle qui répond à n'importe quel style nécessaire, et la logique n'a jamais été touchée. Réutilisabilité - boom ! Et il y a encore tous ces autres avantages, comme les tests - c'est facile ! Quel que soit le contenu du modèle, l'API interne de la directive n'est jamais touchée, ce qui facilite le remaniement. Vous pouvez modifier le modèle autant que vous le souhaitez sans toucher à la directive. Et peu importe ce que vous changez, vos tests passent toujours. w00t ! Si les directives ne sont pas simplement des collections de fonctions de type jQuery, que sont-elles ? Les directives sont en fait des extensions du HTML. Si le HTML ne fait pas quelque chose dont vous avez besoin, vous écrivez une directive pour le faire à votre place, puis vous l'utilisez comme si elle faisait partie du HTML. En d'autres termes, si AngularJS ne fait pas quelque chose à partir de la boîte, pensez à la façon dont l'équipe pourrait l'accomplir pour s'intégrer parfaitement avec ngClick, ngClass, et al.

Résumé

N'utilisez même pas jQuery. Ne l'incluez même pas. Il vous retardera. Et quand vous rencontrez un problème que vous pensez savoir résoudre avec jQuery, avant de prendre le $, essayez de penser à comment le faire dans les limites d'AngularJS. Si vous ne savez pas, demandez ! 19 fois sur 20, la meilleure façon de le faire n'a pas besoin de jQuery et essayer de le résoudre avec jQuery vous donne plus de travail. [1] : http://en.wikipedia.org/wiki/Document_Object_Model [2] : http://en.wikipedia.org/wiki/Single-page_application [3] : http://en.wikipedia.org/wiki/Separation_of_concerns [4] : http://en.wikipedia.org/wiki/Dependency_injection [5] : http://en.wikipedia.org/wiki/Java_%28programming_language%29 [6] : http://en.wikipedia.org/wiki/PHP [7] : http://en.wikipedia.org/wiki/Representational_State_Transfer

Commentaires (22)

Impératif → déclaratif

Dans jQuery, les sélecteurs sont utilisés pour trouver des éléments du [DOM][1], puis leur lier/enregistrer des gestionnaires d'événements. Lorsqu'un événement se déclenche, ce code (impératif) s'exécute pour mettre à jour/modifier le DOM.

Dans AngularJS, il faut penser aux vues plutôt qu'aux éléments du DOM. Les vues sont du HTML (déclaratif) qui contient des directives AngularJS. Les directives configurent les gestionnaires d'événements en arrière-plan pour nous et nous donnent des liaisons de données dynamiques. Les sélecteurs sont rarement utilisés, ce qui réduit considérablement le besoin d'ID (et de certains types de classes). Les vues sont liées aux modèles (via les scopes). Les vues sont une projection du modèle. Les événements modifient les modèles (c'est-à-dire les données, les propriétés de l'étendue), et les vues qui projettent ces modèles se mettent à jour "automatiquement".

Dans AngularJS, pensez aux modèles, plutôt qu'aux éléments DOM sélectionnés par jQuery qui contiennent vos données. Pensez aux vues comme des projections de ces modèles, plutôt que d'enregistrer des rappels pour manipuler ce que l'utilisateur voit.

Séparation des préoccupations

jQuery utilise le [JavaScript discret][2] - le comportement (JavaScript) est séparé de la structure (HTML).

AngularJS utilise des contrôleurs et des directives (dont chacun peut avoir son propre contrôleur, et/ou des fonctions de compilation et de liaison) pour séparer le comportement de la vue/structure (HTML). Angular dispose également de services et de filtres pour aider à séparer/organiser votre application.

Voir aussi https://stackoverflow.com/a/14346528/215945

Conception de l'application

Une approche de la conception d'une application AngularJS :

  1. Réfléchissez à vos modèles. Créez des services ou vos propres objets JavaScript pour ces modèles.
  2. Réfléchissez à la façon dont vous voulez présenter vos modèles - vos vues. Créez des modèles HTML pour chaque vue, en utilisant les directives nécessaires pour obtenir une liaison de données dynamique.
  3. Attachez un contrôleur à chaque vue (en utilisant ng-view et routage, ou ng-controller). Demandez au contrôleur de trouver/obtenir uniquement les données de modèle dont la vue a besoin pour faire son travail. Rendre les contrôleurs aussi fins que possible.

Héritage prototypique

Vous pouvez faire beaucoup de choses avec jQuery sans connaître le fonctionnement de l'héritage prototypique de JavaScript. Lors du développement d'applications AngularJS, vous éviterez certains pièges courants si vous avez une bonne compréhension de l'héritage JavaScript. Lecture recommandée : https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs

[1] : http://en.wikipedia.org/wiki/Document_Object_Model [2] : http://en.wikipedia.org/wiki/Unobtrusive_JavaScript

Commentaires (7)

Pouvez-vous décrire le changement de paradigme qui est nécessaire ?

Impératif vs Déclaratif

Avec jQuery, vous dites au DOM ce qui doit se passer, étape par étape. Avec [AngularJS][1], vous décrivez les résultats que vous souhaitez obtenir, mais pas la manière de le faire. Plus d'informations à ce sujet [ici][2]. Consultez également la réponse de Mark Rajcok.

Comment concevoir et architecturer différemment les applications Web côté client ?

AngularJS est un framework côté client complet qui utilise le modèle [MVC][3] (consultez leur [représentation graphique][4]). Il met fortement l'accent sur la séparation des préoccupations.

Quelle est la plus grande différence ? Que dois-je arrêter de faire ou d'utiliser et que dois-je commencer à faire ou à utiliser à la place ?

jQuery est une bibliothèque

AngularJS est un magnifique framework côté client, hautement testable, qui combine des tonnes de trucs sympas tels que MVC, [injection de dépendances][5], liaison de données et bien plus encore.

Il se concentre sur la [séparation des préoccupations][6] et les tests ([tests unitaires][7] et tests de bout en bout), ce qui facilite le développement piloté par les tests.

La meilleure façon de commencer est de suivre [leur formidable tutoriel][8]. Vous pouvez parcourir les étapes en quelques heures ; toutefois, si vous souhaitez maîtriser les concepts en arrière-plan, ils incluent une myriade de références pour une lecture plus approfondie.

Y a-t-il des considérations/restrictions du côté du serveur ?

Vous pouvez l'utiliser sur des applications existantes où vous utilisez déjà du jQuery pur. Toutefois, si vous souhaitez tirer pleinement parti des fonctionnalités d'AngularJS, vous pouvez envisager de coder le côté serveur en utilisant une approche [RESTful][9].

Vous pourrez ainsi tirer parti de leur [resource factory][10], qui crée une abstraction de votre [API][11] RESTful côté serveur et rend les appels côté serveur (obtenir, enregistrer, supprimer, etc.) incroyablement faciles.

[1] : http://en.wikipedia.org/wiki/AngularJS [2] : https://stackoverflow.com/questions/1784664/what-is-the-difference-between-declarative-and-imperative-programming [3] : http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller [4] : http://docs.angularjs.org/guide/concepts [5] : http://en.wikipedia.org/wiki/Dependency_injection [6] : http://en.wikipedia.org/wiki/Separation_of_concerns [7] : http://en.wikipedia.org/wiki/Unit_testing [8] : http://docs.angularjs.org/tutorial/ [9] : http://en.wikipedia.org/wiki/Representational_state_transfer#RESTful_web_services [10] : http://docs.angularjs.org/api/ngResource.$resource [11] : http://en.wikipedia.org/wiki/Application_programming_interface

Commentaires (4)