<link rel="stylesheet" href="/assets/fonts/inter/inter.css" />
All posts

Angular avec Looker Embedded : comment supprimer le boilerplate et les interfaces TypeScript de vos tableaux de données

Dans toute application de gestion arrive le moment du tableau numéro vingt : encore une interface TypeScript, encore un DTO, encore un composant avec pagination, tri, filtres et export. C'est du code utile, mais toujours le même, et il grossit à chaque demande. Dans cet article, je montre comment, pour les données de consultation et d'analyse, Angular avec Looker Embedded permet de supprimer presque tout ce boilerplate.

Le problème du boilerplate traditionnel

L'approche classique pour afficher un dataset dans Angular demande toujours les mêmes éléments :

  • Des interfaces et des DTO qui dupliquent côté frontend la forme des données du backend, à mettre à jour à chaque modification.
  • Un composant de tableau dédié, avec Angular Material ou PrimeNG, des colonnes définies à la main et des templates pour chaque champ.
  • La pagination, le tri et les filtres à brancher et à tester, souvent aussi côté serveur.
  • La mise en forme des montants, des dates et des statuts, répétée dans chaque tableau.
  • L'export en CSV ou Excel, écrit à la main ou confié à une bibliothèque de plus.

Voici, en version réduite, un seul tableau de commandes :

// 1. The API contract, duplicated on the frontend
export interface OrderDto {
  id: number; customer_name: string; region: string;
  total_cents: number; status: 'OPEN' | 'PAID' | 'CANCELLED'; created_at: string;
}
// 2. The view model, because the API shape is not the table shape
export interface OrderRow {
  id: number; customer: string; region: string;
  total: number; status: string; createdAt: Date;
}

@Component({ /* ... MatTableModule, MatPaginatorModule, MatSortModule ... */ })
export class OrdersTableComponent implements AfterViewInit {
  readonly columns = ['id', 'customer', 'region', 'total', 'status', 'createdAt'];
  readonly dataSource = new MatTableDataSource<OrderRow>([]);
  @ViewChild(MatPaginator) paginator!: MatPaginator;
  @ViewChild(MatSort) sort!: MatSort;

  constructor(private api: OrdersApi) {
    this.api.list().subscribe(dtos => (this.dataSource.data = dtos.map(toRow)));
  }
  ngAfterViewInit(): void {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }
  applyFilter(value: string): void { this.dataSource.filter = value.trim().toLowerCase(); }
  exportCsv(): void { /* build the CSV by hand, escape quotes, trigger the download... */ }
}

// 3. Mapping and formatting, also by hand
const toRow = (d: OrderDto): OrderRow => ({
  id: d.id, customer: d.customer_name, region: d.region,
  total: d.total_cents / 100, status: d.status, createdAt: new Date(d.created_at),
});
// ...plus the HTML template with one <ng-container matColumnDef> per column

Et ce n'est que le frontend : chaque nouvelle colonne oblige à modifier le DTO, le mapping, le template, les tests, puis à livrer backend et frontend ensemble.

Le tournant : Angular avec Looker Embedded

Avec Looker, le contrat de données et la présentation vivent dans le modèle LookML : dimensions, mesures, formats, libellés et relations sont définis une seule fois, côté analytique. Le frontend n'a plus besoin de connaître la forme des données : il se contente d'afficher un contenu prêt à l'emploi, que Looker intègre dans une iframe sécurisée.

On initialise le SDK une fois pour toute l'application, en indiquant l'endpoint de notre backend qui signe les URL d'intégration :

// app.config.ts — once for the whole application
import { LookerEmbedSDK } from '@looker/embed-sdk';

export const appConfig: ApplicationConfig = {
  providers: [
    // The SDK asks our backend for a signed embed URL for each content item
    provideAppInitializer(() => LookerEmbedSDK.init('analytics.example.com', '/api/looker/embed-auth')),
  ],
};

Ensuite, un composant générique suffit, <app-looker-table>, qui ne reçoit que l'identifiant du contenu et ne sait rien des colonnes ni des types :

@Component({
  selector: 'app-looker-table',
  template: `<div #host class="looker-host"></div>`,
  styles: `.looker-host, .looker-host iframe { width: 100%; height: 100%; border: 0; }`,
})
export class LookerTableComponent {
  /** Look ID defined in Looker: the component knows nothing about the data. */
  readonly lookId = input.required<string>();
  private readonly host = viewChild.required<ElementRef<HTMLDivElement>>('host');

  constructor() {
    afterNextRender(() => {
      LookerEmbedSDK.createLookWithId(this.lookId())
        .appendTo(this.host().nativeElement)
        .build()
        .connect()
        .catch(err => console.error('Looker embed failed', err));
    });
  }
}

// Usage: the same component for any dataset
// <app-looker-table lookId="42" />   orders
// <app-looker-table lookId="57" />   invoices

Remarquez ce qui manque : aucune interface, aucun DTO, aucune définition de colonne, aucune fonction d'export. Le même composant affiche les commandes, les factures ou n'importe quel autre dataset.

Les avantages pour l'équipe

De nouvelles colonnes sans déploiement frontend

Ajouter une colonne, changer un format ou un libellé, c'est une modification du modèle LookML ou du Look : dès qu'elle est enregistrée, elle est visible dans l'application. Le frontend n'a besoin ni d'être recompilé ni d'être livré.

Export, drill-down et pagination inclus

Les téléchargements en CSV, Excel et PDF, le tri, la pagination et le drill-down sur les valeurs sont des fonctions de Looker, déjà testées et cohérentes dans chaque tableau, activées selon les permissions de l'utilisateur.

Sécurité au niveau des lignes

Avec le signed embed, notre backend génère l'URL signée avec les attributs de l'utilisateur connecté. Dans le modèle LookML, un access_filter utilise ces attributs pour filtrer chaque requête : un utilisateur ne peut pas voir les lignes d'une autre région, même en modifiant l'URL.

// NestJS: signs the embed URL with the logged-in user's attributes
@Get('looker/embed-auth')
async embedAuth(@Query('src') src: string, @CurrentUser() user: User) {
  const embed = await this.looker.ok(this.looker.create_sso_embed_url({
    target_url: `https://analytics.example.com${src}`,
    session_length: 3600,
    external_user_id: String(user.id),
    first_name: user.firstName,
    last_name: user.lastName,
    permissions: ['access_data', 'see_looks', 'download_without_limit'],
    models: ['sales'],
    user_attributes: { region: user.region },   // drives row-level security
  }));
  return { url: embed.url };
}
# LookML: every query on this explore is filtered by the user's region
explore: orders {
  access_filter: {
    field: customers.region
    user_attribute: region
  }
}

La comparaison

AspectTableau traditionnel en AngularAngular avec Looker Embedded
Code frontend pour un nouveau datasetDes dizaines ou des centaines de lignes : service, mapping, composant, templateUne ligne de template, avec un composant partagé
Interfaces TypeScript par datasetAu moins deux : DTO et modèle de vueAucune
Ajouter une colonneDéveloppement, revue et déploiement du backend et du frontendUne modification du modèle LookML, sans déploiement frontend
Pagination, tri, filtres, exportÀ implémenter et à testerInclus
Sécurité des données par utilisateurLogique dédiée dans le backendAttributs utilisateur et access filters en LookML
MaintenanceAugmente avec le nombre de tableauxConcentrée dans le modèle de données

Quand ne pas l'utiliser

  • Les tableaux éditables : la saisie, l'édition en ligne et les actions sur les lignes restent du ressort de l'application.
  • Les interfaces très personnalisées : le contenu vit dans une iframe, le style s'adapte donc avec les thèmes Looker, pas avec le CSS de l'application.
  • Le coût et l'infrastructure : Looker est une plateforme sous licence avec un modèle de données à maintenir ; c'est rentable quand les datasets sont nombreux et changent souvent.
  • La logique applicative a toujours besoin de ses types : les interfaces disparaissent pour les tableaux de consultation, pas pour les formulaires ni les règles métier.

Conclusions

  • Time-to-market : un nouveau rapport devient une ligne de template, pas un sprint.
  • Maintenabilité : la codebase Angular cesse de grossir à chaque dataset ; la complexité reste dans le modèle de données, là où elle doit être.
  • Sécurité : les règles de visibilité des données sont centralisées et appliquées à chaque requête.
  • Répartition des rôles : les développeurs frontend se concentrent sur l'expérience de l'application, les profils data sur le modèle.

Pour les données de consultation et d'analyse, Angular avec Looker Embedded déplace le travail du code répétitif vers le modèle de données. Pour approfondir l'organisation du reste de l'application, j'ai écrit sur l'architecture Angular et sur les design systems à base de composants réutilisables ; si vous envisagez une intégration similaire, écrivez-moi.

💬 Notes des lecteurs

0 notes

Écrire une note

Partagez votre avis, une suggestion ou un compliment

Dernières notes

Aucune note pour le moment. Soyez le premier à commenter !