AngularJS (1.6) Cheat Sheet

Cet article a pour but de résumer les principaux éléments à connaître quand on débute avec AngularJS. Cela ne dispense pas d’aller voir des tutoriels ou la documentation pour approfondir le sujet.

Concepts

Les principaux concepts d’Angular 1.6 qui seront développés dans cette fiche de rappels :
AngularJS Concepts

Architecture du code

Voir les conseils proposés par Angular Seed sur github ou encore ceux de John Papa.
Quelques exemples, à adapter en fonction de la taille du projet.

Modulaire Orienté micro-service
app/
app/app.css
app/app.js
app/index.html
app/components/component1/componentController.js
app/components/component1/componentController.spec.js
app/components/component1/component-directive.js
app/components/component1/component-directive.spec.js
app/components/component1/some.filter.js
app/module1/module1.module.js
app/module1/config-routes.js
app/module1/view1/view.html
app/module1/view1/viewController.js
app/module1/view1/viewController.spec.js
app/module1/view1/someService.js
app/module1/view1/someService.spec.js
lib/**.js
xxxx.conf.js
e2e-tests/protractor-conf.js
e2e-tests/scenarios.js
e2e-tests/mock-data.json
app/
app/app.css
app/app.js
app/index.html
app/components/component1/componentController.js
app/components/component1/componentController.spec.js
app/components/component1/component-directive.js
app/components/component1/component-directive.spec.js
app/components/component1/some.filter.js
app/views/view1.html
app/ctrl/view1Controller.js
app/ctrl/view1Controller.spec.js
lib/**.js
xxxx.conf.js
e2e-tests/protractor-conf.js
e2e-tests/scenarios.js
e2e-tests/mock-data.json

Conventions

Fichiers :

  • Ecriture des fonctions sous la norme IIFE (Immediately-Invoked Function Expression) pour protéger la visibilité des variables.
  • Les contrôleurs : xxxxController.js
  • Les tests unitaires : xxxx.spec.js. Reprend le nom du controleur ou du service. Dans le même package.
  • Les directives, les filtres : xxxx-directive.js, xxxx-filter.js

Variables :

  • Préfixées de $ : Service fourni par Angular. Ex. $scope, $routeParams, ...
  • vm : ViewModel. Utilisé dans les contrôleurs, évite l’utilisation du $scope et des problèmes d’héritage ($parent).
  • Modules : Nom = Nom de l’application + « . » + nom du module. Ex. hello.module1
  • Contrôleurs : la méthode d’initialisation s’appelle activate() en général.
  • Séparer clairement le contexte public (valeurs de retour) du contexte privé dans les fonctions.
  • Directives : préfixer les noms pour éviter les surcharges (Ex. my : myBadge).
  • Liens Href : #!/myRoute remplace #/myRoute à partir d’Angular 1.6.

Initialisation : Exemple de Index.html

<!DOCTYPE html>
<!-- Définition de l'application Angular (Angular n'est pas exécuté en dehors de l'élément ng-app) -->
<html ng-app="hello"> <!-- ng-strict-di pour forcer la protection contre la minification du code
     (injections explicites) -->
<head> 
  <link href="app.css" rel="stylesheet" />
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.js"></script>
  <script src="https://code.angularjs.org/1.5.8/angular-route.js"></script>

  <script src="app.js"></script>
  <!-- Directives, Modules, Controllers, routes, etc. -->
</head>
<body>
  <!-- Si pas associé dans le routage (ngRoute), on peut définir le controller ici
        via ng-controller="controllerName" -->
  <div ng-view=""></div>
</body>
</html>

Initialisation du contexte Angular : app.js

(function() {
  var app = angular.module("hello", ['ngRoute', 'hello.module1', 'hello.module2', ...]);
  // Route par défaut (si aucun module n'a traité)
  app.config(function($routeProvider) {
    $routeProvider
      .otherwise({
        redirectTo: "/"
      })
  });
}());

Les modules : module1/Module1Module.js

(function()  {
  'use strict';
  // Création du module module1 avec lien vers dépendances si nécessaire
  angular.module("app.module1", []);
}());

Le routing : module/config-routes.js

ngRoute nécessite le paquet angular-route.js.

(function() {
  'use strict';
  // Configuration du module module1 :
  angular.module("app.module1", ['ngRoute']).config(config);
  function config($routeProvider) {
    $routeProvider
      .when("/", {
        templateUrl: "module1/views/view.html",
        controller: "ViewCtrl"
      })
      .when("/module1/:username", {
        templateUrl: "module1/views/view.html",
        controller: "UserCtrl",
        // Appelle la fonction au moment de résoudre le routage :
        resolve: { myResolverService: myResolverService }
      });
  }
  myResolverService.$inject = ['$log'];
  function myResolverService($log) { ... }
());

Les Services : module1/MyService.js

Quelques services fournis par angular : $http ; $interval ; $timeout ; $log ; $animate ; $location ; $browser ; $q ; $window ; $anchorScroll ; $routeParams ; $controller(jasmine) ;

(function() {
  'use strict';
  // Création du service. NB : Un service est indépendant du contexte vue/contrôleur.
  // .factory() => Singleton ; contrairement à .service()
  angular.module("app.module1").factory("myService1", MyService);
  // Utilisation des services angular http et log :
  MyService.$inject = ['$http', '$log'];

  function MyService($http, $log) {
    // Un service retourne un objet fournissant des services (fonctions).
    var service = {
      getUserProfile: getUserProfile,
    }
    return service;
    // Séparation zone publique / Privée
    ///////////////////////////

    function getUserProfile(username) {
      // local vars...
      var selection = null;
      // return une promise
      return $http.get("...").then(function(res) {
        // service code
        selection = { };
        return {
          selection: selection,
        }
      });
    }
  };
}());

Test des services : module1/MyService.spec.js

describe("Chapter 1 : User Service", function() {
  var service, http;
  var mockData = '{ "users": [{"id":1}, {"id":2}, {"id":3}] }';
  // Chargement du module
  beforeEach(module('app.module1'));
  // Injection du service à tester et du service angular $http
  beforeEach(inject(function(myService1, $httpBackend) {
    service = myService1;
    http = $httpBackend;
  }));
  // Test 1
  it("Should ...", function(done) {
    var testData = function(data) {
      expect(data.users.length).toBe(3);
    };
    var testFail = function(error) {
      expect(error).toBeUndefined();
    };
    // Mock de la méthode http.get appelée depuis myService1
    http.expectGET(...).respond(200, mockData);
    // Espionnage : 
    var mySpy = spyOn(service, 'getUserProfile'); // .and.callFake(...); 
    service.getUserProfile(null).then(testData).catch(testFail).finally(done);
    // Espionnage :
    expect(spy).toHaveBeenCalled();
    http.flush();
  });
});

Les contrôleurs : module1/MyController.js

(function() {
  'use strict';
  // Enregistrement du contrôleur auprès du module hôte
  angular.module("app.module1").controller("MyCtrl", MyCtrl);
  // MyCtrl va utiliser MyService et le service $log d'angular.
  MyCtrl.$inject = ['myService1', '$log'];
  function MyCtrl(myService, $log) {
    // local vars
    var vm = this;
    vm.sortBy = "name";
    vm.users = [];
    activate();
    // Séparation public / privé
    ///////////

    function activate() {
      getUsers();
    }
    function getUsers(username) {
      myService.getUserProfile(username).then(function(obj) {
        vm.users = obj.users;
      });
    }
    function search() { ... }
    function sortBy(attribute) { ... }
  };
}());

Test des contrôleurs : module1/MyController.spec.js

// Jasmine test file
describe("Chapitre 1 : My Controller ", function() {
  var $ctrl;
  var scope;
  // Chargement du module au début du test
  beforeEach(module('app.module1'));
  // Injection d'un constructeur du contrôleur (pour créer un mock)
  beforeEach(inject(function($controller, $rootScope) {
    $ctrl = $controller;
    scope = $rootScope.$new();
  }));
  // Test unitaire 1
  it("should .... ", function() {
    var ctrl = $ctrl('MyCtrl');
    expect(ctrl.value).toBe(42);
  });
});

Les directives : directives/badge-directive.js

(function() {
  angular.module('hello').directive('myBadge', getBadge);
  function getBadge() {
    return {
      restrict: 'E', // [E]lement ; [A]ttribute ; [C]lass ; Co[M]ment. Default: EA
      bindToController: true, // Association avec les variables du $scope parent du contrôleur.
      scope: { 
        user: '=user',
        error: '=error'
      },
      templateUrl: "common/directives/badge.html"
    }
  }
}());

Les directives : directives/badge.html

<div>
  <div ng-if="!error">
    <img ng-if="user.avatar_url" ng-src="{{user.avatar_url}}" alt="user"/>
  </div>  <!-- ngIf -->
  <div nf-if="error" style="color: red;">{{error}}</div>
</div>

Les directives : module1/view.html

<!-- NB : myBadge en javascript devient my-badge en html -->
<my-badge user="vm.foundUser" error="vm.error"></my-badge>

Les filtres : view.html

Quelques filtres fournis par Angular : currency ; date ; filter ; json ; limitTo ; lowercase ; uppercase ; number ; orderBy, …

<!-- vm.filtername = écriture de la valeur saisie dans le contrôleur -->
<input type="search" ng-model="vm.filterName" />
<!-- Filtrage des utilisateurs par rapport à la valeur saisie filterName -->
<p>There are {{(vm.users | filter:vm.filterName).length}} matching users.</p>
<!-- ... -->
    <!-- Filtrage par rapport à la valeur saisie + tri par ordre défini par le contrôleur
        (Ex. +name/-name) -->
    <tr ng-repeat="user in vm.users | filter:vm.filterName | orderBy:vm.sortWay+vm.sortAttr">
      <!-- ... -->
    </tr>

Les watchers

$scope.watch("localVar", function(newValue, oldValue) { ... }); 

Les test unitaires avec Jasmine : SpecRunner.js

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.css" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine-html.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/boot.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.js"></script>
  <script src="https://code.angularjs.org/1.5.8/angular-route.js"></script>
  <script src="https://code.angularjs.org/1.6.2/angular-mocks.js"></script>

  <!-- source files -->
  <script src="module1/Module1Module.js" type="text/javascript" charset="utf-8"></script>
  <script src="module1/MyService1.js" type="text/javascript" charset="utf-8"></script>
  <script src="module1/MyCtrl.js" type="text/javascript" charset="utf-8"></script>

  <!-- spec files -->
  <script type="text/javascript" src="duke/DukesCtrl.spec.js"></script>
  <script type="text/javascript" src="duke/DukeService.spec.js"></script>
</head>
<body></body>
</html>

Tests

Inscription des tests dans le cycle de vie du développement :

Tests unitaires avec Jasmine.js

Tests unitaires de non régression destinés à être lancés pendant la phase de développement (ou avant, en TDD). Ciblent un composant (contrôleur) particulier.

Structure d’un test

describe("Title", function() {
  // only => Isoler un test ou un chapitre (describe.only)
  it[.only]("Should blah blah blah"), function() {
    expect(...);
  });
}

Tests HTTP

// Mock d'une réponse HTTP :
$httpBackend.when("GET", "path/to/resource").respond(200, {data});
// Test asynchrone
myService.get("...").then(function(data) { expect(...); })
                    .catch(function(reason) { expect(...); });
$httpBackend.flush();

Tests d’intégration (end-to-end) avec Protractor.js

Utilise le moteur Selenium pour lancer les navigateurs et simuler le comportement de l’utilisateur. Gère les clics et la saisie clavier. Un test correspond à un scénario sur l’application globale (non couplé fortement à un contrôleur en particulier).

Le fichier de configuration spec.js permet d’initialiser le contexte du test (navigateur, taille écran, position du navigateur, etc.
Pour tester en headless : cf. projet headless Chrome XVFB + Docker

Installation (voir intro-to-protractor sur github) :

npm install -g protractor
webdriver-manager update
# Démarrage d'un serveur HTTP Jetty port 4444
webdriver-manager start

# Initialisation du projet
npm install
bower install
start mongodb
run grunt

Exemple de script de test, au format Jasmine :

describe("Chapitre 1", function() {
  it("Should ....", function() {
    browser.get("....");
    var firstElement = element.all(by.binding('name')).first();
    firstElement.getText().then(...);
    firstElement.click();
    browser.waitForAngular();
    ...
  }
});

Les locators disponibles (sélection des composants dans la vue) : by.binding ; by.model ; by.css ; by.buttonText ; by.repeater ; by.options ; by.id ; by.linkTest ; by.name ; by.tagName ; by.xPath ;

Conseils Archi : Créer une page PageObjects pour faire l’association entre les éléments de la vue et les scénarios de tests. Le fichier ‘spec’ utilise les constantes définies dans la PageObjets pour référencer les objets / méthodes. Une seule référence à la fois ; pas de duplication de code ; refactoring simplifié lorsque la vue change.

// create.page.js :
module.exports = function() {
  this.name = element(by.model(item.name));
  ...
}
// create.spec.js :
var CreatePage = require('create.page.js');
...

Ecosystème

Outils principaux gravitant autour de l’écosystème AngularJS :
Angular Tools

Architecture

Angular Seed

Architecture type d’un projet Angular.


Yeoman

« Échaffaudeur » de nouveaux projets, par code. Liste des générateurs sur yeoman.io/generators

# Générer un nouveau projet  Angular (avec Gulp et Bower)
yo yang 
yo yang --help
# Installer Yeoman :
npm install -g yo
# Générer un controleur dans un projet existant, avec la vue :
yo yang:ngc HelpCtrl --view
# Vérifier l'installation
yodoctor 
# Outil interactif
yo

Environnement de développement

Plnkr.co

Éditeur en ligne permettant d’effectuer des tests rapides sans installation nécessaire.


Visual Studio Code

L’outil de Microsoft.


IntelliJ Webstorm

L’éditeur de JetBrains, 30 jours gratuits.


Eclipse

Eclipse IDE.

Outils de builds

Bower

Récupère les librairies nécessaires à partir d’un fichier de configuration.


Gulp

Automate de builds.


ng-annotate

Minification du code.

Intégration Continue (CI)

Travis CI

Intégration continue Travis CI.


Azure Mobile Service

Permet l’hébergement de ses projets Angular sur le Cloud.

Librairies de composants graphiques

ACE Editor

Editeur de code compatible Angular.


Bootstrap

Librairie CSS standard. Pour Angular 2.


bootswatch.com

Thèmes gratuits pour Bootstrap.


Ionic

Construction d’applications mobiles natives ; Angular friendly.

Composants avancés avec angular-ui

Regroupe les librairies suivantes : angular ; angular-animator ; angular-route ; bootstrap ; lodash ; moment ; spin.js ; angular-bootstrap.

  • ui-bootstrap :
  • Regroupe un ensemble de composants pour Angular, non dépendants de jQuery

  • ui-router :
  • Remplace ngRoute. Gestion par ‘state’. $state, $stateProvider.

  • ui-module :
  • Ex. UIGmap (Google Map) ; UI-Calendar (Agenda) ; UI-Aca (ACE Editor).

  • ui-utils :
  • Outils sans dépendances. Ex. scroll ; highlight ; format ; toggle ; …

Material design avec angular-material

Installation de Angular-material :

npm install angular-material
npm init
npm install -g jspm
bower install angular-material

Quelques ressources à voir :

Outils de tests

Jasmine.js

Pour effectuer des tests unitaires.


Protractor.js

Pour effectuer des tests d’intégration.


Karma

Batterie de tests pour Angular, utilisant Jasmine. Fournit les tests unitaires (Jasmine) ; un File Watcher ; Multiple browsers ; indépendant des frameworks ; rapide ; en lignes de commandes.


Live Reload extension

Permet de voir en simultané dans son navigateur les changements apportés à son code.


Istanbul

Code coverage.

Laisser un commentaire

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