Cours Angular · Module 13: UI et styles

Angular Material

2 min de lecture
Dans cette leçon3

Angular Material est la bibliothèque officielle de composants UI - comme un service à thé de cérémonie.

Installation

1ng add @angular/material
2
3# Choose:
4# - Pre-built theme (e.g., Indigo/Pink)
5# - Global typography styles
6# - BrowserAnimationsModule animations

Composants de base

1import { Component } from '@angular/core';
2import { MatButtonModule } from '@angular/material/button';
3import { MatCardModule } from '@angular/material/card';
4import { MatIconModule } from '@angular/material/icon';
5import { MatInputModule } from '@angular/material/input';
6import { MatFormFieldModule } from '@angular/material/form-field';
7import { MatToolbarModule } from '@angular/material/toolbar';
8import { MatMenuModule } from '@angular/material/menu';
9
10@Component({
11  selector: 'app-dojo-dashboard',
12  standalone: true,
13  imports: [
14    MatButtonModule,
15    MatCardModule,
16    MatIconModule,
17    MatInputModule,
18    MatFormFieldModule,
19    MatToolbarModule,
20    MatMenuModule
21  ],
22  template: `
23    <!-- Toolbar -->
24    <mat-toolbar color="primary">
25      <span>Dojo Dashboard</span>
26      <span class="spacer"></span>
27      <button mat-icon-button [matMenuTriggerFor]="menu">
28        <mat-icon>more_vert</mat-icon>
29      </button>
30      <mat-menu #menu="matMenu">
31        <button mat-menu-item>Settings</button>
32        <button mat-menu-item>Logout</button>
33      </mat-menu>
34    </mat-toolbar>
35
36    <!-- Cards -->
37    <mat-card>
38      <mat-card-header>
39        <mat-card-title>Samurai Profile</mat-card-title>
40        <mat-card-subtitle>Clan: Tokugawa</mat-card-subtitle>
41      </mat-card-header>
42      <mat-card-content>
43        <mat-form-field appearance="outline">
44          <mat-label>Name</mat-label>
45          <input matInput placeholder="Enter name">
46          <mat-icon matPrefix>person</mat-icon>
47        </mat-form-field>
48      </mat-card-content>
49      <mat-card-actions align="end">
50        <button mat-button>Cancel</button>
51        <button mat-raised-button color="primary">Save</button>
52      </mat-card-actions>
53    </mat-card>
54  `,
55  styles: [`
56    .spacer {
57      flex: 1 1 auto;
58    }
59    mat-card {
60      max-width: 400px;
61      margin: 2rem auto;
62    }
63  `]
64})
65export class DojoDashboardComponent {}

Thème Material personnalisé

1// custom-theme.scss
2@use '@angular/material' as mat;
3
4// Define colors
5$samurai-primary: mat.m2-define-palette((
6  50: #fce4ec,
7  100: #f8bbd9,
8  500: #c41e3a,  // main
9  700: #ad1457,
10  contrast: (
11    50: rgba(black, 0.87),
12    500: white,
13  )
14));
15
16$samurai-accent: mat.m2-define-palette(mat.$m2-amber-palette);
17
18// Create theme
19$samurai-theme: mat.m2-define-light-theme((
20  color: (
21    primary: $samurai-primary,
22    accent: $samurai-accent,
23  ),
24  typography: mat.m2-define-typography-config(),
25  density: 0,
26));
27
28// Apply theme
29@include mat.all-component-themes($samurai-theme);
Code de cette leçon : src/app/app.component.ts
1import { Component, signal, ViewEncapsulation } from '@angular/core';
2import { CommonModule } from '@angular/common';
3
4@Component({
5  selector: 'app-root',
6  standalone: true,
7  imports: [CommonModule],
8  encapsulation: ViewEncapsulation.Emulated, // Encapsulation par défaut
9  template: `
10    <div class="styling-guide">
11      <h1>Le style dans Angular</h1>
12
13      <section>
14        <h2>ViewEncapsulation</h2>
15        <div class="encapsulation-types">
16          <div class="type emulated">
17            <h3>Emulated (par défaut)</h3>
18            <p>Angular ajoute des attributs uniques aux sélecteurs CSS</p>
19            <code>[_ngcontent-xxx]</code>
20          </div>
21          <div class="type shadow">
22            <h3>ShadowDom</h3>
23            <p>Utilise le vrai Shadow DOM</p>
24            <code>encapsulation: ViewEncapsulation.ShadowDom</code>
25          </div>
26          <div class="type none">
27            <h3>None</h3>
28            <p>Styles globaux - pas d'encapsulation</p>
29            <code>encapsulation: ViewEncapsulation.None</code>
30          </div>
31        </div>
32      </section>
33
34      <section>
35        <h2>Sélecteurs spéciaux</h2>
36        <div class="selectors-demo">
37          <div class="selector">
38            <h3>:host</h3>
39            <code>:host { display: block; }</code>
40            <p>Stylise l'élément hôte du composant</p>
41          </div>
42          <div class="selector">
43            <h3>:host-context()</h3>
44            <code>:host-context(.dark-theme) { ... }</code>
45            <p>Styles dépendant du contexte parent</p>
46          </div>
47          <div class="selector">
48            <h3>::ng-deep</h3>
49            <code>::ng-deep .child { ... }</code>
50            <p>Traverse l'encapsulation (déprécié)</p>
51          </div>
52        </div>
53      </section>
54
55      <section>
56        <h2>Variables CSS (Custom Properties)</h2>
57        <div class="css-vars-demo">
58          <div class="controls">
59            <label>
60              Couleur principale :
61              <input type="color" [value]="primaryColor()" (input)="onColorChange($event)">
62            </label>
63            <label>
64              Rayon de bordure :
65              <input type="range" min="0" max="30" [value]="borderRadius()" (input)="onRadiusChange($event)">
66              {{ borderRadius() }}px
67            </label>
68          </div>
69
70          <div class="preview-box"
71               [style.--primary-color]="primaryColor()"
72               [style.--border-radius]="borderRadius() + 'px'">
73            <h3>Aperçu</h3>
74            <button>Bouton</button>
75            <div class="card">Carte</div>
76          </div>
77        </div>
78      </section>
79
80      <section>
81        <h2>Liaison de styles</h2>
82        <pre><code>{{ stylesCode }}</code></pre>
83      </section>
84
85      <section>
86        <h2>Classes utilitaires façon Tailwind</h2>
87        <div class="utilities-demo">
88          <div class="utility-box p-4 m-2 rounded bg-primary">
89            p-4 m-2 rounded bg-primary
90          </div>
91          <div class="utility-box p-2 m-4 rounded-lg bg-secondary">
92            p-2 m-4 rounded-lg bg-secondary
93          </div>
94          <div class="utility-box p-6 m-1 rounded-full bg-accent">
95            p-6 m-1 rounded-full bg-accent
96          </div>
97        </div>
98      </section>
99    </div>
100  `,
101  styles: [`
102    :host {
103      display: block;
104    }
105
106    .styling-guide {
107      font-family: 'Segoe UI', sans-serif;
108      padding: 20px;
109      background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
110      min-height: 100vh;
111      color: #ecf0f1;
112    }
113
114    h1 {
115      text-align: center;
116      color: #3498db;
117    }
118
119    h2 {
120      color: #3498db;
121      border-bottom: 2px solid #3498db;
122      padding-bottom: 10px;
123    }
124
125    section {
126      background: rgba(255,255,255,0.05);
127      border-radius: 10px;
128      padding: 20px;
129      margin: 20px 0;
130    }
131
132    .encapsulation-types {
133      display: grid;
134      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
135      gap: 15px;
136    }
137
138    .type {
139      padding: 15px;
140      border-radius: 8px;
141    }
142
143    .type.emulated {
144      background: rgba(46, 204, 113, 0.2);
145      border: 2px solid #2ecc71;
146    }
147
148    .type.shadow {
149      background: rgba(52, 152, 219, 0.2);
150      border: 2px solid #3498db;
151    }
152
153    .type.none {
154      background: rgba(231, 76, 60, 0.2);
155      border: 2px solid #e74c3c;
156    }
157
158    .type h3 {
159      margin-top: 0;
160    }
161
162    .type code {
163      display: block;
164      background: rgba(0,0,0,0.3);
165      padding: 8px;
166      border-radius: 4px;
167      font-size: 0.85em;
168    }
169
170    .selectors-demo {
171      display: grid;
172      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
173      gap: 15px;
174    }
175
176    .selector {
177      background: rgba(0,0,0,0.3);
178      padding: 15px;
179      border-radius: 8px;
180    }
181
182    .selector h3 {
183      margin-top: 0;
184      color: #3498db;
185    }
186
187    .selector code {
188      display: block;
189      background: rgba(52, 152, 219, 0.2);
190      padding: 8px;
191      border-radius: 4px;
192      margin-bottom: 10px;
193    }
194
195    .css-vars-demo .controls {
196      display: flex;
197      gap: 20px;
198      flex-wrap: wrap;
199      margin-bottom: 20px;
200    }
201
202    .css-vars-demo label {
203      display: flex;
204      align-items: center;
205      gap: 10px;
206    }
207
208    .preview-box {
209      --primary-color: #3498db;
210      --border-radius: 8px;
211
212      background: rgba(0,0,0,0.3);
213      padding: 20px;
214      border-radius: 10px;
215    }
216
217    .preview-box h3 {
218      color: var(--primary-color);
219    }
220
221    .preview-box button {
222      background: var(--primary-color);
223      color: white;
224      border: none;
225      padding: 10px 20px;
226      border-radius: var(--border-radius);
227      cursor: pointer;
228      margin-right: 10px;
229    }
230
231    .preview-box .card {
232      display: inline-block;
233      background: var(--primary-color);
234      color: white;
235      padding: 15px 25px;
236      border-radius: var(--border-radius);
237    }
238
239    pre {
240      background: #1a252f;
241      padding: 15px;
242      border-radius: 8px;
243      overflow-x: auto;
244      border-left: 4px solid #3498db;
245    }
246
247    code {
248      color: #ecf0f1;
249      font-family: 'Fira Code', monospace;
250      font-size: 0.85em;
251    }
252
253    /* Classes utilitaires */
254    .utilities-demo {
255      display: flex;
256      flex-wrap: wrap;
257      gap: 10px;
258    }
259
260    .utility-box {
261      color: white;
262      font-size: 0.85em;
263    }
264
265    .p-2 { padding: 8px; }
266    .p-4 { padding: 16px; }
267    .p-6 { padding: 24px; }
268    .m-1 { margin: 4px; }
269    .m-2 { margin: 8px; }
270    .m-4 { margin: 16px; }
271    .rounded { border-radius: 4px; }
272    .rounded-lg { border-radius: 8px; }
273    .rounded-full { border-radius: 50px; }
274    .bg-primary { background: #3498db; }
275    .bg-secondary { background: #2ecc71; }
276    .bg-accent { background: #9b59b6; }
277  `]
278})
279export class AppComponent {
280  primaryColor = signal('#3498db');
281  borderRadius = signal(8);
282
283  stylesCode = `// Liaison de style unique
284<div [style.color]="textColor">Texte</div>
285<div [style.font-size.px]="fontSize">Texte</div>
286
287// Liaison de plusieurs styles
288<div [ngStyle]="{
289  'color': textColor,
290  'font-size.px': fontSize,
291  'background': isActive ? 'green' : 'red'
292}">Texte</div>
293
294// Liaison de classe
295<div [class.active]="isActive">...</div>
296<div [class]="dynamicClass">...</div>
297
298// NgClass pour plusieurs classes
299<div [ngClass]="{
300  'active': isActive,
301  'disabled': isDisabled,
302  'highlight': isHighlighted
303}">...</div>
304
305// Variables CSS dans Angular
306<div [style.--primary-color]="primaryColor">
307  <button>Utilise --primary-color</button>
308</div>`;
309
310  onColorChange(event: Event): void {
311    const input = event.target as HTMLInputElement;
312    this.primaryColor.set(input.value);
313  }
314
315  onRadiusChange(event: Event): void {
316    const input = event.target as HTMLInputElement;
317    this.borderRadius.set(+input.value);
318  }
319}

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. À quoi sert le sélecteur :host dans les styles d'un composant Angular ?

  2. 2. Quelle commande ajoute Angular Material à un projet (en configurant le thème et les animations) ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri horizontal

    Ordonne sur une seule ligne l'import du module de boutons Angular Material.

  • Éditeur de code

    Complète le formulaire en utilisant Angular Material.

  • Tri vertical

    Ordonne les éléments de la carte Material selon leur ordre d'apparition dans le template (de haut en bas).

Articles utiles