Blog IT nouvelles technologies du web


Un développeur veille pour rester éveillé...


Utiliser Typescript dans une application Angular 1.x

A l’heure ou cet article est rédigé, AngularJS est la technologie front web la plus répandue en entreprise. Ce n’est de ce fait pas la plus récente. Cela étant, il est possible de lui adosser un langage comme Typescript plus précis et ordonné que ne l’est le Javascript.

Je ne dénigre pas le fait que Javascript soit un langage non typé, cela a ses avantages. Mais dans un contexte d’entreprise où le turnover d’un projet est important, un langage typé comme Typescript favorise la prise en main du code par les nouveaux membres tout étant garant des règles métier, mieux respectées car clairement décrites.

Afin de simplifier l’installation des dépendences du projet, nous utiliserons NPM comme seul gestionnaire de package.

AngularJS

npm install --save angular

AngularJS sera installé dans le dossier node_modules.

Typescript

Je vous recommande d’installer typescript localement pour préserver votre projet de toute erreur/warning dus à une montée de version innoportune.

npm install --save-dev typescript

Puis placez également ce fichier de configuration Typescript minimaliste à la racine du projet. Il sera suffisant pour ce tuto.

tsconfig.json :

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs"
    }
}

AngularJS étant une librairie web non typée (pure JS), il est nécessaire de référencer les types angular pour Typescript :

npm install --save-dev @types/angular

Webpack

Webpack nous permettra ici d’insérer la transpilation Typescript vers Javascript dans le processus de packaging des fichiers.

npm install --save-dev webpack

Afin de faire communiquer Webpack avec Typescript nous avons besoin d’un loader webpack nommé awesome-typescript-loader, ainsi qu’un générateur de source map optionnel source-map-loader.

npm install --save-dev awesome-typescript-loader source-map-loader

Il existe une alternative à awesome-typescript-loader nommée ts-loader, faites-vous une opinion…

Webpack nécessite également un peu de configuration. Placez ce fichier de configuration (webpack.config.js) à la racine du projet:

module.exports = {
    entry: './app/app.ts',
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    devtool: 'source-map',
    module: {
        loaders: [{
            test: /\.ts$/,
            loaders: [
                'awesome-typescript-loader',
                'source-map-loader'
            ]
        }]
    },
    externals: {
        angular: "angular"
    }
};

De haut en bas, il nous indique que :

  • le point d’entrée de l’arbre de dépendences de notre application est le fichier ./app/app.ts,
  • le bundle créé sera ./dist/bundle.js,
  • les source maps seront générés en sortie (bundle.js.map)
  • les fichiers dont l’extension est .ts passeront par deux loaders successifs: awesome-typescript-loader (un passe plat vers typescript) et source-map-loader qui met à disposition de Webpack les source maps Typescript.
  • enfin les instructions import référencant le package angular seront redirigées vers la variable globale “angular”.

Cette dernière instruction est importante pour éviter les erreurs de transpilation de Typescript.

Application

Nous avons besoin d’une page HTML statique pour accueillir l’application. Le framework AngularJS est référencé directement depuis le dossier node_modules, ainsi que notre bundle de sortie.

index.html :

<html ng-app="tsDemo">

<head>
    <title>Angular 1.x + Typescript</title>
    <script src="node_modules/angular/angular.min.js"></script>

    <script src="dist/bundle.js"></script>
</head>

<body>
    <hello />
</body>

</html>

Notre point d’entrée applicatif,

./app/app.ts :

import * as angular from "angular";
import { Hello } from "./helloComponent.ts";

const app: ng.IModule = angular.module('tsDemo', ['ng']);

app.component('hello', new Hello());

Ma première approche pour l’utilisation des fonctions AngularJS en Typescript a été de rajouter les noeuds “typeRoots” et “types” au fichier de configuration tsconfig.json afin de déclarer angular comme une variable globale connue. Il s’agit de la façon la plus documentée sur le net.

Malheureusement cela ne fonctionne pas si l’on utilise notre fichier comme un module node (cad avec des références à d’autres modules via les imports). Il aurait fallut mettre en place des namespaces cassant de ce fait les liens de dépendances entre les fichiers.

Voilà pourquoi l’instruction import * as angular from "angular"; se retrouvera en tête de chaque fichier manipulant la globale angular et que le fichier webpack.config.js place un hook sur le module externe “angular”.

./app/helloComponent.ts :

class HelloController {
  public message: string;

  constructor() {
    this.message = 'Hello World !';
  }
}

export class Hello implements ng.IComponentOptions {
  public template: string = "<h1>{{$ctrl.message}}</h1>";
  public controller: any;
  public controllerAs: string = "$ctrl";

  constructor() {
    this.controller = HelloController;
  }
}

La notion de component dans AngularJS est récente (depuis la version 1.5), vous trouverez plus d’infos ici : Understanding Components

Build

Pour lancer la packaging de l’application avec build intégré, il suffit d’exécuter webpack.

Par exemple, en rajoutant un custom script à votre package.json :

{
  ...
  "scripts": {
    "build": "webpack"
  }
  ...
}

Puis en exécutant : npm run build

Le dossier ./dist/ sera créé avec deux fichiers à l’intérieur : bundle.js et bundle.js.map.

Ouvrez le fichier index.html dans un navigateur afin de voir le résultat.

Beaucoup de configuration et de setup pour si peu de code applicatif me direz-vous, mais gardez à l’esprit que le code croît alors que la config reste plus ou moins figée dans le temps.

N’hésitez pas à poser vos questions sur ce setup si vous êtes bloqués.