Cheatsheets

📋 NPBlue Cheatsheets 11 sheets

Condensed, scannable reference cards — commands, flags, and syntax you can copy-paste mid-task, distilled from NPBlue's full guides.

Angular Cheatsheet

Components, dependency injection, RxJS operators, and the signals API — with the change-detection behavior that explains most “why isn’t my UI updating” questions. For the full explanations, see the Angular guides.

Component anatomy

import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-order-card',
standalone: true,
template: `
<div class="card">
<h3>{{ order.id }}</h3>
<p>{{ formattedTotal }}</p>
<button (click)="onSelect()">Select</button>
</div>
`,
})
export class OrderCardComponent {
@Input({ required: true }) order!: Order;
@Output() selected = new EventEmitter<Order>();
get formattedTotal(): string {
return `$${this.order.total.toFixed(2)}`;
}
onSelect() {
this.selected.emit(this.order);
}
}

Standalone components (default since Angular 17, stable since 14) remove the requirement to declare every component in an NgModule — a component imports what it needs directly, which is why new Angular apps increasingly skip NgModule entirely. @Input({ required: true }) (Angular 16+) turns a missing required input into a compile-time error instead of a silent undefined at runtime — worth using on any input the component can’t sensibly render without.

Data binding, all four directions

<!-- Interpolation: component → view -->
<p>{{ user.name }}</p>
<!-- Property binding: component → view (for DOM properties, not attributes) -->
<img [src]="user.avatarUrl" [class.active]="user.isOnline">
<!-- Event binding: view → component -->
<button (click)="save()">Save</button>
<!-- Two-way binding: both directions at once -->
<input [(ngModel)]="user.name">

[(ngModel)] is banana-in-a-box syntax for [ngModel]="user.name" (ngModelChange)="user.name = $event" combined — it’s sugar over property binding plus event binding, not a separate mechanism. Requires FormsModule imported, which is the single most common reason ngModel “doesn’t work” in a fresh standalone component.

Signals — the reactivity model since Angular 16

import { signal, computed, effect } from '@angular/core';
export class CartComponent {
items = signal<CartItem[]>([]);
total = computed(() =>
this.items().reduce((sum, item) => sum + item.price * item.qty, 0)
);
constructor() {
effect(() => {
console.log(`Cart total changed: ${this.total()}`);
});
}
addItem(item: CartItem) {
this.items.update(current => [...current, item]); // never mutate in place
}
}

A signal is a wrapper around a value that notifies its consumers when it changes — calling items() both reads the current value and registers that call site as a dependent, so computed() and effect() automatically know to re-run when items changes, with no manual subscription or unsubscription. .update() (transform the current value) and .set() (replace it outright) are the only ways to change a signal; mutating the array returned by items() in place won’t trigger change detection, because the signal never sees a new reference.

// Signal-based inputs and outputs (Angular 17.1+) — replaces @Input/@Output decorators
export class OrderCardComponent {
order = input.required<Order>();
selected = output<Order>();
onSelect() {
this.selected.emit(this.order());
}
}

Dependency injection

@Injectable({ providedIn: 'root' }) // singleton, tree-shakeable — the default for app-wide services
export class OrderService {
constructor(private http: HttpClient) {}
getOrders(): Observable<Order[]> {
return this.http.get<Order[]>('/api/orders');
}
}
// Modern inject() function — same DI system, usable outside constructors
export class OrderListComponent {
private orderService = inject(OrderService);
orders = signal<Order[]>([]);
}

providedIn: 'root' registers the service with the application’s root injector and makes it tree-shakeable — if nothing in the app ever imports the service, the bundler drops it entirely, which isn’t possible with the older pattern of listing providers manually in an NgModule. inject() (Angular 14+) does the identical DI resolution as constructor injection but works in contexts without a constructor — field initializers, standalone functions used as route guards, and inside effect().

Dependency injection hierarchy

Root injector

(providedIn: 'root' services live here)

Route-level injector

(providers on a route config)

Component injector

(providers array on @Component)

Child component injector

Angular resolves a dependency by walking up this tree from where it’s requested until it finds a provider — a service registered in a component’s own providers array creates a new instance scoped to that component (and its children), shadowing the root-level singleton for that subtree. This is the mechanism behind per-component state that still uses the same DI syntax as app-wide singletons — the only difference is which level of the tree the provider was registered at.

RxJS — the operators that come up constantly

import { debounceTime, distinctUntilChanged, switchMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(query => this.searchService.search(query).pipe(
catchError(() => of([])) // recover instead of killing the whole stream
))
).subscribe(results => this.results.set(results));
OperatorBehavior
debounceTime(ms)Waits for a pause in emissions before passing the latest value through
distinctUntilChanged()Skips a value if it’s identical to the previous one
switchMapCancels the previous inner observable when a new source value arrives
mergeMapRuns all inner observables concurrently, no cancellation
concatMapRuns inner observables one at a time, strictly in order
catchErrorRecovers from an error — critical inside switchMap, since an uncaught error there kills the outer subscription permanently

switchMap is the default choice for search-as-you-type and route-parameter-driven data fetching specifically because it cancels the previous in-flight request — without it, a slow response to an earlier keystroke can arrive after a faster response to a later one and overwrite the correct result with stale data. mergeMap is for independent operations, like firing off several unrelated saves that should all run in parallel. concatMap matters when execution order must be preserved, such as processing a queue of writes that must land in the order they were issued.

Change detection

@Component({
selector: 'app-item',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`,
})
export class ItemComponent {
@Input() item!: Item;
}

Default change detection checks every component in the tree on every event (click, timer, HTTP response) — correct, but wasteful in a large tree. OnPush restricts checking to when: an @Input reference changes, an event originates from within the component itself, or an async pipe emits. The gotcha OnPush catches people on: mutating an object in place (this.item.name = 'x') doesn’t change the object’s reference, so an OnPush component won’t see it — you have to replace the reference (this.item = { ...this.item, name: 'x' }) or use signals, which track this correctly by design.

Routing

export const routes: Routes = [
{ path: 'orders', component: OrderListComponent },
{ path: 'orders/:id', component: OrderDetailComponent },
{
path: 'admin',
canActivate: [authGuard],
loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),
},
];
// Reading route params reactively
export class OrderDetailComponent {
private route = inject(ActivatedRoute);
orderId = toSignal(this.route.paramMap.pipe(map(params => params.get('id'))));
}

loadChildren with a dynamic import() is Angular’s lazy-loading mechanism — the admin module’s JavaScript isn’t downloaded until a user actually navigates to /admin, which is the single biggest lever for initial bundle size on any app with distinct feature areas most users never visit. canActivate guards run before the route resolves, making them the right place for auth checks that should block navigation outright rather than rendering the component and redirecting after the fact.

Reactive forms

export class SignupComponent {
form = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(8)]),
});
get email() { return this.form.get('email')!; }
submit() {
if (this.form.invalid) {
this.form.markAllAsTouched(); // surfaces validation errors that only show after "touched"
return;
}
console.log(this.form.value);
}
}
<form [formGroup]="form" (ngSubmit)="submit()">
<input formControlName="email">
@if (email.invalid && email.touched) {
<span class="error">Enter a valid email</span>
}
<button type="submit">Sign up</button>
</form>

Reactive forms build the form model in the component class (FormGroup/FormControl), as opposed to template-driven forms which build it implicitly from ngModel directives in the template. Reactive forms are the better default for anything with conditional fields, dynamic validation, or unit-testable form logic — the whole form state is a plain object you can inspect and assert on without touching the DOM.

New control-flow syntax (Angular 17+)

@if (user(); as u) {
<p>Welcome, {{ u.name }}</p>
} @else {
<p>Please log in</p>
}
@for (item of items(); track item.id) {
<li>{{ item.name }}</li>
} @empty {
<li>No items yet</li>
}
@switch (status()) {
@case ('loading') { <spinner /> }
@case ('error') { <error-banner /> }
@default { <content /> }
}

This replaces *ngIf/*ngFor/*ngSwitch structural directives with built-in template syntax, compiled directly rather than through the directive system — Angular’s own benchmarks show meaningfully faster rendering for large lists, and track is now mandatory for @for (unlike the optional trackBy on *ngFor), which prevents the entire list from re-rendering on every change when items only need to be added or removed.

Pipes

<p>{{ order.createdAt | date:'medium' }}</p>
<p>{{ price | currency:'USD' }}</p>
<p>{{ description | slice:0:100 }}...</p>
<p>{{ user$ | async }}</p>
@Pipe({ name: 'timeAgo', standalone: true })
export class TimeAgoPipe implements PipeTransform {
transform(value: Date): string {
const seconds = Math.floor((Date.now() - value.getTime()) / 1000);
if (seconds < 60) return 'just now';
return `${Math.floor(seconds / 60)}m ago`;
}
}

The async pipe does two jobs at once: it subscribes to an Observable (or signal) and automatically unsubscribes when the component is destroyed — it’s the reason you’ll see far fewer manual .subscribe() calls in templates than in component classes. By default, a custom pipe is pure — Angular only re-runs transform() when the input reference changes, not on every change-detection cycle, which is why a pipe that reads mutable object state internally (like TimeAgoPipe reading Date.now()) needs pure: false to actually update over time, at the cost of running on every check instead.

Directives and content projection

@Directive({ selector: '[appHighlight]', standalone: true })
export class HighlightDirective {
private el = inject(ElementRef);
@HostListener('mouseenter') onEnter() {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
@HostListener('mouseleave') onLeave() {
this.el.nativeElement.style.backgroundColor = '';
}
}
card.component.html
<!-- ng-content: let a parent inject markup into a child's template -->
<div class="card">
<ng-content select="[card-header]"></ng-content>
<div class="card-body"><ng-content></ng-content></div>
</div>
<!-- usage -->
<app-card>
<h3 card-header>Order #1234</h3>
<p>Body content lands in the default slot.</p>
</app-card>

Attribute directives (like appHighlight above) change the appearance or behavior of an existing element; structural directives (@if/@for, or the older *ngIf/*ngFor) change the DOM structure itself by adding/removing elements entirely. ng-content with a select attribute routes specific projected content to specific slots — without a select attribute, it acts as a catch-all for anything that didn’t match a more specific slot, which is why the unlabeled <ng-content> in the example above must come after the labeled one to work as a fallback.

Common patterns

Unsubscribing without manual bookkeeping

export class OrderListComponent {
private destroyRef = inject(DestroyRef);
orders = signal<Order[]>([]);
constructor(private orderService: OrderService) {
this.orderService.getOrders()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(orders => this.orders.set(orders));
}
}

Forgetting to unsubscribe from a long-lived observable inside a component is a classic Angular memory leak — takeUntilDestroyed() (Angular 16+) ties the subscription’s lifetime to the component’s, removing the need for a manual ngOnDestroy + Subject teardown pattern that used to be boilerplate in nearly every component.

Converting an Observable to a signal for use in templates

readonly orders = toSignal(this.orderService.getOrders(), { initialValue: [] });

toSignal bridges RxJS (used for HTTP calls, WebSocket streams, complex event composition) into the signals model templates increasingly expect — initialValue avoids the template briefly rendering against undefined before the first emission arrives.

HTTP interceptor for auth headers and centralized error handling

export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).token();
const cloned = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
return next(cloned).pipe(
catchError(err => {
if (err.status === 401) inject(Router).navigate(['/login']);
return throwError(() => err);
})
);
};
// registered once in app.config.ts
provideHttpClient(withInterceptors([authInterceptor]));

An interceptor runs for every outgoing HttpClient request without each service needing to remember to attach the token itself — centralizing this in one place means an auth scheme change (new header name, refresh-token logic) touches one file instead of every service that calls the API.


Not covered: NgRx and other state-management libraries get their own dedicated treatment — they’re a separate architectural decision, not a language feature. For deeper coverage of any section above, see the Angular guides.