Développez et déployez votre projet

Un workflow de développement efficace repose sur des outils bien configurés et des processus automatisés. Dans ce chapitre, nous verrons les essentiels pour développer et déployer votre application Angular.

Maîtrisez le développement

Une application bien architecturée dépend d'un workflow de développement bien rodé. Plusieurs outils et approches vous aideront à maximiser votre efficacité.

Différenciez vos environnements

L'outil de base pour séparer votre environnement de développement de votre environnement de production (et/ou de staging) est le fichier d'environnement. Lorsque vous avez des variables dont la valeur dépend de l'environnement, cette fonctionnalité vous sera très utile.

Généralement, on crée un dossier  environments  dans le dossier  src  . La norme de nomenclature est que le fichier pour le développement s'appelle simplement  environment.ts  , pour la production  environment.prod.ts  , pour le staging  environment.staging.ts  etc.

Voici un exemple de paire dev/prod avec des exemples de variables typiques :

// environment.ts (développement)
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api',
  enableLogging: true,
  enableDebugTools: true,
  featureFlags: {
    newDashboard: true,
    paymentModule: false
  },
  auth: {
    clientId: 'dev-client-id',
    redirectUri: 'http://localhost:4200/auth/callback'
  }
};

// environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.myapp.com/v1',
  enableLogging: false,
  enableDebugTools: false,
  featureFlags: {
    newDashboard: true,
    paymentModule: true
  },
  auth: {
    clientId: 'prod-client-id',
    redirectUri: 'https://myapp.com/auth/callback'
  }
};

Pour rendre la configuration type-safe, vous pouvez créer une interface et une injection token :

export interface EnvironmentConfig {
  production: boolean;
  apiUrl: string;
  enableLogging: boolean;
  enableDebugTools: boolean;
  featureFlags: {
    newDashboard: boolean;
    paymentModule: boolean;
  };
  auth: {
    clientId: string;
    redirectUri: string;
  };
}

export const ENV_CONFIG = new InjectionToken<EnvironmentConfig>('env.config');

Ce qui permet de fournir le contenu du fichier environment comme provider :

import { environment } from './environments/environment';
//...

bootstrapApplication(App, {
  providers: [
    { provide: ENV_CONFIG, useValue: environment },
    //...
  ]
});

Pour ensuite l'utiliser comme ceci :

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);
  private config = inject(ENV_CONFIG);

  getUsers() {
    return this.http.get(`${this.config.apiUrl}/users`);
  }

  isFeatureEnabled(feature: keyof AppConfig['featureFlags']): boolean {
    return this.config.featureFlags[feature];
  }
}

Il faut spécifier, dans angular.json, quel fichier d'environnement doit être utilisé pour quel environnement. Voici un exemple :

"configurations": {
    "production": {
        "fileReplacements": [
            {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
            }
        ],
        //...
    },
    "staging": {
        "fileReplacements": [
            {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.staging.ts"
            }
        ],
        //...
    }
}
Allez plus loin avec un proxy

En production, il nous arrive souvent d'avoir les différents services dont dépend notre application (API, auth, buckets…) exposés sur des endpoints spécifiques du même hôte. Souvent, en développement, soit ces services tournent en local (sur un autre port de localhost), soit sur un serveur distant spécifique au développement.

Pour faciliter ce genre de workflow, vous pouvez mettre en place un proxy. Un proxy intercepte toutes les requêtes vers les routes pour lesquelles il est configuré, et les redirige de manière totalement opaque : ni votre application ni votre navigateur ne savent qu'ils ont été redirigés.

Pour configurer un proxy, il vous suffit de créer un fichier  proxy.conf.json  , généralement à la racine du projet, puis de le configurer dans  angular.json  :

// proxy.conf.json
{
  "/api/*": {
    "target": "http://localhost:3000",
    "secure": false,
    "changeOrigin": true,
  },
  "/auth/*": {
    "target": "http://localhost:8080",
    "secure": false,
    "changeOrigin": true
  },
  "/uploads/*": {
    "target": "https://storage.googleapis.com",
    "secure": true,
    "changeOrigin": true,
    "pathRewrite": {
      "^/uploads": "/my-bucket"
    }
  }
}

// angular.json

{
  "serve": {
    "builder": "@angular/build:dev-server",
    "options": {
      "proxyConfig": "proxy.conf.json"
    }
  }
}

Dans cet exemple :

  • les requêtes sont redirigées :

    • de localhost:4200/api/* vers localhost:3000/*

    • de localhost:4200/auth/* vers localhost:8080/auth/* 

    • localhost:4200/uploads/* vers storage.googleapis.com/my-bucket/* (grâce au  pathRewrite  )

  • dans les deux premiers cas, on utilise  secure: false  pour permettre l'accès à un URL non-sécurisé (sans HTTPS)

  • dans tous les cas, on utilise  changeOrigin: true  pour modifier le header Origin des requêtes pour qu'elles matchent avec l'hôte d'arrivée (pour éviter les erreurs CORS)

Après avoir déclaré la configuration dans  angular.json  , il n'y a plus qu'à faire  ng serve  et le tour est joué !

Garantissez la qualité de votre code

Il y a deux types principaux d'outils pour contrôler la qualité du code : les linters et les formatters.

  • le linting analyse votre code pour détecter des erreurs potentielles, des anti-patterns et faire respecter des règles de qualité logique ; il se concentre sur ce que fait votre code et comment il est structuré

  • Le formatage s'occupe de l'apparence visuelle du code : espacement, indentation, position des accolades, longueur des lignes ; il ne change pas la logique, seulement la présentation

Il y a plusieurs avantages à utiliser des outils automatisés pour ces choses :

  • cohérence du code : tous les développeurs qui travaillent sur le codebase appliquent le même style et les mêmes règles

  • réduction d'erreurs : les linters réduisent les erreurs fonctionnelles, et avec les formatters, on évite beaucoup plus d'erreurs de merge

  • gain de productivité : les développeurs peuvent se concentrer sur l'essentiel de leur travail, en automatisant le style et les règles

Linting

Pour le linting, l'outil de loin le plus utilisé est ESLint. Pour l'installer avec une configuration par défaut, il suffit d'exécuter, depuis une ligne de commande :

ng add angular-eslint

Cela vous permet de faire :

ng lint

qui fera remonter les différents soucis que vous pouvez avoir dans votre code. Vous pouvez également configurer votre IDE pour faire apparaître les warnings et erreurs remontés par ESLint.

Entre l'intégration IDE et la commande du CLI d'Angular, ESLint est un super outil pour le workflow de développement.

Formatting

Pour le formatting, l'outil le plus répandu est Prettier. Il est très simple et très opinioné (il n'y a que très peu d'options de configuration) et permet une cohérence de code style sans tous les débats que cela peut engendrer ! Pour ajouter Prettier à votre projet, il faut plusieurs packages (si vous avez déjà ESLint, car les deux peuvent se trouver en désaccord !) :

npm i --save-dev prettier eslint-config-prettier eslint-plugin-prettier

Ensuite vous créez un fichier  .prettierrc  à la racine de votre projet qui contiendra la configuration. Voici un exemple :

{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "bracketSpacing": true,
  "arrowParens": "avoid"
}

Cette configuration met en place les "bases" : longueur de ligne maximum 100 caractères ; utiliser deux espaces pour l'indentation ; rendre obligatoires les point-virgules, les apostrophes, et les virgules en fin de liste ; ajouter des espaces à l'intérieur des crochets et accolades ; formater les fonctions arrow ( => ) sans parenthèses quand c'est possible.

Il faut ensuite ajouter à votre  eslint.config.js  :

const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended');

//...

module.exports = tseslint.config(
  {
    files: ['**/*.ts'],
    extends: [
      // les autres configurations...
      eslintPluginPrettierRecommended,
    ],
//...

Comme ça, non seulement il n'y a plus de conflits entre ESLint et Prettier, mais les soucis de formatage apparaissent comme des erreurs de linting !

Passez en déploiement

Maintenant que vous avez établi votre workflow de développement, regardons comment faire passer votre code de votre machine locale à la production.

Déployez avec le CLI

Le CLI d'Angular permet un déploiement automatique vers plusieurs services avec des builders spécifiques. Voici une liste de services avec la commande à exécuter pour préparer son déploiement :

  • Firebase —  ng add @angular/fire

  • Netlify —  ng add @netlify-builder/deploy

  • GitHub pages —  ng add angular-cli-ghpages

  • Amazon Cloud S3 —  ng add @jefiozie/ngx-aws-deploy

Une fois votre setup terminé, vous pouvez déployer avec :

ng deploy

C'est peut-être l'approche la plus simple pour un déploiement qui va directement de votre machine à votre environnement de production, mais qu'est-ce qui se passe si vous utilisez un autre service d'hébergement, ou même votre propre serveur sur un VPS (ou même chez vous !) ?

Déployez manuellement

Pour préparer votre application pour un déploiement manuel, il faut générer un build. Avec le CLi, il suffit de faire :

ng build -c=production

Cette commande vous génère un dossier dist qui contient toute votre application. Voici un exemple très simple :

Vue d’un répertoire de build d’une application web avec les fichiers : favicon.ico, index.html, un JS (main-QVAPV5EX.js) et un CSS (styles-5INURTSO.css) versionnés.
Les artefacts de build

Ce sont tous les fichiers de votre application, mais ça ne suffit pas pour qu'elle soit totalement fonctionnelle. Même si votre serveur sert index.html sans problème, si vous essayez d'accéder aux différentes routes de votre application, vous aurez des erreurs 404. Pourquoi ? Parce qu'il faut rediriger toutes les requêtes qui ne correspondent pas à des fichiers vers  index.html.

Pour un serveur Nginx, la partie "redirection" ressemble à ça :

location / {
    try_files $uri $uri/ /index.html;
}

Pour Apache, voici le minimum :

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

Que ce soit avec ng deploy ou par build et déploiement manuels, ce sont les deux façons pour pousser votre code depuis votre machine locale vers votre environnement de production. Ces approches peuvent suffire lorsqu'on est tout seul, mais ça ne suffit pas pour plusieurs développeurs, voire plusieurs équipes. Pour ça, il faut de l'automatisation !

Intégrez et déployez en continu

Holly Cummins de l'équipe Quarkus le dit très bien :

"Déployer, c'est comme se laver les dents. Si ça fait mal, c'est qu'il faut le faire plus souvent, pas moins."

Les pratiques d'intégration continue et de déploiement continu (CI/CD) sont le sujet de livres entiers. Dans ce cours, je ne chercherai pas à vous convaincre de leur utilité (dont les preuves abondent) : on regardera simplement les étapes d'un pipeline typique pour une application Angular.

Le pipeline CI (continuous integration) est exécuté après chaque push sur la branche main.

Généralement le pipeline CI :

  • vérifie le linting avec  ng lint

  • exécute les tests unitaires — attention,  ng test  tout seul ne suffira pas, car il faut les exécuter une seule fois et sans accès à un navigateur

  • vérifie le build de l'application avec  ng build -c=production

  • en option, vous pouvez exécuter les tests end-to-end si vous en avez, mais ça ralentira le pipeline

Vous avez ensuite l'option d'ajouter des étapes CD (continuous deployment) où votre pipeline fera le nécessaire pour déployer la nouvelle version de votre application (avec  ng deploy  , des commandes SSH, les webhooks…selon votre configuration).

Voici un exemple de configuration GitHub Actions qui gère l'intégration continue, suivi du déploiement par SSH sur un VPS doté de Nginx :

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  NODE_VERSION: '22'

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Lint code
      run: ng lint
    
    - name: Run unit tests
      run: ng test --watch=false --browsers=ChromeHeadless --code-coverage
    
    - name: Build application
      run: ng build --configuration production
    
    - name: Deploy to VPS
      if: github.ref == 'refs/heads/main'
      uses: appleboy/ssh-action@v1.0.3
      with:
        host: ${{ secrets.VPS_IP }}
        username: ${{ secrets.VPS_USERNAME }}
        key: ${{ secrets.VPS_SSH_KEY }}
        script: |
          cd /path/to/your/app
          git pull origin main
          npm ci
          ng build --configuration production
          sudo systemctl restart nginx

À vous de jouer

Contexte

Je suis prêt à parier que vous avez au moins une application Angular que vous avez créée à un moment ou un autre et que vous n'avez jamais déployé…et bien, c'est l'occasion !

Consigne

Mettez en place un pipeline d'intégration et de déploiement continus pour votre application et déployez-la en production ! Vous avez plusieurs possibilités gratuites pour l'hébergement des pipelines (GitHub, GitLab, Azure DevOps…) comme pour le déploiement (Firebase, GitHub Pages…). Pour un challenge supplémentaire, louez un VPS chez un hébergeur comme DigitalOcean ou Hostinger et déployez sur une VM que vous contrôlez !

En résumé

  • Gérez vos environnements (dev, staging, prod) via des fichiers d’environnement. Utilisez des injection tokens pour une configuration type-safe et des proxies pour router les requêtes API en phase de développement.

  • Automatisez la qualité de votre code en configurant ESLint pour détecter les problèmes de syntaxe et de style, et Prettier pour assurer un formatage uniforme. En les intégrant à votre IDE et à vos scripts CLI, vous améliorez la lisibilité, la cohérence et la fiabilité de votre base de code.

  • simplifiez le déploiement de votre application grâce à la commande ng deploy pour les services compatibles (Firebase, Netlify, GitHub Pages). Pour d’autres environnements, effectuez un déploiement manuel via ng build, accompagné d’une configuration serveur adaptée à la gestion du routing côté client.

  • Automatisez l’intégration et le déploiement de votre application grâce à une chaîne CI/CD complète. Celle-ci comprend le linting, les tests unitaires, la génération du build de production et le déploiement automatique. Vous pouvez utiliser GitHub Actions pour créer un workflow simple et reproductible de la validation du code jusqu’à la mise en production.

Après ce dernier chapitre, il est temps de valider vos connaissances avec un quiz !

Ever considered an OpenClassrooms diploma?
  • Up to 100% of your training program funded
  • Flexible start date
  • Career-focused projects
  • Individual mentoring
Find the training program and funding option that suits you best