🚀 Formation complète vidéo, avec Angular Intelligence

Précommande -57% jusqu'au 18 juin

Découvrez les nouveautés d'Angular 20 en quelques minutes

Angular 20 arrive avec plusieurs nouveautés et stabilisation des API: Zoneless, les APIs resource() et httpResource(), un nouveau runner de tests, etc. La vidéo vous donne un aperçu de ces nouveautés.

Abonnez-vous à notre chaîne

Pour profiter des prochaines vidéos sur Angular, abonnez-vous à la nouvelle chaîne YouTube !

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

Intégrer l'IA dans Angular avec Genkit

L'intelligence artificielle transforme la façon dont nous développons des applications web. Avec Angular 20 et Genkit, vous pouvez désormais créer des expériences utilisateur intelligentes et interactives en quelques lignes de code.

Imaginez une application de restaurant où les clients peuvent demander des suggestions de plats personnalisées. Au lieu de parcourir un menu statique, ils décrivent leurs préférences ("quelque chose de végétarien et épicé") et l'IA génère instantanément des recommandations adaptées. C'est exactement ce que nous allons construire ensemble !

Qu'est-ce que Genkit ?

Genkit est un framework open-source développé par Google qui simplifie l'intégration de l'IA dans vos applications. Il vous permet de :

  • Unifier l'accès à plusieurs modèles IA (Gemini, OpenAI, Anthropic, etc.)
  • Créer des workflows intelligents avec des chaînes d'inférences
  • Déployer facilement sur Cloud Functions, Cloud Run ou tout serveur Node.js

Pourquoi Genkit avec Angular ?

Angular 20 introduit une section dédiée "Build with AI" qui facilite l'intégration de l'IA. La combinaison d'Angular et Genkit offre une architecture robuste pour créer des applications intelligentes avec une expérience utilisateur fluide.

Prérequis

Avant de commencer, assurez-vous d'avoir :

OutilVersionInstallation
Node.js≥ 20 LTSnodejs.org
Angular CLIDernière versionnpm install -g @angular/cli
Genkit CLIDernière versionnpm install -g genkit-cli
Clé APIGemini ou autreGoogle AI Studio

Création du projet

Étape 1 : Initialiser le projet Angular avec SSR

Nous allons créer un projet Angular avec le rendu côté serveur (SSR) pour pouvoir utiliser Genkit :

bash
ng new restaurant-ai-app --ssr --server-routing
cd restaurant-ai-app

Importance du SSR

Genkit nécessite un environnement serveur pour fonctionner. Le SSR d'Angular nous fournit cette infrastructure nécessaire.

Étape 2 : Installation des dépendances Genkit

Installons les packages nécessaires pour intégrer Genkit :

bash
# Core Genkit
npm install genkit

# Plugin pour Google AI (Gemini)
npm install @genkit-ai/googleai

# Middleware Express pour exposer les flows
npm install @genkit-ai/express

Configuration de l'environnement

Configuration des variables d'environnement

Créez un fichier .env à la racine du projet :

bash
GEMINI_API_KEY=votre_clé_api_ici

Sécurité des clés API

Ne jamais inclure vos clés API dans le code source. Utilisez toujours des variables d'environnement ou un gestionnaire de secrets en production.

Création du workflow IA

Définition du Flow Genkit

Créons notre premier flow IA. Créez le fichier src/genkit/menu-suggestion-flow.ts :

typescript
import { genkit } from 'genkit';
import { googleAI } from '@genkit-ai/googleai';
import { z } from 'zod';

// Configuration de Genkit avec le plugin Google AI
const ai = genkit({ 
  plugins: [googleAI()] 
});

/**
 * Flow pour générer des suggestions de menu basées sur un thème
 * 
 * Ce flow utilise Gemini pour créer des suggestions de plats personnalisées.
 * Il prend en entrée un thème (ex: "végétarien épicé") et génère une suggestion
 * de plat avec description.
 * 
 * @example
 * ```typescript
 * const result = await menuSuggestionFlow({ theme: "italien moderne" });
 * console.log(result.menuItem); // "Risotto aux champignons truffés..."
 * ```
 */
export const menuSuggestionFlow = ai.defineFlow(
  {
    name: 'menuSuggestionFlow',
    inputSchema: z.object({ 
      theme: z.string().describe('Le thème ou style de cuisine souhaité') 
    }),
    outputSchema: z.object({ 
      menuItem: z.string().describe('La suggestion de plat générée'),
      description: z.string().describe('Description détaillée du plat'),
      price: z.string().describe('Prix estimé du plat')
    }),
    streamSchema: z.string(),
  },
  async ({ theme }, { sendChunk }) => {
    // Génération en streaming pour une meilleure UX
    const { stream, response } = ai.generateStream({
      model: googleAI.model('gemini-2.0-flash'),
      prompt: `Tu es un chef cuisinier créatif. Crée un plat unique pour un restaurant ${theme}.
      
      Réponds au format JSON avec :
      - menuItem: nom du plat (maximum 50 caractères)
      - description: description appétissante (maximum 150 caractères)  
      - price: prix en euros (format "XX€")
      
      Sois créatif et original !`,
    });

    // Envoi des chunks en temps réel
    for await (const chunk of stream) {
      sendChunk(chunk.text);
    }

    const { text } = await response;
    
    try {
      // Parse de la réponse JSON
      const parsed = JSON.parse(text);
      return {
        menuItem: parsed.menuItem || 'Plat surprise',
        description: parsed.description || 'Une création unique de notre chef',
        price: parsed.price || '25€'
      };
    } catch (error) {
      // Fallback en cas d'erreur de parsing
      return {
        menuItem: 'Plat du jour',
        description: text.substring(0, 150),
        price: '20€'
      };
    }
  }
);

Comprendre defineFlow

La méthode defineFlow est le cœur de Genkit. Elle permet de créer un workflow IA réutilisable avec :

  • name : Identifiant unique du flow pour le monitoring et les logs
  • inputSchema : Validation Zod des données d'entrée (sécurité et typage)
  • outputSchema : Structure attendue de la réponse finale
  • streamSchema : Type des données envoyées en streaming (ici du texte)

Comprendre sendChunk

La fonction sendChunk permet d'envoyer des données en temps réel au client :

  • Streaming en temps réel : Chaque morceau de texte généré est immédiatement envoyé
  • Meilleure UX : L'utilisateur voit la réponse se construire progressivement
  • Réactivité : Pas d'attente de la réponse complète avant affichage

Exposition du Flow côté serveur

Modifions le fichier src/server.ts pour exposer notre flow avec support du streaming :

typescript
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './main.server';
import { expressHandler } from '@genkit-ai/express';
import { menuSuggestionFlow } from './genkit/menu-suggestion-flow';

const server = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');

// Configuration pour les requêtes JSON
server.use(express.json());

// Exposition du flow IA classique (réponse complète)
server.post('/api/menu-suggestion', expressHandler(menuSuggestionFlow));

// Endpoint de streaming pour une expérience temps réel
server.post('/api/menu-suggestion-stream', async (req, res) => {
  const { theme } = req.body;
  
  if (!theme) {
    return res.status(400).json({ error: 'Theme requis' });
  }

  // Configuration des headers pour le streaming
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  res.setHeader('Transfer-Encoding', 'chunked');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  try {
    // Utilisation du flow avec streaming
    const flowResult = await menuSuggestionFlow({ theme });
    
    // Le streaming est géré automatiquement par Genkit
    // Les chunks sont envoyés via sendChunk dans le flow
    
    // Fin du stream
    res.end();
    
  } catch (error) {
    console.error('Erreur streaming:', error);
    res.status(500).end('Erreur lors de la génération');
  }
});

const commonEngine = new CommonEngine();

// ... reste de la configuration serveur

Configuration du streaming serveur

Le streaming côté serveur nécessite :

  • Headers HTTP appropriés : Content-Type: text/plain, Transfer-Encoding: chunked
  • Pas de cache : Cache-Control: no-cache pour éviter la mise en cache
  • Connexion persistante : Connection: keep-alive pour maintenir le flux

Création de l'interface utilisateur

Service pour l'IA

Créons un service pour gérer les interactions avec notre flow IA. Créez src/app/core/services/ai.service.ts :

typescript
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

/**
 * Service pour gérer les interactions avec l'IA
 * 
 * Ce service encapsule les appels vers nos flows Genkit et fournit
 * une interface simple pour les composants Angular.
 */
@Injectable({
  providedIn: 'root'
})
export class AiService {
  private http = inject(HttpClient);

  /**
   * Génère une suggestion de menu basée sur un thème
   * 
   * Cette méthode appelle notre flow Genkit pour obtenir une suggestion
   * de plat personnalisée selon les préférences de l'utilisateur.
   * 
   * @param theme - Le thème ou style de cuisine souhaité
   * @returns Observable contenant la suggestion générée
   * 
   * @example
   * ```typescript
   * this.aiService.generateMenuSuggestion('cuisine asiatique fusion')
   *   .subscribe(suggestion => {
   *     console.log(suggestion.menuItem);
   *   });
   * ```
   */
  generateMenuSuggestion(theme: string): Observable<{
    menuItem: string;
    description: string;
    price: string;
  }> {
    return this.http.post<{
      menuItem: string;
      description: string;
      price: string;
    }>('/api/menu-suggestion', { theme });
  }
}

Interface utilisateur avec streaming

Créons l'interface utilisateur avec streaming en temps réel dans src/app/features/menu/menu.component.ts :

typescript
import { Component, signal, resource, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

/**
 * Composant pour la génération de suggestions de menu par IA avec streaming
 * 
 * Ce composant utilise l'API `resource` d'Angular pour afficher les réponses
 * de l'IA en temps réel, créant une expérience utilisateur fluide.
 */
@Component({
  selector: 'app-menu',
  imports: [FormsModule],
  template: `
    <div class="menu-generator">
      <h2>🍽️ Générateur de Menu IA</h2>
      
      <div class="input-section">
        <label for="theme">Décrivez vos envies :</label>
        <input 
          id="theme"
          type="text" 
          [(ngModel)]="themeInput"
          placeholder="Ex: végétarien épicé, cuisine italienne moderne..."
          class="theme-input"
          (keyup.enter)="startGeneration()"
        />
        <button 
          (click)="startGeneration()" 
          [disabled]="menuResource.isLoading() || !themeInput().trim()"
          class="generate-btn"
        >
          @if (menuResource.isLoading()) {
            <span class="spinner"></span> Génération...
          } @else {
            ✨ Générer une suggestion
          }
        </button>
      </div>

      @if (menuResource.value()) {
        <div class="suggestion-card">
          <div class="streaming-content">
            {{ menuResource.value()!.value }}
            @if (menuResource.isLoading()) {
              <span class="cursor">|</span>
            }
          </div>
        </div>
      }

      @if (menuResource.error()) {
        <div class="error-message">
          ❌ Erreur lors de la génération. Veuillez réessayer.
        </div>
      }
    </div>
  `
})
export class MenuComponent {
  // Signal pour l'input utilisateur
  themeInput = signal('');
  
  // Signal pour déclencher la génération
  private triggerGeneration = signal<string | null>(null);

  /**
   * Resource pour gérer le streaming des réponses IA
   * 
   * Cette resource utilise l'API streaming d'Angular pour afficher
   * les réponses de l'IA en temps réel, chunk par chunk.
   */
  menuResource = resource({
    request: () => this.triggerGeneration(),
    stream: async ({ request }) => {
      if (!request) return null;

      // Signal pour accumuler les chunks reçus
      const streamData = signal<{ value: string } | { error: unknown }>({
        value: "",
      });

      try {
        // Appel à notre API de streaming
        const response = await fetch('/api/menu-suggestion-stream', {
          method: 'POST',
          headers: { 
            'Content-Type': 'application/json',
            'Accept': 'text/plain'
          },
          body: JSON.stringify({ theme: request })
        });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }

        if (!response.body) {
          throw new Error('Pas de body dans la réponse');
        }

        // Lecture du stream avec ReadableStream
        const reader = response.body.getReader();
        const decoder = new TextDecoder();

        while (true) {
          const { done, value } = await reader.read();
          
          if (done) break;

          // Décodage du chunk et mise à jour du signal
          const chunkText = decoder.decode(value, { stream: true });
          
          streamData.update((prev) => {
            if ("value" in prev) {
              return { value: prev.value + chunkText };
            } else {
              return { error: chunkText };
            }
          });
        }

      } catch (error) {
        streamData.set({ error: error });
      }

      return streamData;
    },
  });

  /**
   * Démarre la génération d'une suggestion de menu
   * 
   * Cette méthode déclenche le resource streaming en mettant à jour
   * le signal triggerGeneration avec le thème saisi par l'utilisateur.
   */
  startGeneration(): void {
    const theme = this.themeInput().trim();
    
    if (!theme) {
      return;
    }

    // Déclenche la resource en mettant à jour le signal
    this.triggerGeneration.set(theme);
  }
}

Comprendre resource avec streaming

L'API resource d'Angular est parfaite pour le streaming IA :

  • request : Signal qui déclenche la resource quand il change
  • stream : Fonction asynchrone qui gère le flux de données en temps réel
  • Réactivité : Mise à jour automatique du template à chaque chunk reçu
  • Gestion d'état : isLoading(), value(), error() automatiquement gérés

Test et développement

Lancement de l'application

Pour tester votre application :

bash
# Définir la clé API
export GEMINI_API_KEY=votre_clé_api

# Lancer l'application
ng serve

Votre application sera accessible sur http://localhost:4200.

Interface de développement Genkit

Lancez genkit start dans un autre terminal pour accéder à l'interface de développement Genkit avec les métriques et logs.

Avantages du streaming avec Angular

Le streaming offre plusieurs avantages pour les applications IA :

  • Perception de rapidité : L'utilisateur voit immédiatement que quelque chose se passe
  • Engagement utilisateur : L'effet "machine à écrire" maintient l'attention
  • Gestion des timeouts : Évite les timeouts sur les longues générations
  • Feedback progressif : Possibilité d'arrêter si la réponse ne convient pas

Déploiement

Firebase Functions

Pour déployer sur Firebase Functions :

bash
# Installation Firebase CLI
npm install -g firebase-tools

# Initialisation
firebase init functions

# Configuration des secrets
firebase functions:secrets:set GEMINI_API_KEY

# Déploiement
firebase deploy --only functions

Cloud Run

Pour déployer sur Google Cloud Run :

bash
# Build de l'application
ng build

# Création de l'image Docker
docker build -t restaurant-ai-app .

# Déploiement sur Cloud Run
gcloud run deploy restaurant-ai-app \
  --image restaurant-ai-app \
  --set-secrets GEMINI_API_KEY=gemini-key:latest

Bonnes pratiques

Sécurité

  • Jamais de clés API côté client : Toujours utiliser des variables d'environnement
  • Validation des entrées : Valider et nettoyer les données utilisateur
  • Rate limiting : Implémenter des limites de requêtes pour éviter les abus

Performance

  • Mise en cache : Cacher les réponses fréquentes pour réduire les coûts
  • Streaming : Utiliser le streaming pour une meilleure UX
  • Optimisation des prompts : Créer des prompts précis pour de meilleures réponses

Observabilité

  • Logs structurés : Utiliser des logs JSON pour faciliter le monitoring
  • Métriques : Suivre les temps de réponse et taux d'erreur
  • Interface Genkit : Utiliser l'interface de développement pour le debugging

Gestion des erreurs

Toujours implémenter une gestion d'erreur robuste avec des fallbacks appropriés. Les modèles IA peuvent parfois être indisponibles ou retourner des réponses inattendues.

TIP

Vous avez des exemples sur https://github.com/angular/examples/tree/main