-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathangular.html
More file actions
1686 lines (1493 loc) · 88.5 KB
/
Copy pathangular.html
File metadata and controls
1686 lines (1493 loc) · 88.5 KB
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
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Angular Cheatsheet (v17–v22)</title>
<style>
:root {
--bg: #f7f8fa;
--panel: #ffffff;
--text: #1c1e21;
--muted: #5b6270;
--border: #e2e5ea;
--accent: #dd0031;
--code-bg: #f0f2f5;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
}
header.page-header { padding: 1.6rem 1.5rem .8rem; text-align: center; }
header.page-header h1 { margin: 0 0 .3rem; font-size: 1.8rem; }
header.page-header p { color: var(--muted); margin: 0 auto; max-width: 660px; }
.sheet-links { margin-top: .6rem; font-size: .85rem; }
.sheet-links a { color: var(--accent); text-decoration: none; margin: 0 .4rem; font-weight: 600; }
.sheet-links a:hover { text-decoration: underline; }
.sheet-links a.current { color: var(--text); text-decoration: underline; }
.live-note {
max-width: 700px; margin: .8rem auto 0; padding: .6rem .9rem;
background: #fdeaea; border: 1px solid #f3b9b9; border-radius: 8px;
font-size: .8rem; color: #8f1c1c; text-align: center;
}
.search-bar {
position: sticky; top: 0; z-index: 20;
background: var(--bg);
padding: .8rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.search-bar input {
width: 100%; max-width: 700px; display: block; margin: 0 auto;
padding: .65rem .9rem; font-size: 1rem;
border: 2px solid var(--border); border-radius: 10px;
outline: none; background: var(--panel); color: var(--text);
}
.search-bar input:focus { border-color: var(--accent); }
.search-meta { text-align: center; font-size: .78rem; color: var(--muted); margin-top: .35rem; }
nav.toc {
max-width: 1100px; margin: 1rem auto; padding: .8rem 1.2rem;
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
}
nav.toc h2 { margin: 0 0 .5rem; font-size: .8rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
nav.toc ul { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; padding: 0; margin: 0 0 .7rem; }
nav.toc ul:last-child { margin-bottom: 0; }
nav.toc a {
display: inline-block; padding: .3rem .7rem; border-radius: 999px;
background: var(--code-bg); color: var(--accent); text-decoration: none; font-size: .82rem;
}
nav.toc a:hover { background: var(--accent); color: #fff; }
nav.toc li.version-link a { border: 1px solid #f3c6c6; }
main { max-width: 1100px; margin: 0 auto 3rem; padding: 0 1.2rem; }
section.category {
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
margin: 1.4rem 0; padding: 1.1rem 1.2rem;
}
section.category h2 { margin: 0 0 .3rem; font-size: 1.2rem; display: flex; align-items: center; gap: .5rem; }
section.category h2 .ver-badge {
font-size: .7rem; padding: .15rem .55rem; border-radius: 999px;
background: var(--accent); color: #fff; font-weight: 700; letter-spacing: .02em;
}
section.category .cat-intro {
margin: 0 0 1rem; padding-bottom: .8rem; color: var(--muted); font-size: .85rem;
border-bottom: 2px solid var(--accent);
}
section.category.hidden { display: none; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: .9rem; }
.cheat-card {
border: 1px solid var(--border); border-radius: 10px; padding: .8rem .9rem .9rem; background: #fdfdfe;
display: flex; flex-direction: column;
}
.cheat-card.hidden { display: none; }
.cheat-card h3 {
margin: 0 0 .35rem; font-size: .98rem; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
color: var(--accent);
}
.cheat-card .since {
font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
font-size: .62rem; padding: .1rem .45rem; border-radius: 999px;
background: var(--code-bg); color: var(--muted); font-weight: 700; letter-spacing: .02em;
}
.cheat-card p.desc { margin: 0 0 .55rem; font-size: .82rem; color: var(--text); opacity: .85; }
.micro-label {
font-size: .65rem; text-transform: uppercase; letter-spacing: .06em;
color: var(--muted); font-weight: 700; margin: 0 0 .25rem;
}
.cheat-card pre {
margin: 0 0 .6rem; background: var(--code-bg); padding: .5rem .6rem; border-radius: 6px;
font-size: .73rem; overflow-x: auto; white-space: pre-wrap; overflow-wrap: anywhere;
}
.cheat-card .preview { font-size: .82rem; margin-top: auto; }
.preview-box { border: 1px dashed var(--border); border-radius: 6px; padding: .5rem .6rem; background: #fafbfc; font-size: .8rem; }
code { font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; }
#no-results { text-align: center; color: var(--muted); padding: 2rem; display: none; }
footer.page-footer { text-align: center; color: var(--muted); font-size: .8rem; padding: 2rem 0; }
</style>
</head>
<body>
<header class="page-header">
<h1>Angular Cheatsheet</h1>
<p>A full Angular reference — components, directives, signals, forms, routing, RxJS, testing, performance, and security — plus what changed in every major release from v17 to v22. Angular needs the CLI and TypeScript compiler to actually run, so this page shows real, accurate reference code rather than live execution.</p>
<div class="sheet-links">
<a href="html.html">HTML</a>·
<a href="css.html">CSS</a>·
<a href="javascript.html">JavaScript</a>·
<a href="angular.html" class="current">Angular</a>
</div>
<div class="live-note">🅰️ Nothing on this page executes — Angular templates need the TypeScript/Angular compiler. Copy a snippet into <code>ng new</code> to try it for real. Version claims are based on official Angular release notes as of August 2026.</div>
</header>
<div class="search-bar">
<input type="text" id="search" placeholder="Search... (e.g. signal, defer, ViewChild, v20)" autocomplete="off">
<div class="search-meta" id="search-meta"></div>
</div>
<nav class="toc" aria-label="Table of contents">
<h2>Reference</h2>
<ul>
<li><a href="#components">Components</a></li>
<li><a href="#templates">Template Syntax</a></li>
<li><a href="#directives">Built-in Directives</a></li>
<li><a href="#control-flow">Control Flow</a></li>
<li><a href="#signals">Signals</a></li>
<li><a href="#communication">Component Communication</a></li>
<li><a href="#custom-directives">Custom Directives</a></li>
<li><a href="#di">DI & Services</a></li>
<li><a href="#routing">Routing</a></li>
<li><a href="#forms">Forms</a></li>
<li><a href="#http">HTTP & Data</a></li>
<li><a href="#rxjs">RxJS Essentials</a></li>
<li><a href="#lifecycle">Lifecycle & Change Detection</a></li>
<li><a href="#styling">Style Encapsulation & Host</a></li>
<li><a href="#animations">Animations</a></li>
<li><a href="#testing">Testing</a></li>
<li><a href="#performance">Performance</a></li>
<li><a href="#security">Security</a></li>
<li><a href="#cli">CLI Commands</a></li>
</ul>
<h2>Versions</h2>
<ul>
<li class="version-link"><a href="#v17">v17</a></li>
<li class="version-link"><a href="#v18">v18</a></li>
<li class="version-link"><a href="#v19">v19</a></li>
<li class="version-link"><a href="#v20">v20</a></li>
<li class="version-link"><a href="#v21">v21</a></li>
<li class="version-link"><a href="#v22">v22</a></li>
</ul>
</nav>
<main>
<!-- ===================== COMPONENTS ===================== -->
<section class="category" id="components">
<h2>Components</h2>
<p class="cat-intro">The basic building block of every Angular UI: a TypeScript class paired with a template.</p>
<div class="grid">
<div class="cheat-card" data-key="component decorator standalone selector template">
<h3>@Component <span class="since">core</span></h3>
<p class="desc">Marks a class as an Angular component and configures its selector, template, and styles. Since v17, generated components are <code>standalone</code> by default — no <code>NgModule</code> required.</p>
<div class="micro-label">Code</div>
<pre>@Component({
selector: 'app-greeting',
template: `<h1>Hello, {{ name }}!</h1>`,
styles: `h1 { color: #dd0031; }`,
})
export class GreetingComponent {
name = 'World';
}</pre>
</div>
<div class="cheat-card" data-key="standalone component imports ngmodule free">
<h3>Standalone components <span class="since">v14, default since v17</span></h3>
<p class="desc">A component declares its own dependencies via <code>imports</code> instead of relying on an <code>NgModule</code>. Since v17 this has been the CLI default for new projects and components.</p>
<div class="micro-label">Code</div>
<pre>@Component({
selector: 'app-card',
imports: [CommonModule, RouterLink],
templateUrl: './card.html',
})
export class CardComponent {}</pre>
</div>
<div class="cheat-card" data-key="bootstrapapplication main ts entry point standalone">
<h3>bootstrapApplication() <span class="since">v14, default entry since v17</span></h3>
<p class="desc">Boots a standalone app from a root component instead of an <code>AppModule</code> — the entry point generated in <code>main.ts</code> for every modern Angular project.</p>
<div class="micro-label">Code</div>
<pre>bootstrapApplication(AppComponent, {
providers: [provideRouter(routes), provideHttpClient()],
});</pre>
</div>
<div class="cheat-card" data-key="interpolation double curly braces text binding">
<h3>Interpolation {{ }} <span class="since">core</span></h3>
<p class="desc">Double curly braces embed a component expression's value as text inside the template, re-evaluated whenever the value changes.</p>
<div class="micro-label">Code</div>
<pre><p>Total: {{ price * quantity }}</p></pre>
</div>
<div class="cheat-card" data-key="property binding square brackets attribute">
<h3>Property binding [ ] <span class="since">core</span></h3>
<p class="desc">Square brackets bind a DOM property or component <code>@Input</code> to an expression, evaluated as JavaScript rather than plain text.</p>
<div class="micro-label">Code</div>
<pre><img [src]="imageUrl" [alt]="imageAlt">
<app-card [title]="cardTitle"></app-card></pre>
</div>
<div class="cheat-card" data-key="event binding parentheses click handler">
<h3>Event binding ( ) <span class="since">core</span></h3>
<p class="desc">Parentheses bind a DOM event or component <code>@Output</code> to a handler method, with <code>$event</code> giving access to the event object.</p>
<div class="micro-label">Code</div>
<pre><button (click)="save()">Save</button>
<input (input)="onInput($event)"></pre>
</div>
<div class="cheat-card" data-key="two-way binding banana box ngmodel">
<h3>Two-way binding [( )] <span class="since">core</span></h3>
<p class="desc">The "banana in a box" syntax combines property and event binding — commonly used with <code>ngModel</code> on form fields, or a component's own <code>model()</code>.</p>
<div class="micro-label">Code</div>
<pre><input [(ngModel)]="searchTerm"></pre>
</div>
<div class="cheat-card" data-key="component inputs outputs decorator classic legacy">
<h3>@Input() / @Output() (decorator form) <span class="since">core, superseded by signals since v19</span></h3>
<p class="desc">The original decorator-based way to expose a property or event across component boundaries — still fully supported, though <code>input()</code>/<code>output()</code> are now recommended for new code.</p>
<div class="micro-label">Code</div>
<pre>@Input() title = '';
@Output() saved = new EventEmitter<void>();</pre>
</div>
</div>
</section>
<!-- ===================== TEMPLATE SYNTAX ===================== -->
<section class="category" id="templates">
<h2>Template Syntax</h2>
<p class="cat-intro">Helpers for reading, referencing, and transforming data inside a template.</p>
<div class="grid">
<div class="cheat-card" data-key="pipe transform date currency uppercase">
<h3>Pipes | <span class="since">core</span></h3>
<p class="desc">Transforms a value for display without changing the underlying data — chainable, and can take arguments after a colon.</p>
<div class="micro-label">Code</div>
<pre>{{ today | date:'longDate' }}
{{ price | currency:'EUR' }}
{{ name | uppercase }}
{{ items | slice:0:3 | json }}</pre>
</div>
<div class="cheat-card" data-key="template reference variable hash local ref">
<h3>Template reference #var <span class="since">core</span></h3>
<p class="desc">A hash-prefixed local variable that refers to a DOM element or directive instance, usable elsewhere in the same template.</p>
<div class="micro-label">Code</div>
<pre><input #box>
<button (click)="log(box.value)">Log</button></pre>
</div>
<div class="cheat-card" data-key="content projection ng-content slot select">
<h3><ng-content> <span class="since">core</span></h3>
<p class="desc">Projects markup passed by a parent component into a specific slot of the child's template — Angular's version of a "children" prop. <code>select</code> filters which content goes where.</p>
<div class="micro-label">Code</div>
<pre><div class="card">
<ng-content select="[card-title]"></ng-content>
<ng-content></ng-content>
</div></pre>
</div>
<div class="cheat-card" data-key="ng-template ng-container template ref grouping no element">
<h3><ng-template> / <ng-container> <span class="since">core</span></h3>
<p class="desc"><code><ng-template></code> defines markup that isn't rendered until explicitly instantiated; <code><ng-container></code> groups elements or bindings without adding an extra DOM node.</p>
<div class="micro-label">Code</div>
<pre><ng-container *ngIf="showBoth">
<h2>Title</h2>
<p>Body</p>
</ng-container></pre>
</div>
<div class="cheat-card" data-key="ngtemplateoutlet dynamic template context">
<h3>[ngTemplateOutlet] <span class="since">core</span></h3>
<p class="desc">Renders a <code>TemplateRef</code> dynamically, optionally passing a context object the template can read via <code>let-</code> variables.</p>
<div class="micro-label">Code</div>
<pre><ng-container *ngTemplateOutlet="rowTpl; context: { $implicit: item }"></ng-container>
<ng-template #rowTpl let-item>{{ item.name }}</ng-template></pre>
</div>
<div class="cheat-card" data-key="safe navigation operator optional chaining template">
<h3>Safe navigation ?. <span class="since">core</span></h3>
<p class="desc">Reads a nested property without throwing if an intermediate value is <code>null</code>/<code>undefined</code> — the template equivalent of JavaScript's optional chaining.</p>
<div class="micro-label">Code</div>
<pre><p>{{ user?.address?.city }}</p></pre>
</div>
</div>
</section>
<!-- ===================== BUILT-IN DIRECTIVES ===================== -->
<section class="category" id="directives">
<h2>Built-in Directives</h2>
<p class="cat-intro">Directives shipped with Angular for common conditional, styling, and binding needs.</p>
<div class="grid">
<div class="cheat-card" data-key="ngif structural directive legacy replaced by if">
<h3>*ngIf <span class="since">core, superseded by @if since v17</span></h3>
<p class="desc">The original structural directive for conditional rendering, with an optional <code>else</code> template reference. Still works, but <code>@if</code> is now recommended and faster.</p>
<div class="micro-label">Code</div>
<pre><p *ngIf="isLoggedIn; else guest">Welcome back!</p>
<ng-template #guest><p>Please log in.</p></ng-template></pre>
</div>
<div class="cheat-card" data-key="ngfor structural directive legacy replaced by for trackby">
<h3>*ngFor <span class="since">core, superseded by @for since v17</span></h3>
<p class="desc">The original structural directive for looping, exposing <code>index</code>, <code>first</code>, <code>last</code>, and requiring a <code>trackBy</code> function for efficient diffing.</p>
<div class="micro-label">Code</div>
<pre><li *ngFor="let item of items; trackBy: trackById; let i = index">
{{ i }}: {{ item.name }}
</li></pre>
</div>
<div class="cheat-card" data-key="ngswitch legacy replaced by switch">
<h3>[ngSwitch] <span class="since">core, superseded by @switch since v17</span></h3>
<p class="desc">The original directive-based switch statement for templates, using <code>*ngSwitchCase</code> and <code>*ngSwitchDefault</code>.</p>
<div class="micro-label">Code</div>
<pre><div [ngSwitch]="status">
<p *ngSwitchCase="'error'">Error</p>
<p *ngSwitchDefault>Ready</p>
</div></pre>
</div>
<div class="cheat-card" data-key="ngclass class binding conditional">
<h3>[ngClass] <span class="since">core</span></h3>
<p class="desc">Toggles several CSS classes at once from an object map — for a single class, the simpler <code>[class.name]</code> binding is usually preferred.</p>
<div class="micro-label">Code</div>
<pre><div [ngClass]="{ active: isActive, disabled: !enabled }">...</div>
<div [class.active]="isActive">...</div></pre>
</div>
<div class="cheat-card" data-key="ngstyle style binding inline">
<h3>[ngStyle] <span class="since">core</span></h3>
<p class="desc">Binds several inline CSS styles at once from an object map — <code>[style.prop]</code> is the simpler single-property equivalent.</p>
<div class="micro-label">Code</div>
<pre><div [ngStyle]="{ color: isError ? 'red' : 'green', 'font-weight': 'bold' }">...</div></pre>
</div>
<div class="cheat-card" data-key="ngmodel two way binding form control directive">
<h3>ngModel <span class="since">core (FormsModule)</span></h3>
<p class="desc">Enables two-way binding on form elements when <code>FormsModule</code> is imported — the backbone of template-driven forms.</p>
<div class="micro-label">Code</div>
<pre><input [(ngModel)]="user.name" name="userName" required></pre>
</div>
<div class="cheat-card" data-key="async pipe observable promise template unwrap">
<h3>async pipe <span class="since">core</span></h3>
<p class="desc">Subscribes to an observable or promise directly in the template and auto-unsubscribes when the component is destroyed.</p>
<div class="micro-label">Code</div>
<pre><p>{{ user$ | async | json }}</p></pre>
</div>
</div>
</section>
<!-- ===================== CONTROL FLOW ===================== -->
<section class="category" id="control-flow">
<h2>Control Flow</h2>
<p class="cat-intro">The built-in <code>@if</code>/<code>@for</code>/<code>@switch</code> block syntax, introduced in developer preview in v17 and stable since v18 — it replaces the older <code>*ngIf</code>/<code>*ngFor</code> structural directives shown above.</p>
<div class="grid">
<div class="cheat-card" data-key="if else block control flow v17 v18">
<h3>@if / @else <span class="since">v17 preview, v18 stable</span></h3>
<p class="desc">Conditionally renders a block, with optional chained <code>@else if</code>/<code>@else</code> — no import needed, and it's roughly 90% faster to render than the old <code>*ngIf</code>.</p>
<div class="micro-label">Code</div>
<pre>@if (user.isLoggedIn) {
<p>Welcome back, {{ user.name }}!</p>
} @else if (isLoading) {
<p>Loading...</p>
} @else {
<a routerLink="/login">Log in</a>
}</pre>
</div>
<div class="cheat-card" data-key="if as alias narrowed value block">
<h3>@if ... as alias <span class="since">v17</span></h3>
<p class="desc">Captures the condition's (narrowed, non-null) value into a local variable for reuse inside the block — great for unwrapping a nullable expression once.</p>
<div class="micro-label">Code</div>
<pre>@if (user(); as u) {
<p>Hello, {{ u.name }}!</p>
}</pre>
</div>
<div class="cheat-card" data-key="for loop track block control flow">
<h3>@for ... track <span class="since">v17 preview, v18 stable</span></h3>
<p class="desc">Loops over a collection. <code>track</code> is mandatory — it identifies items for efficient DOM diffing, replacing <code>trackBy</code> functions from <code>*ngFor</code>.</p>
<div class="micro-label">Code</div>
<pre>@for (item of items; track item.id) {
<li>{{ item.name }}</li>
} @empty {
<li>No items yet.</li>
}</pre>
</div>
<div class="cheat-card" data-key="for loop implicit variables index first last count even odd">
<h3>@for implicit variables <span class="since">v17</span></h3>
<p class="desc">Inside a <code>@for</code> block, Angular exposes <code>$index</code>, <code>$first</code>, <code>$last</code>, <code>$even</code>, <code>$odd</code>, and <code>$count</code> without any extra syntax.</p>
<div class="micro-label">Code</div>
<pre>@for (item of items; track item.id) {
<li [class.last]="$last">{{ $index + 1 }} / {{ $count }}: {{ item.name }}</li>
}</pre>
</div>
<div class="cheat-card" data-key="switch case block control flow">
<h3>@switch / @case <span class="since">v17 preview, v18 stable</span></h3>
<p class="desc">Renders one of several blocks based on matching a value, with <code>@default</code> as the fallback — the built-in replacement for <code>[ngSwitch]</code>.</p>
<div class="micro-label">Code</div>
<pre>@switch (status) {
@case ('loading') { <p>Loading...</p> }
@case ('error') { <p>Something broke.</p> }
@default { <p>Ready.</p> }
}</pre>
</div>
<div class="cheat-card" data-key="let block local template variable">
<h3>@let <span class="since">v18.1</span></h3>
<p class="desc">Declares a local template variable, useful for naming a re-used expression or unwrapping an async/signal value once per template.</p>
<div class="micro-label">Code</div>
<pre>@let total = price * quantity;
<p>Total: {{ total }}</p></pre>
</div>
<div class="cheat-card" data-key="defer block lazy loading trigger idle viewport">
<h3>@defer <span class="since">v17 preview, v18 stable</span></h3>
<p class="desc">Lazily loads a block's component/directive/pipe dependencies, splitting them into a separate chunk. Trigger on visibility, interaction, idle time, or a timer.</p>
<div class="micro-label">Code</div>
<pre>@defer (on viewport) {
<app-heavy-chart [data]="chartData"></app-heavy-chart>
} @placeholder {
<p>Chart will load when scrolled into view.</p>
} @loading (minimum 200ms) {
<app-spinner />
} @error {
<p>Failed to load the chart.</p>
}</pre>
</div>
<div class="cheat-card" data-key="defer triggers on idle timer hover interaction when">
<h3>@defer trigger types <span class="since">v17</span></h3>
<p class="desc">Beyond <code>on viewport</code>, defer blocks can trigger <code>on idle</code>, <code>on interaction</code>, <code>on hover</code>, <code>on timer(ms)</code>, or an arbitrary boolean via <code>when</code>.</p>
<div class="micro-label">Code</div>
<pre>@defer (on interaction; prefetch on idle) {
<app-comments />
}
@defer (when isReady()) {
<app-report />
}</pre>
</div>
</div>
</section>
<!-- ===================== SIGNALS ===================== -->
<section class="category" id="signals">
<h2>Signals</h2>
<p class="cat-intro">Angular's fine-grained reactivity primitive: values that track their own dependents and update only what actually changed. Introduced in developer preview in v16, stable since v17, and now the backbone of most new APIs.</p>
<div class="grid">
<div class="cheat-card" data-key="signal writable reactive value v17">
<h3>signal() <span class="since">v16 preview, v17 stable</span></h3>
<p class="desc">Creates a writable reactive value. Read it by calling it as a function; update it with <code>.set()</code> or <code>.update()</code>.</p>
<div class="micro-label">Code</div>
<pre>count = signal(0);
increment() {
this.count.update(n => n + 1);
}</pre>
</div>
<div class="cheat-card" data-key="computed derived signal readonly">
<h3>computed() <span class="since">v16 preview, v17 stable</span></h3>
<p class="desc">Derives a read-only signal from other signals, recalculating automatically (and only) when a dependency actually changes.</p>
<div class="micro-label">Code</div>
<pre>firstName = signal('Ada');
lastName = signal('Lovelace');
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);</pre>
</div>
<div class="cheat-card" data-key="effect side effect reactive">
<h3>effect() <span class="since">v16 preview, v18 stable</span></h3>
<p class="desc">Runs a side effect automatically whenever any signal it reads changes — useful for logging, syncing to localStorage, or imperative DOM work.</p>
<div class="micro-label">Code</div>
<pre>constructor() {
effect(() => {
console.log(`count is now ${this.count()}`);
});
}</pre>
</div>
<div class="cheat-card" data-key="effect cleanup oncleanup teardown">
<h3>effect() cleanup <span class="since">v16+</span></h3>
<p class="desc">An effect callback can register a cleanup function via its <code>onCleanup</code> parameter, run before the next execution and when the effect is destroyed.</p>
<div class="micro-label">Code</div>
<pre>effect((onCleanup) => {
const id = setInterval(() => this.tick(), 1000);
onCleanup(() => clearInterval(id));
});</pre>
</div>
<div class="cheat-card" data-key="untracked read signal without dependency">
<h3>untracked() <span class="since">v18</span></h3>
<p class="desc">Reads a signal's current value inside a reactive context (like <code>effect()</code> or <code>computed()</code>) without registering it as a dependency.</p>
<div class="micro-label">Code</div>
<pre>effect(() => {
console.log(`count changed to ${this.count()}, id was ${untracked(this.id)}`);
});</pre>
</div>
<div class="cheat-card" data-key="signal equal custom equality function">
<h3>Custom equality (equal option) <span class="since">v16+</span></h3>
<p class="desc">By default signals compare with <code>===</code>; pass a custom <code>equal</code> function to control when a new value counts as "changed" — useful for objects/arrays.</p>
<div class="micro-label">Code</div>
<pre>selection = signal(new Set<string>(), {
equal: (a, b) => a.size === b.size,
});</pre>
</div>
<div class="cheat-card" data-key="input signal decorator replacement required">
<h3>input() <span class="since">v17.1 preview, v19 stable</span></h3>
<p class="desc">A signal-based replacement for the <code>@Input()</code> decorator. Add <code>.required()</code> for mandatory inputs, or pass a default as the first argument.</p>
<div class="micro-label">Code</div>
<pre>title = input.required<string>();
count = input(0);</pre>
</div>
<div class="cheat-card" data-key="output signal decorator replacement emit">
<h3>output() <span class="since">v17.3 preview, v19 stable</span></h3>
<p class="desc">A simpler, signal-friendly replacement for the <code>@Output()</code>/<code>EventEmitter</code> pair for emitting custom component events.</p>
<div class="micro-label">Code</div>
<pre>saved = output<void>();
save() {
this.saved.emit();
}</pre>
</div>
<div class="cheat-card" data-key="model signal two-way binding input output combined">
<h3>model() <span class="since">v17.2 preview, v19 stable</span></h3>
<p class="desc">Declares a signal that supports two-way binding from a parent via <code>[(...)]</code>, combining an input and its matching output in one declaration.</p>
<div class="micro-label">Code</div>
<pre>checked = model(false);
// parent template:
// <app-toggle [(checked)]="isDarkMode" /></pre>
</div>
<div class="cheat-card" data-key="linkedsignal writable derived resettable">
<h3>linkedSignal() <span class="since">v19 experimental, v20 stable</span></h3>
<p class="desc">A writable signal that recomputes from a source signal like <code>computed()</code>, but can also be overwritten manually — handy for "reset when the source changes, otherwise editable" state.</p>
<div class="micro-label">Code</div>
<pre>selectedId = linkedSignal(() => this.items()[0]?.id);</pre>
</div>
<div class="cheat-card" data-key="resource async loader promise signal">
<h3>resource() / httpResource() <span class="since">v19 experimental, v22 stable</span></h3>
<p class="desc">Signal-based async data loading: <code>resource()</code> wraps any <code>Promise</code>-returning loader, and <code>httpResource()</code> wraps an HTTP GET — both expose <code>.value()</code>, <code>.loading()</code>, and <code>.error()</code> signals.</p>
<div class="micro-label">Code</div>
<pre>userId = signal(1);
user = httpResource(() => `/api/users/${this.userId()}`);
// in the template:
// @if (user.isLoading()) { ... }
// {{ user.value()?.name }}</pre>
</div>
<div class="cheat-card" data-key="tosignal observable interop rxjs">
<h3>toSignal() / toObservable() <span class="since">v16 preview, v20 stable</span></h3>
<p class="desc">Bridges between RxJS observables and signals in either direction, for gradually mixing the two reactivity models in one codebase.</p>
<div class="micro-label">Code</div>
<pre>time$ = interval(1000);
seconds = toSignal(this.time$, { initialValue: 0 });</pre>
</div>
</div>
</section>
<!-- ===================== COMPONENT COMMUNICATION ===================== -->
<section class="category" id="communication">
<h2>Component Communication</h2>
<p class="cat-intro">Ways for a component to reach into its children or find out about content it received.</p>
<div class="grid">
<div class="cheat-card" data-key="viewchild query child component element ref">
<h3>@ViewChild / viewChild() <span class="since">core, signal form since v17.3</span></h3>
<p class="desc">Grabs a reference to a single child component, directive, or DOM element from the template — the signal-based <code>viewChild()</code> function is the modern equivalent.</p>
<div class="micro-label">Code</div>
<pre>chart = viewChild<ChartComponent>('chart');
// classic decorator form:
// @ViewChild('chart') chart!: ChartComponent;</pre>
</div>
<div class="cheat-card" data-key="viewchildren querylist multiple children signal">
<h3>@ViewChildren / viewChildren() <span class="since">core, signal form since v17.3</span></h3>
<p class="desc">Grabs references to every matching child, updating live as the DOM changes — a <code>QueryList</code> (decorator form) or a plain array signal (<code>viewChildren()</code>).</p>
<div class="micro-label">Code</div>
<pre>rows = viewChildren(RowComponent);</pre>
</div>
<div class="cheat-card" data-key="contentchild content projected child query">
<h3>@ContentChild / contentChild() <span class="since">core, signal form since v17.3</span></h3>
<p class="desc">Grabs a reference to content <em>projected into</em> the component via <code><ng-content></code>, as opposed to <code>ViewChild</code> which queries the component's own template.</p>
<div class="micro-label">Code</div>
<pre>header = contentChild(CardHeaderDirective);</pre>
</div>
<div class="cheat-card" data-key="elementref native element dom access">
<h3>ElementRef <span class="since">core</span></h3>
<p class="desc">Wraps a native DOM element for direct access — use sparingly, and prefer Renderer2 or host bindings where possible to stay platform-agnostic.</p>
<div class="micro-label">Code</div>
<pre>constructor(private el: ElementRef<HTMLElement>) {}
focus() { this.el.nativeElement.focus(); }</pre>
</div>
<div class="cheat-card" data-key="destroyref oncleanup destroy signal">
<h3>DestroyRef <span class="since">v16</span></h3>
<p class="desc">An injectable handle for registering cleanup logic tied to a component/directive's destruction — the modern alternative to implementing <code>ngOnDestroy</code> just for teardown.</p>
<div class="micro-label">Code</div>
<pre>constructor() {
inject(DestroyRef).onDestroy(() => this.subscription.unsubscribe());
}</pre>
</div>
</div>
</section>
<!-- ===================== CUSTOM DIRECTIVES ===================== -->
<section class="category" id="custom-directives">
<h2>Custom Directives</h2>
<p class="cat-intro">Writing your own reusable behavior that attaches to elements or components.</p>
<div class="grid">
<div class="cheat-card" data-key="directive decorator attribute custom behavior">
<h3>@Directive <span class="since">core</span></h3>
<p class="desc">Marks a class as an attribute directive — reusable behavior attached to an element via a selector, without its own template.</p>
<div class="micro-label">Code</div>
<pre>@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
private el = inject(ElementRef);
@HostListener('mouseenter') onEnter() {
this.el.nativeElement.style.background = 'yellow';
}
}</pre>
</div>
<div class="cheat-card" data-key="host object metadata replaces hostbinding hostlistener decorators">
<h3>host metadata object <span class="since">v14.1, favored since v15+</span></h3>
<p class="desc">Declares host bindings and listeners in the <code>@Component</code>/<code>@Directive</code> metadata object instead of <code>@HostBinding</code>/<code>@HostListener</code> decorators — one central, statically analyzable spot.</p>
<div class="micro-label">Code</div>
<pre>@Directive({
selector: '[appHighlight]',
host: {
'[class.active]': 'isActive()',
'(mouseenter)': 'onEnter()',
},
})
export class HighlightDirective {}</pre>
</div>
<div class="cheat-card" data-key="structural directive custom templateref viewcontainerref star">
<h3>Custom structural directive <span class="since">core</span></h3>
<p class="desc">A directive with a <code>*</code>-prefixed selector that controls whether/how a <code><ng-template></code> is instantiated, using <code>TemplateRef</code> and <code>ViewContainerRef</code>.</p>
<div class="micro-label">Code</div>
<pre>@Directive({ selector: '[appUnless]' })
export class UnlessDirective {
private tpl = inject(TemplateRef);
private vcr = inject(ViewContainerRef);
@Input() set appUnless(condition: boolean) {
condition ? this.vcr.clear() : this.vcr.createEmbeddedView(this.tpl);
}
}</pre>
</div>
<div class="cheat-card" data-key="directive composition api hostdirectives">
<h3>Directive composition API (hostDirectives) <span class="since">v15</span></h3>
<p class="desc">Applies one or more directives to a component from within its own metadata, composing behavior without the consumer needing to add them manually.</p>
<div class="micro-label">Code</div>
<pre>@Component({
selector: 'app-button',
hostDirectives: [{ directive: HighlightDirective, inputs: ['appHighlight'] }],
})
export class ButtonComponent {}</pre>
</div>
</div>
</section>
<!-- ===================== DI & SERVICES ===================== -->
<section class="category" id="di">
<h2>Dependency Injection & Services</h2>
<p class="cat-intro">How Angular shares logic and state across components without manual wiring.</p>
<div class="grid">
<div class="cheat-card" data-key="injectable service providedin root">
<h3>@Injectable <span class="since">core</span></h3>
<p class="desc">Marks a class as available for dependency injection. <code>providedIn: 'root'</code> registers it as a singleton for the whole app, tree-shakable if unused.</p>
<div class="micro-label">Code</div>
<pre>@Injectable({ providedIn: 'root' })
export class CartService {
items = signal<Item[]>([]);
}</pre>
</div>
<div class="cheat-card" data-key="inject function di without constructor">
<h3>inject() <span class="since">v14, favored since v17+</span></h3>
<p class="desc">Retrieves a dependency without a constructor parameter — works in field initializers, functional guards, interceptors, and factory functions.</p>
<div class="micro-label">Code</div>
<pre>export class CartComponent {
private cart = inject(CartService);
}</pre>
</div>
<div class="cheat-card" data-key="providers component level di token">
<h3>providers array <span class="since">core</span></h3>
<p class="desc">Registers services scoped to a specific component (and its children) instead of the whole app, via the component's <code>providers</code> array.</p>
<div class="micro-label">Code</div>
<pre>@Component({
selector: 'app-widget',
providers: [WidgetStateService],
})
export class WidgetComponent {}</pre>
</div>
<div class="cheat-card" data-key="injectiontoken custom token non-class dependency">
<h3>InjectionToken <span class="since">core</span></h3>
<p class="desc">Creates a unique, type-safe DI token for values that aren't classes — configuration objects, primitives, or interfaces.</p>
<div class="micro-label">Code</div>
<pre>export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');
providers: [{ provide: API_BASE_URL, useValue: 'https://api.example.com' }]</pre>
</div>
<div class="cheat-card" data-key="multi provider array true extensible list">
<h3>Multi-providers <span class="since">core</span></h3>
<p class="desc">Registers several values under the same token as an array, using <code>multi: true</code> — how Angular itself implements things like <code>HTTP_INTERCEPTORS</code>.</p>
<div class="micro-label">Code</div>
<pre>providers: [
{ provide: VALIDATORS, useValue: mustBePositive, multi: true },
{ provide: VALIDATORS, useValue: mustBeEven, multi: true },
]</pre>
</div>
<div class="cheat-card" data-key="inject optional self skipself host modifiers">
<h3>inject() modifiers <span class="since">v14+</span></h3>
<p class="desc">Fine-tunes how a dependency is resolved: <code>optional</code> avoids an error if it's missing, <code>self</code>/<code>skipSelf</code> control which injector level to search.</p>
<div class="micro-label">Code</div>
<pre>logger = inject(LoggerService, { optional: true });
parentForm = inject(ControlContainer, { skipSelf: true });</pre>
</div>
</div>
</section>
<!-- ===================== ROUTING ===================== -->
<section class="category" id="routing">
<h2>Routing</h2>
<p class="cat-intro">Angular's built-in client-side router for multi-view, single-page navigation.</p>
<div class="grid">
<div class="cheat-card" data-key="routes config standalone loadcomponent lazy">
<h3>Route config + lazy loading <span class="since">core, standalone since v17</span></h3>
<p class="desc">Routes map URL paths to standalone components. <code>loadComponent</code> lazy-loads the target as a separate chunk, only fetched when that route is visited.</p>
<div class="micro-label">Code</div>
<pre>export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'users/:id', loadComponent: () =>
import('./user-detail.component').then(m => m.UserDetailComponent) },
];</pre>
</div>
<div class="cheat-card" data-key="router outlet routerlink template">
<h3>RouterOutlet / routerLink <span class="since">core</span></h3>
<p class="desc"><code><router-outlet></code> marks where the matched route's component renders; <code>routerLink</code> creates navigable links without a full page reload.</p>
<div class="micro-label">Code</div>
<pre><nav><a routerLink="/users" routerLinkActive="active">Users</a></nav>
<router-outlet></router-outlet></pre>
</div>
<div class="cheat-card" data-key="nested child routes layout outlet">
<h3>Nested (child) routes <span class="since">core</span></h3>
<p class="desc">A route's <code>children</code> array defines sub-routes rendered inside that route's own <code><router-outlet></code> — the basis for shared layouts.</p>
<div class="micro-label">Code</div>
<pre>{
path: 'settings',
component: SettingsLayoutComponent,
children: [
{ path: 'profile', component: ProfileComponent },
{ path: 'security', component: SecurityComponent },
],
}</pre>
</div>
<div class="cheat-card" data-key="wildcard redirect route not found catch all">
<h3>Wildcard & redirect routes <span class="since">core</span></h3>
<p class="desc"><code>**</code> matches any URL that didn't match an earlier route — the standard way to build a "page not found" fallback. <code>redirectTo</code> forwards to another path.</p>
<div class="micro-label">Code</div>
<pre>{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: '**', component: NotFoundComponent },</pre>
</div>
<div class="cheat-card" data-key="functional guard canactivate function">
<h3>Functional guards <span class="since">v14.2, default pattern since v15+</span></h3>
<p class="desc">Route guards written as plain functions using <code>inject()</code>, replacing the older class-based <code>CanActivate</code> interface.</p>
<div class="micro-label">Code</div>
<pre>export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
return auth.isLoggedIn() || inject(Router).parseUrl('/login');
};</pre>
</div>
<div class="cheat-card" data-key="functional resolver preload data before navigation">
<h3>Functional resolvers <span class="since">v14.2</span></h3>
<p class="desc">Pre-fetches data before a route activates, so the component starts with it already available via <code>ActivatedRoute.data</code> or a bound input.</p>
<div class="micro-label">Code</div>
<pre>export const userResolver: ResolveFn<User> = (route) =>
inject(UserService).getUser(route.paramMap.get('id')!);
{ path: 'users/:id', component: UserDetailComponent, resolve: { user: userResolver } }</pre>
</div>
<div class="cheat-card" data-key="input binding route parameter component">
<h3>Route parameters as inputs <span class="since">v16, refined through v18</span></h3>
<p class="desc">With <code>withComponentInputBinding()</code>, route/query/matrix params (and resolved data) bind directly to a component's <code>input()</code> fields — no manual <code>ActivatedRoute</code> subscription needed.</p>
<div class="micro-label">Code</div>
<pre>provideRouter(routes, withComponentInputBinding());
// route: { path: 'users/:id', component: UserDetailComponent }
export class UserDetailComponent {
id = input<string>(); // bound automatically from :id
}</pre>
</div>
<div class="cheat-card" data-key="preloading strategy lazy chunks preloadallmodules">
<h3>Preloading strategy <span class="since">core</span></h3>
<p class="desc">Loads lazy route chunks in the background after the initial render, so navigation later feels instant while still keeping the first load small.</p>
<div class="micro-label">Code</div>
<pre>provideRouter(routes, withPreloading(PreloadAllModules));</pre>
</div>
</div>
</section>
<!-- ===================== FORMS ===================== -->
<section class="category" id="forms">
<h2>Forms</h2>
<p class="cat-intro">Two established approaches to forms, plus the new signal-based option landing in v21.</p>
<div class="grid">
<div class="cheat-card" data-key="reactive forms formgroup formcontrol validators">
<h3>Reactive Forms <span class="since">core</span></h3>
<p class="desc">Forms defined and validated in the component class via <code>FormGroup</code>/<code>FormControl</code> — explicit, testable, and the recommended approach for anything non-trivial.</p>
<div class="micro-label">Code</div>
<pre>form = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
});</pre>
</div>
<div class="cheat-card" data-key="formbuilder shorthand group array control">
<h3>FormBuilder <span class="since">core</span></h3>
<p class="desc">A convenience service (<code>fb</code>) for constructing <code>FormGroup</code>/<code>FormControl</code>/<code>FormArray</code> trees with less boilerplate than calling their constructors directly.</p>
<div class="micro-label">Code</div>
<pre>private fb = inject(FormBuilder);
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', Validators.minLength(8)],
});</pre>
</div>
<div class="cheat-card" data-key="formarray dynamic list of controls repeat">
<h3>FormArray <span class="since">core</span></h3>
<p class="desc">A resizable list of form controls or groups, for dynamic form sections like "add another phone number".</p>
<div class="micro-label">Code</div>
<pre>phones = this.fb.array([this.fb.control('')]);
addPhone() { this.phones.push(this.fb.control('')); }</pre>
</div>
<div class="cheat-card" data-key="custom validator function sync error object">
<h3>Custom validator <span class="since">core</span></h3>
<p class="desc">A plain function receiving a control and returning an error object (or <code>null</code> if valid) — reusable across any <code>FormControl</code>.</p>
<div class="micro-label">Code</div>
<pre>function mustBeEven(control: AbstractControl): ValidationErrors | null {
return control.value % 2 === 0 ? null : { mustBeEven: true };
}</pre>
</div>
<div class="cheat-card" data-key="cross field validator group level password match">
<h3>Cross-field validator <span class="since">core</span></h3>
<p class="desc">A validator attached to a <code>FormGroup</code> itself (rather than one control), used for rules that compare multiple fields — like confirming a password match.</p>
<div class="micro-label">Code</div>
<pre>this.fb.group({ password: [''], confirm: [''] }, {
validators: (group) =>
group.get('password')!.value === group.get('confirm')!.value ? null : { mismatch: true },
});</pre>
</div>
<div class="cheat-card" data-key="template driven forms ngmodel directive">
<h3>Template-driven forms <span class="since">core</span></h3>
<p class="desc">Forms wired up mostly in the template via <code>ngModel</code> and directives — quicker to set up for simple forms, less explicit than reactive forms.</p>
<div class="micro-label">Code</div>
<pre><form #f="ngForm" (ngSubmit)="save(f.value)">
<input name="email" ngModel required email>
</form></pre>
</div>
<div class="cheat-card" data-key="signal forms experimental new api">
<h3>Signal Forms <span class="since">v21 experimental</span></h3>
<p class="desc">A new, still-experimental forms API built entirely on signals, aiming for the ergonomics of template-driven forms with the type-safety of reactive forms.</p>
<div class="micro-label">Code</div>
<pre>// experimental API — syntax may still change
const emailField = form(signal(''), (f) => {
required(f);
email(f);
});</pre>
</div>
</div>
</section>
<!-- ===================== HTTP & DATA ===================== -->
<section class="category" id="http">
<h2>HTTP & Data</h2>
<p class="cat-intro">Talking to a backend, the RxJS way and the newer signal-based way.</p>
<div class="grid">
<div class="cheat-card" data-key="httpclient providehttpclient functional interceptors">
<h3>HttpClient <span class="since">core, functional interceptors since v15</span></h3>
<p class="desc">Makes HTTP requests and returns RxJS observables. <code>provideHttpClient()</code> configures it in the app config; interceptors are now plain functions too.</p>
<div class="micro-label">Code</div>
<pre>export class UserService {
private http = inject(HttpClient);
getUser(id: string) {
return this.http.get<User>(`/api/users/${id}`);
}
}</pre>
</div>
<div class="cheat-card" data-key="functional interceptor httpinterceptorfn chain">
<h3>Functional interceptors <span class="since">v15</span></h3>
<p class="desc">Intercepts outgoing requests/responses as a plain function chain, replacing the older class-based <code>HttpInterceptor</code> service pattern.</p>
<div class="micro-label">Code</div>
<pre>export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).token();
return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};
provideHttpClient(withInterceptors([authInterceptor]));</pre>
</div>
<div class="cheat-card" data-key="httpparams httpheaders query string">
<h3>HttpParams / HttpHeaders <span class="since">core</span></h3>
<p class="desc">Immutable builders for query-string parameters and request headers — chainable, and safer than hand-building a URL string.</p>
<div class="micro-label">Code</div>
<pre>const params = new HttpParams().set('page', 1).set('size', 20);
this.http.get('/api/items', { params });</pre>
</div>
<div class="cheat-card" data-key="catcherror retry rxjs error handling http">
<h3>Error handling (catchError / retry) <span class="since">core (RxJS)</span></h3>
<p class="desc">Standard RxJS operators for recovering from or retrying a failed HTTP call before it reaches the subscriber.</p>
<div class="micro-label">Code</div>
<pre>this.http.get('/api/items').pipe(
retry(2),
catchError((err) => { console.error(err); return of([]); }),
);</pre>
</div>
<div class="cheat-card" data-key="httpresource signal based fetching get">
<h3>httpResource() <span class="since">v19 experimental, v22 stable</span></h3>
<p class="desc">A signal-first alternative to manually subscribing: re-fetches automatically whenever a signal used inside the URL/params changes.</p>
<div class="micro-label">Code</div>
<pre>query = signal('angular');
results = httpResource<SearchResult[]>(
() => `/api/search?q=${this.query()}`