Cours Angular · Module 13: UI et styles
Angular Material
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 animationsComposants 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. À quoi sert le sélecteur :host dans les styles d'un composant Angular ?
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).