-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
637 lines (572 loc) · 21.5 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular 14 update</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet"
href="./dist/reveal.css">
<link rel="stylesheet"
href="./dist/theme/black.css"
id="theme">
<link rel="stylesheet"
href="./plugin/highlight/monokai.css">
</head>
<body>
<div class="reveal">
<div class="slides">
<section data-auto-animate
data-auto-animate-unmatched="fade">
<h3>Angular 14</h3>
<p>What's new in Angular 14?!</p>
<p style="text-align: right;">By Oskar Kumor</p>
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-standalone-component',
standalone: true,
template: `<img src="{{url}}">`,
})
export class StandaloneComponent {
@Input() url: string | undefined;
}
</code></pre>
</section>
<section>
<section data-auto-animate>
<h3>Top 10 Angular 14 Features And Updates</h3>
<ul>
<li>Standalone Components</li>
<li>Strictly Typed Forms </li>
<li>Angular CLI Auto-Completion</li>
<li>Enhanced Template Diagnostics</li>
</ul>
</section>
<section data-auto-animate>
<h3>Top 10 Angular 14 Features And Updates</h3>
<ul>
<li>Standalone Components</li>
<li>Strictly Typed Forms </li>
<li>Angular CLI Auto-Completion</li>
<li>Enhanced Template Diagnostics</li>
<li>Streamlined Page Title Accessibility</li>
<li>Latest Primitives in the Angular CDK</li>
<li>Angular DevTools is now present online</li>
<li>Optional Injectors</li>
<li>Built-in Enhancements</li>
<li>Extended Developer Diagnostics</li>
</ul>
</section>
</section>
<section>
<section data-auto-animate>
<h3 data-id="text-props">Standalone components</h3>
</section>
<section data-auto-animate>
<h3 data-id="text-props">Standalone components</h3>
<p style="font-size: 18px;">
In v14 and higher, standalone components provide a simplified way to build Angular applications.
Standalone components, directives, and pipes aim to streamline the authoring experience by
reducing
the need for NgModules. Existing applications can optionally and incrementally adopt the new
standalone style without any breaking changes.
</p>
</section>
<section data-auto-animate>
<h5>Overview</h5>
<p style="font-size:16px;">
Components, directives, and pipes can now be marked as standalone: true. Angular classes marked
as standalone do not need to be declared in an NgModule (the Angular compiler will report an
error if you try).
<br />
<br />
Standalone components specify their dependencies directly instead of getting them through
NgModules. For example, if PhotoGalleryComponent is a standalone component, it can directly
import another standalone component ImageGridComponent:
</p>
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
@Component({
standalone: true,
selector: 'photo-gallery',
imports: [ImageGridComponent],
template: `
... <image-grid [images]="imageList"></image-grid>
`,
})
export class PhotoGalleryComponent {
// component logic
}
</code></pre>
</section>
<section data-auto-animate>
<h5>Using existing NgModules in a standalone component</h5>
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
@Component({
standalone: true,
selector: 'photo-gallery',
// an existing module is imported
// directly into a standalone component
imports: [MatButtonModule],
template: `
...
<button mat-button>Next Page</button>
`,
})
export class PhotoGalleryComponent {
// logic
}
</code></pre>
</section>
<section data-auto-animate>
<h5>Using standalone components in NgModule-based applications</h5>
<p style="font-size:16px;">
You can import a standalone component (or directive, or pipe) just like you would an NgModule -
using NgModule.imports:
</p>
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
@NgModule({
declarations: [AlbumComponent],
exports: [AlbumComponent],
imports: [PhotoGalleryComponent],
})
export class AlbumModule {}
</code></pre>
</section>
<section data-auto-animate>
<h5>Lazy loading many routes at once</h5>
<p style="font-size:16px;">
The loadChildren operation now supports loading a new set of child Routes without needing to
write a lazy loaded NgModule that imports RouterModule.forChild to declare the routes. This
works when every route loaded this way is using a standalone component.
</p>
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
// In the main application:
export const ROUTES: Route[] = [
{path: 'admin', loadChildren: () =>
import('./admin/routes').then(mod => mod.ADMIN_ROUTES)},
// ...
];
// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [
{path: 'home', component: AdminHomeComponent},
{path: 'users', component: AdminUsersComponent},
// ...
];
</code></pre>
</section>
</section>
<section>
<section data-auto-animate>
<h3 data-id="text-props">Strictly Typed Forms</h3>
</section>
<section data-auto-animate>
<h3>Strictly Typed Forms</h3>
<p style="font-size: 16px;">
When updating to Angular v14, a migration will automatically replace all the form entities in
your application by their untyped versions:
</p>
<pre style="font-size: 16px;"
data-id="code">
<code data-line-numbers class="hljs" data-trim>
<script type="text/template">
export class RegisterComponent {
registerForm: FormGroup;
constructor() {
this.registerForm = new FormGroup({
login: new FormControl(null, Validators.required),
passwordGroup: new FormGroup({
password: new FormControl('', Validators.required),
confirm: new FormControl('', Validators.required)
}),
rememberMe: new FormControl(false, Validators.required)
});
}
}
</script>
</code>
</pre>
</section>
<section data-auto-animate>
<h3 >Strictly Typed Forms</h3>
<p style="font-size: 16px;">
When using the automated migration, you end up with:
</p>
<pre style="font-size: 16px;"
data-id="code">
<code data-line-numbers class="hljs" data-trim>
export class RegisterComponent {
registerForm: UntypedFormGroup;
constructor() {
this.registerForm = new UntypedFormGroup({
login: new UntypedFormControl(null, Validators.required),
passwordGroup: new UntypedFormGroup({
password: new UntypedFormControl('', Validators.required),
confirm: new UntypedFormControl('', Validators.required)
}),
rememberMe: new UntypedFormControl(false, Validators.required)
});
}
}
</code>
</pre>
</section>
<section data-auto-animate>
<h3>Strictly Typed Forms</h3>
<p style="font-size: 16px;">
Our work is to remove all the Untyped* usage, and properly type the form. Let’s start with the
code in the constructor as this is the most straightforward.
Each UntypedFormControl must be converted to FormControl<T>, with T the type of the value of the
form control. Most of the time, TypeScript can infer this information based on the initial
value given to the FormControl.
For example, passwordGroup can be converted easily:
</p>
<pre style="font-size: 16px;"
data-id="code">
<code style="width: 100%;" data-line-numbers class="hljs" data-trim>
<script type="text/template">
passwordGroup: new FormGroup({
password: new FormControl('', Validators.required), // inferred as `FormControl<string | null>`
confirm: new FormControl('', Validators.required) // inferred as `FormControl<string | null>`
}),
</script>
</code>
</pre>
</section>
<section data-auto-animate>
<h3>Strictly Typed Forms</h3>
<p style="font-size: 16px;">
When updating to Angular v14, a migration will automatically replace all the form entities in
your application by their untyped versions:
<ul style="font-size:16px;">
<li>FormControl → UntypedFormControl (which is an alias for FormControl<any>)</li>
<li>FormGroup → UntypedFormGroup (which is an alias for FormGroup<any>)</li>
<li>FormArray → UntypedFormArray (which is an alias for FormArray<any>)</li>
<li>FormBuilder → UntypedFormBuilder (which is an alias for FormBuilder<any>)</li>
</ul>
</p>
<pre data-id="code"><code style="font-size: 16px;" data-line-numbers class="hljs" data-trim>
<script type="text/template">
registerForm: FormGroup<{
login: FormControl<string | null>;
passwordGroup: FormGroup<{
password: FormControl<string>;
confirm: FormControl<string | null>;
}>;
rememberMe: FormControl<boolean | null>;
}>;
constructor() {
this.registerForm = new FormGroup({
login: new FormControl<string | null>(null, Validators.required),
passwordGroup: new FormGroup({
password: new FormControl('', Validators.required),
confirm: new FormControl('', Validators.required)
}),
rememberMe: new FormControl<boolean | null>(false, Validators.required)
});
}
</script>
</code></pre>
</section>
<section data-auto-animate>
<h3>What do we gain?</h3>
<p style="font-size:16px;">
Is this migration trouble worth it? In my opinion, definitely. The original forms API is not
playing very well with TypeScript. For example, the value of a control or group is typed as any.
So we could write this.registerForm.value.whatever and the application would happily compile.
This can be a very painful issue when refactoring an application: TypeScript would warn you
about every mistake in TS and HTML files… except in forms!
</p>
<pre data-id="code"><code data-line-numbers class="hljs" data-trim>
{
login?: string;
passwordGroup?: {
password?: string;
confirm?: string;
};
rememberMe?: boolean;
} // this.registerForm.value
</code></pre>
</section>
<section data-auto-animate>
<h3>What do we gain?</h3>
<pre data-id="code"><code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
<script type="text/template">
export class UserService {
register(login: string, password: string): Observable<void> {
// ...
}
}
const value = this.registerForm.value;
// does not compile as the `login` and `password` parameters must be strings
// and `value.login`, `value.passwordGroup`, `value.passwordGroup.password`
// can all theoretically be undefined
if (value.login && value.passwordGroup && value.passwordGroup.password) {
// TypeScript narrows the types to `string` inside the `if` block
this.userService.register(value.login, value.passwordGroup.password).subscribe();
}
</script>
</code></pre>
</section>
<section data-auto-animate>
<h3>A newcomer: FormRecord</h3>
<p style="font-size:16px;">
FormRecord is a new form entity that has been added to the API. A FormRecord is similar to a
FormGroup but the controls must all be of the same type. This can help if you use a FormGroup as
a map, to which you add and remove controls dynamically. In that case, properly typing the
FormGroup is not really easy, and that’s where FormRecord can help.
<br> <br>
It can be handy when you want to represent a list of checkboxes for example, where your user can
add or remove options. For example, our users can add and remove the language they understand
(or don’t understand) when they register:
</p>
<pre data-id="code"><code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
languages: new FormRecord({
english: new FormControl(true, { nonNullable: true }),
french: new FormControl(false, { nonNullable: true })
});
// later
this.registerForm.get('languages').addControl('spanish', new FormControl(false, { nonNullable: true }));
// error
this.registerForm.get('languages').addControl('spanish', new FormControl(0, { nonNullable: true })); // does not compile
</code></pre>
</section>
</section>
<section>
<section data-auto-animate>
<h3 data-id="text-props">Angular inject function</h3>
</section>
<section data-auto-animate>
<h3>Angular inject function</h3>
<p style="font-size:16px;">
Using Angular's inject() function, we can get a reference to a token from the injector that is
active. However, only services and factory providers might be called in.
</p>
<pre data-id="code"><code style="font-size:13px;" data-line-numbers class="hljs" data-trim>
import { inject } from '@angular/core';
import { HttpClient } from '@angular/http/client';
function getUrl(url: string) {
return inject(HttpClient).get(url);
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
standalone: true,
styleUrls: ['./app.component.scss']
})
export class AppComponent{
data$ = getUrl('<url>');
}
</code></pre>
</section>
<section data-auto-animate>
<h3>Angular inject function</h3>
<pre data-id="code"><code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
import { inject } from '@angular/core';
import { HttpClient } from '@angular/http/client';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
standalone: true,
styleUrls: ['./app.component.scss']
})
export class AppComponent{
window = inject(WINDOW);
// constructor(@inject(WINDOW) private window: Window)
}
</code></pre>
</section>
<section data-auto-animate>
<h3>Angular inject function</h3>
<p style="font-size:16px;">
Using Angular's inject() function, we can get a service reference and we don't have to pass by
super method to the abstract class services (less boilerplate code)
</p>
<pre data-id="code"><code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
import { inject } from '@angular/core';
import { HttpClient } from '@angular/http/client';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
standalone: true,
styleUrls: ['./app.component.scss']
})
export class AppComponent extends SomeBaseComponent{
//constructor(private service1: Service1){
// super(this.service1)
//}
}
@Directive()
export abstract class SomeBaseComponent{
const service1 = inject(Service1);
//constructor(protected service1: Service1)
protected someMagicFeature(){
this.service1.magic();
}
}
</code></pre>
</section>
</section>
<section>
<section data-auto-animate>
<h3 data-id="text-props">Streamlined page title accessibility</h3>
</section>
<section data-auto-animate>
<h3>Streamlined page title accessibility</h3>
<p style="font-size:16px;">
Another best practice is ensuring that your app’s page titles uniquely communicate the page’s
contents. v13.2 streamlines this with the new Route.title property in the Angular Router.
</p>
<pre data-id="code"><code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
const routes: Routes = [{
path: 'home',
component: HomeComponent
title: 'My App - Home' // <-- Page title
}, {
path: 'about',
component: AboutComponent,
title: 'My App - About Me' // <-- Page title
}];
</code></pre>
</section>
<section data-auto-animate>
<h3>Streamlined page title accessibility</h3>
<p style="font-size:16px;">
You can configure more complex title logic by providing a custom TitleStrategy.
</p>
<pre data-id="code"><code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
const routes: Routes = [{
path: 'home',
component: HomeComponent
}, {
path: 'about',
component: AboutComponent,
title: 'About Me' // <-- Page title
}];
@Injectable()
export class TemplatePageTitleStrategy extends TitleStrategy {
override updateTitle(routerState: RouterStateSnapshot) {
const title = this.buildTitle(routerState);
if (title !== undefined) {
document.title = `My App - ${title}`;
} else {
document.title = `My App - Home`;
};
};
@NgModule({
…
providers: [{provide: TitleStrategy, useClass: TemplatePageTitleStrategy}]
})
class MainModule {}
</code></pre>
</section>
</section>
<section data-auto-animate>
<h5>Extended developer diagnostics</h5>
<p style="font-size:14px">A common developer syntax error is to flip the brackets and parentheses in
two-way binding,
writing ([]) instead of [()]. Since () sorta looks like a banana and [] sorta looks like a box,
we nicknamed this the “banana in a box” error, since the banana should go in the box.</p>
<div>
<img src="./examples/assets/diagnostics.gif" />
</div>
<pre data-id="code"><code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
Warning: src/app/app.component.ts:7:25 - warning NG8101: In the two-way binding syntax
the parentheses should be inside the brackets, ex. '[(fruit)]="favoriteFruit"'.
Find more at https://angular.io/guide/two-way-binding
<app-favorite-fruit ([fruit])="favoriteFruit"></app-favorite-fruit>
</code></pre>
</section>
<section data-auto-animate>
<h5>Catch nullish coalescing on non-nullable values</h5>
<p style="font-size:14px">Extended diagnostics also raise errors for useless nullish coalescing
operators (??) in Angular templates. Specifically, this error is raised when the input is not
“nullable”, meaning its type does not include null or undefined.</p>
<pre data-id="code">
<code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
{
"angularCompilerOptions": {
"extendedDiagnostics": {
// The categories to use for specific diagnostics.
"checks": {
// Maps check name to its category.
"invalidBananaInBox": "error"
"nullishCoalescingNotNullable": "warning"
},
// The category to use for any diagnostics not listed in `checks` above.
"defaultCategory": "suppress"
},
...
},
...
}
</code>
</pre>
</section>
<section data-auto-animate>
<h5>Bind to protected component members</h5>
<p style="font-size:14px">In v14, you can now bind to protected component members directly from your
templates</p>
<pre data-id="code">
<code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
@Component({
selector: 'my-component',
template: '{{ message }}', // Now compiles!
})
export class MyComponent {
protected message: string = 'Hello world';
}
</code>
</pre>
</section>
<section data-auto-animate>
<h5>NgModel OnPush</h5>
<p style="font-size:14px">NgModel changes are reflected in the UI for OnPush components.</p>
<pre data-id="code">
<code style="font-size:15px;" data-line-numbers class="hljs" data-trim>
@Component({
selector: 'my-component',
template: '<child [ngModel]="value"></child>',
changeDetection: ChangeDetectionStrategy.OnPush
})
class MyComponent {
public value;
}
</code>
</pre>
<br />
<a style="font-size:14px">https://github.com/angular/angular/issues/10816</a>
</section>
<section data-auto-animate>
<h5>ng completion</h5>
<p style="font-size:14px">Accidentally typing ng sevre instead of ng serve happens all the time. Typos
are one of the most common reasons a command line prompt throws an error. To solve this, v14’s new
ng completion introduces real-time type-ahead autocompletion!</p>
<div>
<img src="./examples/assets/completion.gif" />
</div>
</section>
<section data-auto-animate>
<h5>Sources:</h5>
<a style="font-size:14px">https://blog.angular.io/angular-v14-is-now-available-391a6db736af</a>
<br />
<a style="font-size:14px">https://www.zenesys.com/blog/angular-14-features</a>
<br />
<a style="font-size:14px">https://angular.io/guide/standalone-components</a>
</section>
<section data-auto-animate>
<h5>Thank you!</h5>
</section>
</div>
</div>
<script src="./dist/reveal.js"></script>
<script src="./plugin/highlight/highlight.js"></script>
<script>
Reveal.initialize({
center: true,
hash: true,
plugins: [RevealHighlight]
});
</script>
</body>
</html>