Attention!!! Cette formation n'est plus maintenue depuis ember 3.12 et ne correspond pas aux dernières évolutions du framework. Je vous invite à vous reporter sur les guides officiels d'ember pour des tutoriaux complets d'introduction.

Ember se définit comme : “A framework for creating ambitious web applications”. Deux mots sont à appuyer particulièrement :

  • web : l’une des caractéristiques majeures d’Ember est son attachement au web et aux URLs en particulier. Les URLs et donc le routeur sont au cœur d’Ember là où bien ‘autres frameworks les considèrent au mieux comme un add-on important.

  • framework : Ember est réellement un framework. Pas une lib, pas une colonne vertébrale, pas une boîte à outils : un framework avec un véritable modèle de développement qu’il est nécessaire d’adopter.

Conventions de nommage - Conventions Over Configuration

Ce modèle de développement commence par les conventions de nommage. Ember applique en effet le principe de “conventions over configuration” et repose sur un nommage cohérent des différents composants de votre application.

Ces conventions de nommage prennent la forme d’une structure d’application normalisée utilisant des noms de dossiers et des noms de fichiers particuliers, que le Resolver d’Ember s’attend à retrouver.

-> doc officielle.

Modularité

La modularité du framework repose d’abord sur les conventions de nommage et les Modules ES6 et, depuis sa version 2.16, sur une nouvelle API. Ember a en effet été découpé en modules qu’il est désormais possible d’importer individuellement sans nécessiter l’import de l’ensemble du namespace Ember comme cela était le cas auparavant.

On passe donc de cette syntaxe :

import Ember from 'ember';

export default Ember.Component.extend({
  session: Ember.inject.service(),
  title: 'The Curious Case'
});

à celle-ci :

import Component from '@ember/component';
import { inject as service } from '@ember/service';

export default Component.extend({
  session: service(),
  title: 'The Curious Case'
});

Cette API permettent une modularisation très fine du framework en offrant la possibilité de n’importer que le strict nécessaire. Outre l’apport en termes de lisibilité, l’ambition est - à court terme - de permettre la construction du builds plus légers en n’embarquant que les modules du framework nécessaire à notre application.

-> Ember Modules API

Application

La figure suivante, extraite de la Documentation officielle, montre une vue générale du fonctionnement d’une application Ember et des différents objets impliqués :

Application Ember

Routeur

Le routeur permet de faire correspondre à une URL un ensemble de templates imbriqués permettant le rendu des modèles associés à chacun de ces templates.

L’exemple suivant permet le rendu des URLs :

  • /books
  • /books/:book_id
  • /books/:book_id/edit
  • /books/create
// app/router.js

Router.map(function() {
  this.route('books', function() {
    this.route('book', { path: '/:book_id' }, function () {
      this.route('edit');
    });
    this.route('create');
  });
});

Routes

Les routes sont en charge de la récupération d’un modèle associé à la requête de l’utilisateur puis de l’association avec un contrôleur (et de son initialisation) et un template (ainsi que de son rendu). La récupération du modèle ainsi que l’association entre un (ou plusieurs) modèle(s) et un (ou plusieurs) template(s) implique également la gestion des transitions entre les différentes URLs de l’application.

// app/routes/books.js

import Route from '@ember/routing/route';

export default Route.extend({
  model: function () {
    return this.store.findAll('book');
  }
});

-> doc officielle.

Templates

Un template est un fragment de code HTML permettant, via des expressions, d’afficher les données du modèle associé. Les templates d’Ember sont des templates Handlebars. Les expressions Handlebars sont délimitées par {{ et }}.

L’exemple suivant permet d’afficher le titre d’une app composé d’un prénom et d’un nom pour peu que l’on ait passé au template un modèle contenant les deux propriétés firstname et lastname.

<h1>{{firstname}} {{lastname}} Library</h1>

Handlebars vient avec de nombreux outils (helpers) permettant de dynamiser nos templates : {{#if isActive}} ... {{/if}}, {{#each users}} ... {{/each}}, etc.

Dans Ember, les templates peuvent contenir un élément très important : un {{outlet}}. Cet outlet définit un emplacement pour un autre template permettant ainsi de multiples imbrications à mesure que les routes de l’application sont activées.

<h1>{{firstname}} {{lastname}} Library</h1>

<div>
  {{outlet}}
</div>

Tout élément de modèle injecté dans un template sera automatiquement mis à jour (binding) par Ember lorsque le modèle associé au template sera modifié. Évidemment, seul cet élément sera rafraîchi et non le template entier. Ce binding, qu’il soit unidirectionnel ou bidirectionnel est au coeur du fonctionnement d’Ember.

-> doc officielle.

Modèles

Un modèle est un objet avec des propriétés contenant des données métier. Le modèle est ensuite passé au template pour être rendu par celui-ci en HTML. Typiquement, les modèles peuvent être récupérés d’un back end via une API REST JSON via Ember Data abordé plus loin, mais pas uniquement. Dans le premier cas, il s’agit d’un objet de type DS.Model, DS étant le namespace commun à tous les éléments d’Ember Data.

// app/models/book.js

import DS from 'ember-data';

export default DS.Model.extend({
  title               : DS.attr('string'),
  publicationDate     : DS.attr('date'),
  author              : DS.attr('string'),
  publisher           : DS.attr('string'),
  summary             : DS.attr('string')
});

Cependant, l’ensemble des mécanismes décrits plus bas (les bindings notamment) peuvent parfaitement fonctionner en s’appuyant directement sur le modèle objet d’ember et la classe Ember.Object en détail.

-> doc officielle.

Contrôleurs

Le contrôleur, qui n’apparaît pas sur la figure ci-dessus, gère l’état de l’application. Il est situé entre la route dont il récupère le modèle et le template dont il répond aux appels. Le template accède aux données du contrôleur et le contrôleur accède aux données du modèle. Le contrôleur est par exemple responsable du traitement des actions effectuées par l’utilisateur sur l’interface rendue par le template :

<button {{action "sort"}}></button>
// app/controllers/books.js

import Controller from '@ember/controller';

export default Controller.extend({
  actions: {

    // appelé lors du clic sur le bouton
    sort: function () {
      ...
    }
  }
});

-> doc officielle.

Note: Les contrôleurs Ember sont appelés à disparaître progressivement au profit de l’utilisation de composants routables. Ce qui explique l’absence des contrôleurs sur la figure ci-dessus.

Composants

Un composant Ember permet de partager de puissants éléments réutilisables au sein d’une application Ember. Depuis toujours Ember met fortement en avant son approche composants. Avec la version 2, celle-ci s’est encore renforcée avec la disparition des vues remplacées par des composants. Les composants Ember s’articulent autour d’une partie template et/ou d’une partie logique. Dans les prochaines versions à venir, l’apparition de composants routables devrait rendre l’utilisation de contrôleurs seuls obsolète.

-> doc officielle.

Génération d’objets

Pour qu’un template soit rendu lorsqu’une URL est demandée, il faut donc que le routeur définisse cette URL, qu’elle soit implémentée par une route qui récupèrera un modèle qu’elle mettra à disposition du contrôleur et du template. Le contrôleur écoutera les évènements en provenance du template et y apportera la réponse adaptée. À noter que l’évènement peut également remonter jusqu’à la route.

Un principe important d’Ember est cependant qu’il n’est pas nécessaire de créer systématiquement tous ces objets si aucune logique spécifique n’a besoin d’y être définie. En effet, Ember s’appuie sur les conventions de nommage pour retrouver successivement, à partir d’une URL, la route, le contrôleur et le template associés. Si l’un de ces objet n’est pas trouvé, Ember va en générer un par défaut.

Donc si l’on crée dans le routeur la route suivante sans créer aucun autre objet :

Router.map(function() {
  this.route("about", { path: "/about" });
});

Ember va générer les objets suivants :

  • route : AboutRoute
  • contrôleur : AboutController
  • template : about

Dans une application Ember, il n’est donc nécessaire de définir que ce dont on a besoin !

-> doc officielle.

Un bon moyen de se rendre compte de ça consiste à installer le plugin Ember le navigateur de votre choix. Vous aurez, entre autres, la liste de l’ensemble des objets impliqués dans le rendu d’une URL donnée. Cette liste distingue de manière claire les objets créés par vous et ceux générés par Ember.

Ce module s’appelle Ember Inspector et est disponible pour Chrome et Firefox. C’est absolument indispensable lorsqu’on développe en Ember.

Conclusion

Ember est donc un framework très riche et extrêmement plaisant à pratiquer. C’est aussi et surtout un vrai framework avec un vrai parti pris et des vrais choix structurants.

Il est résolument tourné vers le web et les URLs. Ses créateurs sont également ceux de son moteur de templates Handlebars et sont très impliqués dans diverses initiatives autour de la standardisation et de l’évolution du web. Pour n’en citer que deux : JSON API et Web Components, notamment au travers de son moteur de rendu Glimmer.

Ils embrassent très rapidement les nouveaux standards tels que ES6 Harmony à l’image des travaux effectués autour d’ember-cli.

Enfin, contrairement aux a priori, la courbe d’apprentissage d’Ember est progressive et il est très simple à prendre en main une fois les concepts de base appréhendés.