Guide complet PrimeNG : installation, composants, thèmes et intégration dans vos projets Angular 17+.
Pourquoi choisir PrimeNG ?
Lorsqu'on développe une application Angular professionnelle — tableau de bord, ERP, back-office, ou CRM — le besoin de composants UI riches et fiables se fait rapidement sentir. Créer de toutes pièces une table avec pagination, tri multi-colonnes et filtres personnalisés représente plusieurs jours de travail. PrimeNG résout ce problème en fournissant plus de 90 composants prêts à l'emploi, maintenus activement par PrimeTek et utilisés par des milliers d'équipes dans le monde.
PrimeNG est bien plus qu'une simple collection de boutons et de champs de formulaire. La bibliothèque inclut des composants complexes comme p-table (DataTable avec virtual scroll, lazy loading et édition inline), p-treeTable pour les données hiérarchiques, p-fileUpload avec preview drag-and-drop, et une intégration native de Chart.js via p-chart. Tous ces composants partagent un système de thème cohérent basé sur des variables CSS, ce qui garantit une interface homogène sans effort.
Depuis la version 17, PrimeNG adopte pleinement le modèle standalone components d'Angular. Chaque composant s'importe individuellement, ce qui facilite le tree-shaking et réduit la taille du bundle final. Ce guide vous accompagne de l'installation jusqu'aux patterns avancés, avec des exemples concrets, commentés et directement applicables dans vos projets Angular 17+.
Ce que PrimeNG apporte concrètement
- Plus de 90 composants UI : tables, formulaires, menus, charts, overlays, media…
- Système de thèmes CSS basé sur des Design Tokens — dark mode inclus nativement
- Accessibilité WCAG 2.1 intégrée dans chaque composant (ARIA, navigation clavier)
- PrimeIcons : bibliothèque d'icônes incluse (300+ icônes vectorielles)
- Compatible Angular 15, 16, 17, 18, 19 avec support standalone
- PrimeFlex : utilitaires CSS optionnels (équivalent Tailwind/Bootstrap utilities)
Installation et configuration
L'installation de PrimeNG se fait en quelques commandes. La configuration differe selon que vous utilisez des modules Angular classiques ou les composants standalone (recommande pour Angular 17+).
Installation via npm
# Installer PrimeNG et PrimeIcons (icones incluses)
npm install primeng primeicons
# Optionnel : PrimeFlex (utilitaires CSS)
npm install primeflex
Ajouter le theme dans angular.json
PrimeNG propose plusieurs themes officiels. Ajoutez-en un dans le tableau styles de votre angular.json. Le theme Aura est recommande pour les nouveaux projets.
// angular.json — dans "projects > votre-app > architect > build > options"
{
"styles": [
// Theme PrimeNG — choisir un seul theme
"node_modules/primeng/resources/themes/aura-light-blue/theme.css",
// Icones PrimeNG
"node_modules/primeicons/primeicons.css",
// Votre feuille de style globale
"src/styles.css"
]
}
Configuration dans app.config.ts (Angular 17+ standalone)
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeng/themes/aura';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// Active les animations Angular (obligatoire pour PrimeNG)
provideAnimationsAsync(),
// Configure PrimeNG avec le theme Aura
providePrimeNG({
theme: {
preset: Aura,
options: {
// Active le dark mode automatique selon prefers-color-scheme
darkModeSelector: 'system',
},
},
}),
],
};
Configuration dans app.module.ts (Angular classique avec NgModule)
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// Importer uniquement les modules des composants utilises
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { TableModule } from 'primeng/table';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule, // Obligatoire pour les animations PrimeNG
ButtonModule,
InputTextModule,
TableModule,
],
bootstrap: [AppComponent],
})
export class AppModule {}
provideAnimationsAsync() dans app.config.ts.
Verification de l'installation
// src/app/app.component.ts — test minimal
import { Component } from '@angular/core';
import { ButtonModule } from 'primeng/button';
@Component({
selector: 'app-root',
standalone: true,
imports: [ButtonModule], // Import direct du composant PrimeNG
template: `
<!-- Si ce bouton s'affiche avec le style PrimeNG, l'installation est OK -->
<p-button label="PrimeNG installe avec succes !" icon="pi pi-check" />
`,
})
export class AppComponent {}
Composants essentiels
Voici les composants les plus utilises au quotidien, avec leurs configurations les plus courantes et des exemples complets commentes.
Button et Badge
Le composant p-button est le plus simple a prendre en main. Il supporte les icones PrimeIcons, les variantes de style, les etats de chargement et les tailles.
// boutons.component.ts
import { Component } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { BadgeModule } from 'primeng/badge';
@Component({
selector: 'app-boutons',
standalone: true,
imports: [ButtonModule, BadgeModule],
template: `
<!-- Bouton standard avec icone -->
<p-button label="Enregistrer" icon="pi pi-save" />
<!-- Variante outlined -->
<p-button label="Annuler" icon="pi pi-times" variant="outlined" severity="secondary" />
<!-- Bouton danger -->
<p-button label="Supprimer" icon="pi pi-trash" severity="danger" />
<!-- Etat de chargement (desactive le clic automatiquement) -->
<p-button label="Traitement..." [loading]="isLoading" (onClick)="submit()" />
<!-- Badge sur un bouton (notifications) -->
<p-button
icon="pi pi-bell"
[rounded]="true"
[text]="true"
badgeClass="p-badge-danger"
badge="3"
/>
`,
})
export class BoutonsComponent {
isLoading = false;
submit(): void {
// Active le spinner de chargement pendant 2 secondes
this.isLoading = true;
setTimeout(() => this.isLoading = false, 2000);
}
}
InputText et formulaires reactifs
PrimeNG s'integre parfaitement avec ReactiveFormsModule. Les composants acceptent formControlName et formControl directement.
// login-form.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { InputTextModule } from 'primeng/inputtext';
import { PasswordModule } from 'primeng/password';
import { ButtonModule } from 'primeng/button';
import { MessageModule } from 'primeng/message';
@Component({
selector: 'app-login-form',
standalone: true,
imports: [ReactiveFormsModule, InputTextModule, PasswordModule, ButtonModule, MessageModule],
template: `
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" class="p-fluid">
<!-- Champ email avec validation et message d'erreur -->
<div class="field mb-3">
<label for="email" class="block mb-1">Adresse email</label>
<input
pInputText
id="email"
type="email"
formControlName="email"
placeholder="votre@email.com"
[class.ng-invalid]="email?.invalid && email?.touched"
[class.ng-dirty]="email?.touched"
/>
<!-- Message d'erreur conditionnel -->
@if (email?.invalid && email?.touched) {
<p-message severity="error" text="Email invalide" />
}
</div>
<!-- Champ mot de passe avec jauge de force -->
<div class="field mb-3">
<label for="password" class="block mb-1">Mot de passe</label>
<p-password
formControlName="password"
[toggleMask]="true"
[feedback]="true"
placeholder="Min. 8 caracteres"
/>
</div>
<p-button
type="submit"
label="Se connecter"
icon="pi pi-sign-in"
[disabled]="loginForm.invalid"
/>
</form>
`,
})
export class LoginFormComponent {
loginForm: FormGroup;
constructor(private fb: FormBuilder) {
this.loginForm = this.fb.group({
// Validation email obligatoire + format valide
email: ['', [Validators.required, Validators.email]],
// Validation mot de passe obligatoire + longueur minimum
password: ['', [Validators.required, Validators.minLength(8)]],
});
}
// Getter pour acceder facilement au champ email dans le template
get email() { return this.loginForm.get('email'); }
onSubmit(): void {
if (this.loginForm.valid) {
console.log('Formulaire valide :', this.loginForm.value);
}
}
}
Table (p-table) avec pagination, tri et filtres
p-table est le composant phare de PrimeNG. Il gere la pagination, le tri multi-colonnes, les filtres globaux et par colonne, le lazy loading et l'edition inline.
// users-table.component.ts
import { Component, OnInit, signal } from '@angular/core';
import { TableModule } from 'primeng/table';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from 'primeng/button';
import { TagModule } from 'primeng/tag';
export interface User {
id: number;
nom: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
actif: boolean;
}
@Component({
selector: 'app-users-table',
standalone: true,
imports: [TableModule, InputTextModule, ButtonModule, TagModule],
template: `
<!-- Barre de recherche globale -->
<div class="flex justify-content-between mb-3">
<input
pInputText
type="text"
placeholder="Rechercher un utilisateur..."
(input)="dt.filterGlobal($any($event.target).value, 'contains')"
/>
</div>
<!-- Table avec pagination, tri et filtre global -->
<p-table
#dt
[value]="users()"
[paginator]="true"
[rows]="10"
[rowsPerPageOptions]="[5, 10, 25]"
[sortMode]="'multiple'"
[globalFilterFields]="['nom', 'email', 'role']"
[tableStyle]="{ 'min-width': '60rem' }"
>
<ng-template pTemplate="header">
<tr>
<!-- pSortableColumn active le tri sur cette colonne -->
<th pSortableColumn="nom">Nom <p-sortIcon field="nom" /></th>
<th pSortableColumn="email">Email <p-sortIcon field="email" /></th>
<th pSortableColumn="role">Role <p-sortIcon field="role" /></th>
<th>Statut</th>
<th>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-user>
<tr>
<td>{{ user.nom }}</td>
<td>{{ user.email }}</td>
<td>
<!-- p-tag colore automatiquement selon la severite -->
<p-tag
[value]="user.role"
[severity]="getRoleSeverity(user.role)"
/>
</td>
<td>
<p-tag
[value]="user.actif ? 'Actif' : 'Inactif'"
[severity]="user.actif ? 'success' : 'danger'"
/>
</td>
<td>
<p-button icon="pi pi-pencil" [text]="true" severity="info" (onClick)="edit(user)" />
<p-button icon="pi pi-trash" [text]="true" severity="danger" (onClick)="delete(user)" />
</td>
</tr>
</ng-template>
<!-- Message quand aucun resultat -->
<ng-template pTemplate="emptymessage">
<tr><td colspan="5" class="text-center p-4">Aucun utilisateur trouve.</td></tr>
</ng-template>
</p-table>
`,
})
export class UsersTableComponent implements OnInit {
// Signal Angular pour la liste des utilisateurs
users = signal<User[]>([]);
ngOnInit(): void {
// Simulation de donnees (remplacer par un appel HTTP)
this.users.set([
{ id: 1, nom: 'Alice Martin', email: 'alice@exemple.fr', role: 'admin', actif: true },
{ id: 2, nom: 'Bob Dupont', email: 'bob@exemple.fr', role: 'editor', actif: true },
{ id: 3, nom: 'Claire Leblanc', email: 'claire@exemple.fr', role: 'viewer', actif: false },
]);
}
// Retourne la couleur du badge selon le role
getRoleSeverity(role: string): 'success' | 'info' | 'warning' {
const map: Record<string, 'success' | 'info' | 'warning'> = {
admin: 'warning',
editor: 'info',
viewer: 'success',
};
return map[role] ?? 'info';
}
edit(user: User): void { console.log('Editer', user); }
delete(user: User): void { console.log('Supprimer', user); }
}
Dialog et ConfirmDialog
// user-actions.component.ts
import { Component } from '@angular/core';
import { DialogModule } from 'primeng/dialog';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { ButtonModule } from 'primeng/button';
import { ConfirmationService } from 'primeng/api';
@Component({
selector: 'app-user-actions',
standalone: true,
imports: [DialogModule, ConfirmDialogModule, ButtonModule],
// ConfirmationService doit etre fourni dans providers
providers: [ConfirmationService],
template: `
<!-- Bouton qui ouvre le dialog -->
<p-button label="Modifier l'utilisateur" icon="pi pi-pencil" (onClick)="dialogVisible = true" />
<!-- Bouton de suppression avec confirmation -->
<p-button label="Supprimer" severity="danger" (onClick)="confirmerSuppression()" />
<!-- p-dialog : fenetre modale personnalisee -->
<p-dialog
header="Modifier l'utilisateur"
[(visible)]="dialogVisible"
[modal]="true"
[style]="{ width: '450px' }"
[draggable]="false"
>
<p>Contenu du formulaire d'edition ici...</p>
<ng-template pTemplate="footer">
<p-button label="Annuler" icon="pi pi-times" variant="outlined" (onClick)="dialogVisible = false" />
<p-button label="Enregistrer" icon="pi pi-check" (onClick)="sauvegarder()" />
</ng-template>
</p-dialog>
<!-- p-confirmDialog : dialogue de confirmation global -->
<p-confirmDialog />
`,
})
export class UserActionsComponent {
dialogVisible = false;
constructor(private confirmationService: ConfirmationService) {}
confirmerSuppression(): void {
this.confirmationService.confirm({
message: 'Voulez-vous vraiment supprimer cet utilisateur ?',
header: 'Confirmation de suppression',
icon: 'pi pi-exclamation-triangle',
// Callback si l'utilisateur confirme
accept: () => console.log('Supprime !'),
// Callback si l'utilisateur annule
reject: () => console.log('Annule'),
});
}
sauvegarder(): void {
this.dialogVisible = false;
console.log('Modifications enregistrees');
}
}
Toast et MessageService
// notifications.component.ts
import { Component } from '@angular/core';
import { ToastModule } from 'primeng/toast';
import { ButtonModule } from 'primeng/button';
import { MessageService } from 'primeng/api';
@Component({
selector: 'app-notifications',
standalone: true,
imports: [ToastModule, ButtonModule],
providers: [MessageService], // MessageService injecte dans le composant
template: `
<!-- Placement du toast en haut a droite -->
<p-toast position="top-right" />
<div class="flex gap-2">
<p-button label="Succes" severity="success" (onClick)="showSuccess()" />
<p-button label="Erreur" severity="danger" (onClick)="showError()" />
<p-button label="Info" severity="info" (onClick)="showInfo()" />
</div>
`,
})
export class NotificationsComponent {
constructor(private messageService: MessageService) {}
showSuccess(): void {
this.messageService.add({
severity: 'success', // Couleur verte
summary: 'Succes',
detail: 'L\'enregistrement a bien ete effectue.',
life: 3000, // Disparait apres 3 secondes
});
}
showError(): void {
this.messageService.add({
severity: 'error',
summary: 'Erreur',
detail: 'Une erreur est survenue. Veuillez reessayer.',
sticky: true, // Ne disparait pas automatiquement
});
}
showInfo(): void {
this.messageService.add({
severity: 'info',
summary: 'Information',
detail: 'Votre session expire dans 5 minutes.',
});
}
}
Themes et personnalisation
PrimeNG 17+ introduit un nouveau systeme de themes base sur les Design Tokens. Chaque composant expose des variables CSS que vous pouvez surcharger globalement ou par composant, sans toucher au code source de la librairie.
Themes officiels disponibles
| Theme | Style visuel | Variants | Usage recommande |
|---|---|---|---|
| Aura | Moderne, arrondi, Material-inspired | light/dark × 14 couleurs | Applications SaaS, dashboards |
| Lara | Sobre, professionnel, bords droits | light/dark × 14 couleurs | ERP, back-office, entreprise |
| Nora | Minimaliste, flat design | light/dark × 14 couleurs | Applications B2B, outils internes |
| Wind | Inspire de Tailwind CSS | light/dark | Projets avec Tailwind CSS |
Personnalisation via Design Tokens
// app.config.ts — personnalisation du theme Aura
import { ApplicationConfig } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeng/themes/aura';
import { definePreset } from '@primeng/themes';
// Creer un preset personnalise base sur Aura
const MonTheme = definePreset(Aura, {
semantic: {
// Changer la couleur primaire du theme (bleu → violet)
primary: {
50: '{violet.50}',
100: '{violet.100}',
200: '{violet.200}',
300: '{violet.300}',
400: '{violet.400}',
500: '{violet.500}',
600: '{violet.600}',
700: '{violet.700}',
800: '{violet.800}',
900: '{violet.900}',
950: '{violet.950}',
},
// Couleur de fond des inputs en dark mode
colorScheme: {
dark: {
surface: {
card: '{zinc.900}',
},
},
},
},
});
export const appConfig: ApplicationConfig = {
providers: [
provideAnimationsAsync(),
providePrimeNG({
theme: {
preset: MonTheme, // Utilise notre theme personnalise
options: {
darkModeSelector: '.dark', // Active dark mode via classe CSS
},
},
}),
],
};
Basculer entre light et dark mode
// theme-toggle.component.ts
import { Component } from '@angular/core';
import { ButtonModule } from 'primeng/button';
@Component({
selector: 'app-theme-toggle',
standalone: true,
imports: [ButtonModule],
template: `
<p-button
[icon]="isDark ? 'pi pi-sun' : 'pi pi-moon'"
[label]="isDark ? 'Mode clair' : 'Mode sombre'"
variant="outlined"
(onClick)="toggleTheme()"
/>
`,
})
export class ThemeToggleComponent {
isDark = false;
toggleTheme(): void {
this.isDark = !this.isDark;
// Ajoute/retire la classe 'dark' sur le document.documentElement
// Le darkModeSelector dans app.config.ts reference cette classe
document.documentElement.classList.toggle('dark', this.isDark);
}
}
Composants avances
PrimeNG excelle dans les composants complexes que la plupart des librairies ne proposent pas. Voici les plus puissants avec leurs configurations les plus courantes.
FileUpload avec preview et drag-and-drop
// upload-documents.component.ts
import { Component } from '@angular/core';
import { FileUploadModule, FileUploadHandlerEvent } from 'primeng/fileupload';
import { ToastModule } from 'primeng/toast';
import { MessageService } from 'primeng/api';
@Component({
selector: 'app-upload-documents',
standalone: true,
imports: [FileUploadModule, ToastModule],
providers: [MessageService],
template: `
<p-toast />
<!-- Upload avec drag-and-drop, preview et validation -->
<p-fileupload
name="documents"
[url]="'/api/upload'"
[multiple]="true"
accept="image/*,.pdf,.docx"
[maxFileSize]="5000000"
[customUpload]="true"
(uploadHandler)="onUpload($event)"
(onError)="onError()"
>
<!-- Zone de drop personnalisee -->
<ng-template pTemplate="empty">
<div class="flex align-items-center justify-content-center flex-column p-4">
<i class="pi pi-cloud-upload text-5xl text-muted-color mb-3"></i>
<p>Glissez vos fichiers ici ou cliquez pour parcourir</p>
<small class="text-muted">Images, PDF, DOCX — max 5 Mo par fichier</small>
</div>
</ng-template>
</p-fileupload>
`,
})
export class UploadDocumentsComponent {
constructor(private messageService: MessageService) {}
onUpload(event: FileUploadHandlerEvent): void {
// Traitement personnalise des fichiers (appel HTTP manuel)
const formData = new FormData();
for (const file of event.files) {
formData.append('file', file, file.name);
}
// Ici : envoyer formData via HttpClient
// this.http.post('/api/upload', formData).subscribe(...)
this.messageService.add({
severity: 'success',
summary: 'Upload reussi',
detail: `${event.files.length} fichier(s) envoye(s)`,
});
}
onError(): void {
this.messageService.add({
severity: 'error',
summary: 'Erreur upload',
detail: 'Le fichier depasse la taille maximale autorisee.',
});
}
}
Chart avec Chart.js (p-chart)
// dashboard-chart.component.ts
import { Component, OnInit, signal } from '@angular/core';
import { ChartModule } from 'primeng/chart';
@Component({
selector: 'app-dashboard-chart',
standalone: true,
imports: [ChartModule],
template: `
<div class="card">
<div class="card-header">
<h5>Ventes mensuelles 2025</h5>
</div>
<div class="card-body">
<!-- p-chart gere automatiquement Chart.js en interne -->
<p-chart
type="line"
[data]="chartData()"
[options]="chartOptions"
[height]="'300px'"
/>
</div>
</div>
`,
})
export class DashboardChartComponent implements OnInit {
// Signal pour les donnees du graphique
chartData = signal<any>(null);
// Options Chart.js passees directement a PrimeNG
chartOptions = {
responsive: true,
plugins: {
legend: { position: 'top' },
title: { display: false },
},
scales: {
y: { beginAtZero: true },
},
};
ngOnInit(): void {
// Initialisation des donnees du graphique
this.chartData.set({
labels: ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aou', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [
{
label: 'Ventes 2025',
data: [12500, 18300, 15200, 22100, 19800, 25400, 28000, 24600, 21300, 27800, 31200, 35000],
borderColor: 'rgb(99, 102, 241)', // Couleur de la ligne
backgroundColor: 'rgba(99, 102, 241, 0.1)', // Remplissage sous la courbe
fill: true,
tension: 0.4, // Courbe lissee
},
{
label: 'Ventes 2024',
data: [10200, 14500, 13100, 17800, 16200, 19500, 22400, 20100, 18500, 23200, 26800, 29500],
borderColor: 'rgb(234, 179, 8)',
backgroundColor: 'rgba(234, 179, 8, 0.1)',
fill: true,
tension: 0.4,
},
],
});
}
}
DatePicker (Calendar) avec localisation francaise
// date-picker-fr.component.ts
import { Component } from '@angular/core';
import { DatePickerModule } from 'primeng/datepicker';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-date-picker-fr',
standalone: true,
imports: [DatePickerModule, FormsModule],
template: `
<div class="field">
<label for="dateNaissance">Date de naissance</label>
<!-- DatePicker avec localisation francaise et plage de selection -->
<p-datepicker
[(ngModel)]="selectedDate"
[showIcon]="true"
[showButtonBar]="true"
dateFormat="dd/mm/yy"
placeholder="JJ/MM/AAAA"
[monthNavigator]="true"
[yearNavigator]="true"
yearRange="1950:2010"
[locale]="frLocale"
/>
<!-- Affiche la date selectionnee -->
@if (selectedDate) {
<small class="text-muted mt-1">
Date selectionnee : {{ selectedDate | date:'dd/MM/yyyy' }}
</small>
}
</div>
`,
})
export class DatePickerFrComponent {
selectedDate: Date | null = null;
// Configuration de la localisation francaise
frLocale = {
firstDayOfWeek: 1, // Commence le lundi
dayNames: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
dayNamesMin: ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa'],
monthNames: ['Janvier', 'Fevrier', 'Mars', 'Avril', 'Mai', 'Juin',
'Juillet', 'Aout', 'Septembre', 'Octobre', 'Novembre', 'Decembre'],
monthNamesShort: ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Jun',
'Jul', 'Aou', 'Sep', 'Oct', 'Nov', 'Dec'],
today: "Aujourd'hui",
clear: 'Effacer',
};
}
p-chart necessite l'installation de Chart.js : npm install chart.js. PrimeNG ne l'inclut pas dans ses dependances directes pour reduire la taille du bundle.
PrimeNG avec Angular Standalone
Depuis PrimeNG 17, chaque composant est exportable en tant que module standalone. Cela signifie qu'il n'est plus necessaire de creer un SharedModule ou d'importer des modules globaux. On importe uniquement ce dont on a besoin dans chaque composant.
Pattern recommande Angular 17+
// product-list.component.ts — composant standalone avec PrimeNG
import { Component, OnInit, signal, computed } from '@angular/core';
import { FormsModule } from '@angular/forms';
// Imports PrimeNG — uniquement ce qui est utilise
import { DataViewModule } from 'primeng/dataview';
import { ButtonModule } from 'primeng/button';
import { DropdownModule } from 'primeng/dropdown';
import { TagModule } from 'primeng/tag';
import { InputTextModule } from 'primeng/inputtext';
export interface Product {
id: number;
name: string;
price: number;
category: string;
rating: number;
inventoryStatus: 'INSTOCK' | 'LOWSTOCK' | 'OUTOFSTOCK';
image: string;
}
@Component({
selector: 'app-product-list',
standalone: true,
// Import direct — pas de SharedModule necessaire
imports: [
FormsModule,
DataViewModule,
ButtonModule,
DropdownModule,
TagModule,
InputTextModule,
],
template: `
<!-- Barre d'outils : tri et basculement vue grille/liste -->
<p-dataview
[value]="filteredProducts()"
[layout]="layout"
>
<ng-template pTemplate="header">
<div class="flex justify-content-between">
<p-dropdown
[options]="sortOptions"
[(ngModel)]="sortKey"
placeholder="Trier par"
(onChange)="onSortChange($event)"
/>
<!-- Boutons pour basculer entre vue grille et liste -->
<div class="flex gap-1">
<p-button
icon="pi pi-th-large"
[text]="layout !== 'grid'"
(onClick)="layout = 'grid'"
/>
<p-button
icon="pi pi-list"
[text]="layout !== 'list'"
(onClick)="layout = 'list'"
/>
</div>
</div>
</ng-template>
<!-- Template vue grille -->
<ng-template pTemplate="grid" let-products>
<div class="grid grid-nogutter">
@for (product of products; track product.id) {
<div class="col-12 md:col-4 p-2">
<div class="p-4 border-1 surface-border border-round">
<img [src]="product.image" [alt]="product.name" class="w-full mb-3" />
<div class="font-bold mb-1">{{ product.name }}</div>
<div class="text-primary font-bold">{{ product.price | currency:'EUR' }}</div>
<p-tag
[value]="product.inventoryStatus"
[severity]="getStatusSeverity(product.inventoryStatus)"
class="mt-2"
/>
</div>
</div>
}
</div>
</ng-template>
</p-dataview>
`,
})
export class ProductListComponent implements OnInit {
products = signal<Product[]>([]);
searchTerm = signal<string>('');
sortKey = '';
layout: 'list' | 'grid' = 'grid';
// Filtre reactif base sur les signals
filteredProducts = computed(() => {
const term = this.searchTerm().toLowerCase();
if (!term) return this.products();
return this.products().filter(p =>
p.name.toLowerCase().includes(term) ||
p.category.toLowerCase().includes(term)
);
});
sortOptions = [
{ label: 'Prix croissant', value: 'price' },
{ label: 'Prix decroissant', value: '!price' },
{ label: 'Nom A-Z', value: 'name' },
];
ngOnInit(): void {
// Donnees de demonstration
this.products.set([
{ id: 1, name: 'Casque audio', price: 89.99, category: 'Audio',
rating: 4, inventoryStatus: 'INSTOCK', image: 'assets/casque.jpg' },
{ id: 2, name: 'Souris ergonomique', price: 49.99, category: 'Peripheriques',
rating: 5, inventoryStatus: 'LOWSTOCK', image: 'assets/souris.jpg' },
]);
}
getStatusSeverity(status: string): 'success' | 'warning' | 'danger' {
const map: Record<string, 'success' | 'warning' | 'danger'> = {
INSTOCK: 'success',
LOWSTOCK: 'warning',
OUTOFSTOCK: 'danger',
};
return map[status] ?? 'info';
}
onSortChange(event: any): void {
// Logique de tri — a implementer selon le besoin
console.log('Trier par :', event.value);
}
}
- Importer uniquement les modules necessaires dans le tableau
imports - Ne jamais creer un
SharedModulequi importe tous les composants PrimeNG en bloc - Combiner les composants PrimeNG avec les Signals Angular pour la reactivite
- Fournir
MessageServiceetConfirmationServicedans le composant ou au niveau de l'application - Placer
p-toastetp-confirmDialogdans le template du composant racine (app.component.html)
Performance et bonnes pratiques
PrimeNG est optimise pour la performance, mais quelques configurations cles permettent de tirer le meilleur parti de la librairie dans les applications a fort volume de donnees.
Virtual Scroll pour les grandes listes
Avec des milliers de lignes, le rendu DOM complet est prohibitif. p-table integre un virtual scroll qui ne rend que les lignes visibles dans le viewport.
// large-dataset-table.component.ts
import { Component, OnInit, signal } from '@angular/core';
import { TableModule } from 'primeng/table';
interface LogEntry {
id: number;
timestamp: string;
level: 'INFO' | 'WARN' | 'ERROR';
message: string;
}
@Component({
selector: 'app-large-dataset-table',
standalone: true,
imports: [TableModule],
template: `
<p-table
[value]="logs()"
[scrollable]="true"
scrollHeight="400px"
[virtualScroll]="true"
[virtualScrollItemSize]="46"
[rows]="50"
>
<ng-template pTemplate="header">
<tr>
<th>Timestamp</th>
<th>Niveau</th>
<th>Message</th>
</tr>
</ng-template>
<!-- Le virtual scroll ne rend que les lignes visibles -->
<ng-template pTemplate="body" let-log let-rowIndex="rowIndex">
<tr [style.height]="'46px'">
<td>{{ log.timestamp }}</td>
<td>{{ log.level }}</td>
<td>{{ log.message }}</td>
</tr>
</ng-template>
</p-table>
`,
})
export class LargeDatasetTableComponent implements OnInit {
logs = signal<LogEntry[]>([]);
ngOnInit(): void {
// Generation de 10 000 entrees de log pour la demonstration
this.logs.set(
Array.from({ length: 10000 }, (_, i) => ({
id: i + 1,
timestamp: new Date(Date.now() - i * 1000).toISOString(),
level: (['INFO', 'WARN', 'ERROR'] as const)[i % 3],
message: `Evenement systeme #${i + 1} traite avec succes`,
}))
);
}
}
Lazy Loading avec p-table et API serveur
// server-side-table.component.ts
import { Component, signal } from '@angular/core';
import { TableModule, TableLazyLoadEvent } from 'primeng/table';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-server-side-table',
standalone: true,
imports: [TableModule],
template: `
<p-table
[value]="rows()"
[lazy]="true"
[paginator]="true"
[rows]="pageSize"
[totalRecords]="totalRecords()"
[loading]="loading()"
(onLazyLoad)="loadData($event)"
>
<ng-template pTemplate="header">
<tr><th pSortableColumn="name">Nom <p-sortIcon field="name"/></th></tr>
</ng-template>
<ng-template pTemplate="body" let-row>
<tr><td>{{ row.name }}</td></tr>
</ng-template>
</p-table>
`,
})
export class ServerSideTableComponent {
rows = signal<any[]>([]);
totalRecords = signal<number>(0);
loading = signal<boolean>(false);
pageSize = 15;
constructor(private http: HttpClient) {}
loadData(event: TableLazyLoadEvent): void {
this.loading.set(true);
// Construire les parametres de l'API a partir de l'evenement PrimeNG
const params = {
first: String(event.first ?? 0), // Index de depart
rows: String(event.rows ?? this.pageSize), // Nombre de lignes
sortField: String(event.sortField ?? ''), // Champ de tri
sortOrder: String(event.sortOrder ?? '1'), // 1=ASC, -1=DESC
};
this.http.get<{ data: any[]; total: number }>('/api/users', { params })
.subscribe({
next: (res) => {
this.rows.set(res.data); // Donnees de la page courante
this.totalRecords.set(res.total); // Total pour la pagination
this.loading.set(false);
},
error: () => this.loading.set(false),
});
}
}
OnPush Change Detection
// optimized-card.component.ts
import { Component, input, ChangeDetectionStrategy } from '@angular/core';
import { CardModule } from 'primeng/card';
@Component({
selector: 'app-optimized-card',
standalone: true,
imports: [CardModule],
// OnPush : Angular ne verifie ce composant que si ses inputs changent
// Ideal pour les composants presentationnels avec des donnees immuables
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<p-card [header]="title()" [subheader]="subtitle()">
<p>{{ content() }}</p>
</p-card>
`,
})
export class OptimizedCardComponent {
title = input.required<string>();
subtitle = input<string>('');
content = input.required<string>();
}
[lazy]="true" pour les tables avec plus de 500 lignes. Utilisez [virtualScroll]="true" pour les listes deroule. Activez ChangeDetectionStrategy.OnPush sur tous les composants presentationnels.
PrimeNG vs Angular Material
Le choix entre PrimeNG et Angular Material depend du contexte du projet. Les deux librairies sont de qualite professionnelle, activement maintenues et compatibles Angular 17+. Voici une comparaison objective.
| Critere | PrimeNG | Angular Material |
|---|---|---|
| Nombre de composants | 90+ composants | ~35 composants |
| DataTable avance | Oui — pagination, tri, filtres, virtual scroll, edition inline | Basique — mat-table sans fonctionnalites avancees |
| Charts / Graphiques | Oui — integration Chart.js via p-chart | Non — necessite une librairie tierce |
| Upload de fichiers | Oui — drag & drop, preview, validation | Non |
| Systeme de themes | Design Tokens, 28+ themes pre-faits, personnalisation fine | Material Design 3, theming via SCSS variables |
| Coherence designee | Plusieurs styles (Aura, Lara, Nora) — flexibilite totale | Material Design strict — coherent mais moins flexible |
| Accessibilite | WCAG 2.1 AA sur tous les composants | WCAG 2.1 AA sur tous les composants |
| Taille du bundle | Equivalent — tree-shaking efficace avec standalone | Equivalent — tree-shaking efficace |
| Documentation | Excellente — exemples interactifs pour chaque composant | Excellente — examples et API reference complets |
| Cas d'usage ideal | ERP, CRM, dashboards, applications de gestion complexes | Applications Google-like, apps consumer, mobile-first |
Quand choisir PrimeNG ?
- Votre application affiche des tables de donnees complexes avec tri, filtres et pagination
- Vous avez besoin de composants specialises (TreeTable, FileUpload, Chart, Scheduler)
- Votre equipe veut une liberte de design sans etre lie au Material Design
- Vous developpez un back-office, ERP ou CRM
Quand choisir Angular Material ?
- Votre application doit suivre les guidelines Material Design (applications Google-like)
- Votre application est mobile-first ou grand public
- Vous privilegiez l'integration ecosysteme Google (Firebase, Google Maps)
- Votre equipe est deja familiere avec Material Design
Migration Angular Material vers PrimeNG
// Exemple de remplacement courant
// AVANT : Angular Material
// <mat-button color="primary">Sauvegarder</mat-button>
// APRES : PrimeNG — meme semantique, syntaxe differente
// <p-button label="Sauvegarder" severity="primary" />
// AVANT : Angular Material table
// <mat-table [dataSource]="dataSource">...</mat-table>
// APRES : PrimeNG table avec fonctionnalites bonus
// <p-table [value]="data" [paginator]="true" [rows]="10">...</p-table>
// AVANT : Angular Material dialog
// this.dialog.open(MonDialog, { data: {...} })
// APRES : PrimeNG dialog via [(visible)]
// dialogVisible = true dans le composant + <p-dialog [(visible)]="dialogVisible">
Conclusion
PrimeNG s'impose comme la librairie UI la plus complete de l'ecosysteme Angular. Avec plus de 90 composants, un systeme de themes flexible base sur les Design Tokens, et un support natif des composants standalone Angular 17+, elle couvre la quasi-totalite des besoins d'une application professionnelle sans devoir assembler plusieurs librairies.
L'approche recommended est d'installer PrimeNG des le debut d'un projet Angular, de configurer le theme une seule fois dans app.config.ts, puis d'importer uniquement les composants necessaires dans chaque fichier. Grace au tree-shaking, seul le code reellement utilise est inclus dans le bundle final. Les composants avances comme p-table avec virtual scroll et lazy loading serveur, ou p-chart avec Chart.js, permettent de construire des interfaces de gestion sophistiquees en quelques dizaines de lignes de code.
npm install primeng primeicons, configurez le theme Aura dans app.config.ts, et importez uniquement les composants dont vous avez besoin — le tree-shaking fait le reste.