Un tutoriel étape par étape pour votre première application AngularJS
Publié: 2022-03-11Qu'est-ce qu'AngularJS ?
AngularJS est un framework JavaScript MVC développé par Google qui vous permet de créer des applications frontales bien structurées, facilement testables et maintenables.
Et pourquoi devrais-je l'utiliser ?
Si vous n'avez pas encore essayé AngularJS, vous manquez quelque chose. Le cadre se compose d'un ensemble d'outils étroitement intégrés qui vous aideront à créer des applications côté client riches et bien structurées de manière modulaire, avec moins de code et plus de flexibilité.
AngularJS étend le HTML en fournissant des directives qui ajoutent des fonctionnalités à votre balisage et vous permettent de créer de puissants modèles dynamiques. Vous pouvez également créer vos propres directives, en créant des composants réutilisables qui répondent à vos besoins et en faisant abstraction de toute la logique de manipulation du DOM.
Il implémente également une liaison de données bidirectionnelle, connectant votre code HTML (vues) à vos objets JavaScript (modèles) de manière transparente. En termes simples, cela signifie que toute mise à jour de votre modèle sera immédiatement reflétée dans votre vue sans qu'il soit nécessaire de manipuler le DOM ou de gérer les événements (par exemple, avec jQuery).
Enfin, j'aime Angular en raison de sa flexibilité en matière de communication avec le serveur. Comme la plupart des frameworks JavaScript MVC, il vous permet de travailler avec n'importe quelle technologie côté serveur tant qu'elle peut servir votre application via une API Web RESTful. Mais Angular fournit également des services en plus de XHR qui simplifient considérablement votre code et vous permettent de résumer les appels d'API en services réutilisables. Par conséquent, vous pouvez déplacer votre modèle et votre logique métier vers le front-end et créer des applications Web agnostiques back-end. Dans cet article, nous ferons exactement cela, une étape à la fois.
Alors, par où commencer ?
Tout d'abord, décidons de la nature de l'application que nous voulons créer. Dans ce guide, nous préférerions ne pas passer trop de temps sur le back-end, nous allons donc écrire quelque chose basé sur des données facilement accessibles sur Internet, comme une application de flux sportifs !
Puisqu'il se trouve que je suis un grand fan de course automobile et de Formule 1, j'utiliserai un service d'API de sport automobile pour agir en tant que back-end. Heureusement, les gars d'Ergast ont la gentillesse de fournir une API de sport automobile gratuite qui sera parfaite pour nous.
Pour un aperçu de ce que nous allons construire, jetez un œil à la démo en direct. Pour embellir la démo et montrer des modèles angulaires, j'ai appliqué un thème Bootstrap de WrapBootstrap, mais vu que cet article ne concerne pas CSS, je vais simplement l'abstraire des exemples et le laisser de côté.
Tutoriel de démarrage
Commençons notre exemple d'application avec un passe-partout. Je recommande le projet angular-seed car il vous fournit non seulement un excellent squelette pour l'amorçage, mais prépare également le terrain pour les tests unitaires avec Karma et Jasmine (nous ne ferons aucun test dans cette démo, nous allons donc simplement laissez cela de côté pour le moment ; consultez la partie 2 de ce didacticiel pour plus d'informations sur la configuration de votre projet pour les tests unitaires et de bout en bout).
EDIT (mai 2014): Depuis que j'ai écrit ce tutoriel, le projet angular-seed a subi de lourds changements (y compris l'ajout de Bower en tant que gestionnaire de packages). Si vous avez des doutes sur la façon de déployer le projet, jetez un coup d'œil à la première section de leur guide de référence. Dans la partie 2 de ce didacticiel, Bower, entre autres outils, est traité plus en détail.
OK, maintenant que nous avons cloné le référentiel et installé les dépendances, le squelette de notre application ressemblera à ceci :
Nous pouvons maintenant commencer à coder. Alors que nous essayons de créer un flux sportif pour un championnat de course, commençons par la vue la plus pertinente : le tableau du championnat .
Étant donné que nous avons déjà une liste de pilotes définie dans notre champ d'application (restez avec moi - nous y arriverons), et en ignorant tout CSS (pour la lisibilité), notre HTML pourrait ressembler à :
<body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>
La première chose que vous remarquerez dans ce modèle est l'utilisation d'expressions ("{{" et "}}") pour renvoyer des valeurs de variables. Dans le développement AngularJS, les expressions vous permettent d'exécuter des calculs afin de renvoyer une valeur souhaitée. Certaines expressions valides seraient :
-
{{ 1 + 1 }}
-
{{ 946757880 | date }}
-
{{ user.name }}
En fait, les expressions sont des extraits de code de type JavaScript. Mais bien qu'ils soient très puissants, vous ne devez pas utiliser d'expressions pour implémenter une logique de niveau supérieur. Pour cela, nous utilisons des directives.
Comprendre les directives de base
La deuxième chose que vous remarquerez est la présence de ng-attributes
, que vous ne verriez pas dans un balisage typique. Ce sont des directives.
À un niveau élevé, les directives sont des marqueurs (tels que des attributs, des balises et des noms de classe) qui indiquent à AngularJS d'attacher un comportement donné à un élément DOM (ou de le transformer, de le remplacer, etc.). Jetons un œil à ceux que nous avons déjà vus :
La directive
ng-app
est responsable du démarrage de votre application en définissant sa portée. Dans AngularJS, vous pouvez avoir plusieurs applications dans la même page, donc cette directive définit où chaque application distincte commence et se termine.La directive
ng-controller
définit quel contrôleur sera en charge de votre vue. Dans ce cas, nous notonsdriversController
, qui fournira notre liste de pilotes (driversList
).La directive
ng-repeat
est l'une des plus couramment utilisées et sert à définir la portée de votre modèle lors de la boucle dans les collections. Dans l'exemple ci-dessus, il réplique une ligne dans la table pour chaque pilote dansdriversList
.
Ajout de contrôleurs
Bien sûr, notre vue ne sert à rien sans contrôleur. Ajoutons driversController
à notre controllers.js :
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });
Vous avez peut-être remarqué la variable $scope
que nous passons en paramètre au contrôleur. La variable $scope
est censée lier votre contrôleur et vos vues. En particulier, il contient toutes les données qui seront utilisées dans votre modèle. Tout ce que vous y ajouterez (comme la liste des driversList
dans l'exemple ci-dessus) sera directement accessible dans vos vues. Pour l'instant, travaillons simplement avec un tableau de données factice (statique), que nous remplacerons plus tard par notre service API.
Maintenant, ajoutez ceci à app.js :
angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);
Avec cette ligne de code, nous initialisons en fait notre application et enregistrons les modules dont elle dépend. Nous reviendrons sur ce fichier ( app.js
) plus tard.
Maintenant, rassemblons tout dans index.html
:
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
Modulo erreurs mineures, vous pouvez maintenant démarrer votre application et vérifier votre liste (statique) de pilotes.
Remarque : Si vous avez besoin d'aide pour déboguer votre application et visualiser vos modèles et votre portée dans le navigateur, je vous recommande de jeter un coup d'œil à l'excellent plugin Batarang pour Chrome.
Chargement des données depuis le serveur
Puisque nous savons déjà comment afficher les données de notre contrôleur dans notre vue, il est temps de récupérer les données en direct à partir d'un serveur RESTful.
Pour faciliter la communication avec les serveurs HTTP, AngularJS fournit les services $http
et $resource
. Le premier n'est qu'une couche au-dessus de XMLHttpRequest ou JSONP, tandis que le second fournit un niveau d'abstraction plus élevé. Nous utiliserons $http
.
Pour extraire nos appels d'API de serveur du contrôleur, créons notre propre service personnalisé qui récupérera nos données et agira comme un wrapper autour $http
en ajoutant ceci à notre services.js
:
angular.module('F1FeederApp.services', []). factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
Avec les deux premières lignes, nous créons un nouveau module ( F1FeederApp.services
) et enregistrons un service dans ce module ( ergastAPIservice
). Notez que nous passons $http
comme paramètre à ce service. Cela indique au moteur d'injection de dépendances d'Angular que notre nouveau service nécessite (ou dépend de ) le service $http
.
De la même manière, nous devons dire à Angular d'inclure notre nouveau module dans notre application. Enregistrons-le avec app.js
, en remplaçant notre code existant par :
angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);
Maintenant, tout ce que nous avons à faire est de modifier un peu notre controller.js
, d'inclure ergastAPIservice
en tant que dépendance, et nous serons prêts à partir :
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; ergastAPIservice.getDrivers().success(function (response) { //Dig into the responde to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); });
Maintenant, rechargez l'application et vérifiez le résultat. Notez que nous n'avons apporté aucune modification à notre modèle, mais nous avons ajouté une variable nameFilter
à notre portée. Utilisons cette variable.

Filtres
Génial! Nous avons un contrôleur fonctionnel. Mais il ne montre qu'une liste de pilotes. Ajoutons quelques fonctionnalités en implémentant une simple entrée de recherche de texte qui filtrera notre liste. Ajoutons la ligne suivante à notre index.html
, juste en dessous de la <body>
:
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
Nous utilisons maintenant la directive ng-model
. Cette directive lie notre champ de texte à la variable $scope.nameFilter
et s'assure que sa valeur est toujours à jour avec la valeur d'entrée. Maintenant, visitons index.html une fois de plus et apportons un petit ajustement à la ligne qui contient la directive ng-repeat
:
<tr ng-repeat="driver in driversList | filter: nameFilter">
Cette ligne indique à ng-repeat
qu'avant de sortir les données, le tableau driversList
doit être filtré par la valeur stockée dans nameFilter
.
À ce stade, la liaison de données bidirectionnelle entre en jeu : chaque fois qu'une valeur est saisie dans le champ de recherche, Angular s'assure immédiatement que le $scope.nameFilter
que nous lui avons associé est mis à jour avec la nouvelle valeur. Étant donné que la liaison fonctionne dans les deux sens, dès que la valeur nameFilter
est mise à jour, la deuxième directive qui lui est associée (c'est-à-dire ng-repeat
) obtient également la nouvelle valeur et la vue est mise à jour immédiatement.
Rechargez l'application et consultez la barre de recherche.
Notez que ce filtre recherchera le mot-clé sur tous les attributs du modèle, y compris ceux qui n'étaient pas utilisés. Disons que nous voulons uniquement filtrer par Driver.givenName
et Driver.familyName
: Tout d'abord, nous ajoutons à driversController
, juste en dessous de $scope.driversList = [];
ligne:
$scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };
Maintenant, revenons à index.html
, nous mettons à jour la ligne qui contient la directive ng-repeat
:
<tr ng-repeat="driver in driversList | filter: searchFilter">
Rechargez l'application une fois de plus et nous avons maintenant une recherche par nom.
Itinéraires
Notre prochain objectif est de créer une page de détails sur le pilote qui nous permettra de cliquer sur chaque pilote et de voir les détails de sa carrière.
Tout d'abord, incluons le service $routeProvider
(dans app.js
) qui nous aidera à gérer ces routes d'application variées . Ensuite, nous ajouterons deux itinéraires de ce type : un pour le tableau du championnat et un autre pour les détails du pilote. Voici notre nouveau app.js
:
angular.module('F1FeederApp', [ 'F1FeederApp.services', 'F1FeederApp.controllers', 'ngRoute' ]). config(['$routeProvider', function($routeProvider) { $routeProvider. when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}). when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}). otherwise({redirectTo: '/drivers'}); }]);
Avec ce changement, la navigation vers http://domain/#/drivers
chargera driversController
et recherchera la vue partielle à rendre dans partials/drivers.html
. Mais attendez! Nous n'avons pas encore d'opinions partielles, n'est-ce pas ? Nous devrons également les créer.
Vues partielles
AngularJS vous permettra de lier vos routes à des contrôleurs et des vues spécifiques.
Mais d'abord, nous devons dire à Angular où rendre ces vues partielles. Pour cela, nous utiliserons la directive ng-view
, en modifiant notre index.html
pour refléter ce qui suit :
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
Désormais, chaque fois que nous naviguons dans nos itinéraires d'application, Angular charge la vue associée et la restitue à la place de la <ng-view>
. Tout ce que nous avons à faire est de créer un fichier nommé partials/drivers.html
et d'y mettre notre table de championnat HTML. Nous utiliserons également cette opportunité pour lier le nom du conducteur à notre itinéraire des détails du conducteur :
<input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>
Enfin, décidons de ce que nous voulons afficher dans la page de détails. Que diriez-vous d'un résumé de tous les faits pertinents sur le conducteur (par exemple, naissance, nationalité) accompagné d'un tableau contenant ses résultats récents ? Pour ce faire, nous ajoutons à services.js
:
angular.module('F1FeederApp.services', []) .factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
Cette fois, nous fournissons l'identifiant du conducteur au service afin de récupérer les informations pertinentes uniquement pour un conducteur spécifique. Maintenant, nous modifions controllers.js
:
angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });
La chose importante à noter ici est que nous venons d'injecter le service $routeParams
dans le contrôleur du pilote. Ce service nous permettra d'accéder à nos paramètres d'URL (pour le :id
, dans ce cas) en utilisant $routeParams.id
.
Maintenant que nous avons nos données dans la portée, nous n'avons besoin que de la vue partielle restante. Créons un fichier nommé partials/driver.html
et ajoutons :
<section> <a href="./#/drivers"><- Back to drivers list</a> <nav class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>
Notez que nous utilisons maintenant la directive ng-show
à bon escient. Cette directive n'affichera l'élément HTML que si l'expression fournie est true
(c'est-à-dire ni false
, ni null
). Dans ce cas, l'avatar n'apparaîtra qu'une fois que l'objet pilote aura été chargé dans l'oscilloscope par le contrôleur.
La touche finale
Ajoutez un tas de CSS et affichez votre page. Vous devriez vous retrouver avec quelque chose comme ça :
Vous êtes maintenant prêt à lancer votre application et à vous assurer que les deux itinéraires fonctionnent comme vous le souhaitez. Vous pouvez également ajouter un menu statique à index.html
pour améliorer les capacités de navigation de l'utilisateur. Les possibilités sont infinies.
EDIT (mai 2014) : J'ai reçu de nombreuses demandes pour une version téléchargeable du code que nous construisons dans ce tutoriel. J'ai donc décidé de le publier ici (dépouillé de tout CSS). Cependant, je ne recommande vraiment pas de le télécharger, car ce guide contient toutes les étapes dont vous avez besoin pour créer la même application de vos propres mains, ce qui sera un exercice d'apprentissage beaucoup plus utile et efficace.
Conclusion
À ce stade du didacticiel, nous avons couvert tout ce dont vous avez besoin pour écrire une application simple (comme un chargeur de Formule 1). Chacune des pages restantes de la démo en direct (par exemple, tableau du championnat constructeur, détails de l'équipe, calendrier) partage la même structure de base et les mêmes concepts que nous avons examinés ici.
Enfin, gardez à l'esprit qu'Angular est un framework très puissant et que nous avons à peine effleuré la surface en termes de tout ce qu'il a à offrir. Dans la partie 2 de ce didacticiel, nous donnerons des exemples de la raison pour laquelle Angular se démarque parmi ses frameworks MVC frontaux homologues : la testabilité. Nous passerons en revue le processus d'écriture et d'exécution des tests unitaires avec Karma, en réalisant une intégration continue avec Yeomen, Grunt et Bower, et d'autres points forts de ce fantastique framework frontal.