# Cheat Sheet

Course: [Angular](https://www.devtutorials.ai/learn/angular) · Section: References
Source: https://www.devtutorials.ai/learn/angular/references/cheat-sheet

> A short reference of the whole course: structure, components, templates, binding, directives, pipes, forms, services, DI, routing, RxJS, HTTP, signals, state, render modes, security, errors and performance.

## Angular Application Structure

| File or folder | Purpose |
|---|---|
| `src/main.ts` | Starts the app: `bootstrapApplication(App, appConfig)` |
| `src/index.html` | The page that contains `<app-root>` |
| `src/styles.css` | Global styles |
| `src/app/app.ts` | The root component |
| `src/app/app.config.ts` | App-wide providers: router, `HttpClient`, error handler |
| `src/app/app.routes.ts` | The list of routes |
| `public/` | Static files, such as images and `favicon.ico` |
| `angular.json` | Build settings, budgets, environments |

| Command | What it does |
|---|---|
| `ng new my-app` | Creates a project |
| `ng serve` | Starts the development server on `http://localhost:4200` |
| `ng build` | Builds the app for production into `dist/` |
| `ng generate component name` | Creates a component (`ng g c name`) |
| `ng generate service name` | Creates a service (`ng g s name`) |
| `ng generate environments` | Creates the environment files |
| `ng add @angular/ssr` | Adds server-side rendering |

## Components

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-product-card',   // used as <app-product-card />
  imports: [CurrencyPipe],        // components, directives, pipes used in the template
  templateUrl: './product-card.html',
  styleUrl: './product-card.css',
})
export class ProductCard {
  name = 'Keyboard';
}
```

| Property | Purpose |
|---|---|
| `template` / `templateUrl` | Inline HTML / HTML file |
| `styles` / `styleUrl` / `styleUrls` | Inline CSS / one CSS file / several CSS files |
| `encapsulation` | `ViewEncapsulation.Emulated` (default), `None`, `ShadowDom` |
| `changeDetection` | `ChangeDetectionStrategy.Default` or `OnPush` |
| `providers` | Component-level services |

| CSS selector | Selects |
|---|---|
| `:host` | The component's own element |
| `:host(.selected)` | The host element when it has a class |
| `:host-context(.dark)` | The component when a parent has a class |
| `var(--brand-color, #c3002f)` | A CSS custom property with a default |

## Templates

| Syntax | Meaning |
|---|---|
| `{{ name }}` | Interpolation: show a value |
| `{{ user?.name ?? 'Guest' }}` | Optional chaining with a default |
| `@if (cond) { } @else if (x) { } @else { }` | Conditional block |
| `@for (item of items; track item.id) { } @empty { }` | Loop with tracking and an empty state |
| `@switch (value) { @case ('a') { } @default { } }` | Switch block |
| `#input` | Template reference variable |
| `<ng-container>` | Groups elements without adding an element |
| `<ng-template>` | Content that is not rendered until it is used |
| `<ng-content />` | Shows content passed between the component's tags |

## Data Binding

| Binding | Syntax | Direction |
|---|---|---|
| Interpolation | `{{ value }}` | Class → template |
| Property | `[disabled]="isBusy"` | Class → element |
| Attribute | `[attr.aria-label]="label"` | Class → HTML attribute |
| Class | `[class.active]="isActive"` | Class → CSS class |
| Style | `[style.width.px]="width"` | Class → CSS style |
| Event | `(click)="save()"` | Element → class |
| Two-way | `[(ngModel)]="name"` | Both directions |

## Directives and Pipes

| Directive | Example |
|---|---|
| `ngClass` | `[ngClass]="{ active: isActive, error: hasError }"` |
| `ngStyle` | `[ngStyle]="{ color: textColor }"` |
| `*ngIf` (older) | `<p *ngIf="loggedIn">Hi</p>` |
| `*ngFor` (older) | `<li *ngFor="let o of orders; trackBy: trackById">` |
| Custom attribute directive | `@Directive({ selector: '[appHighlight]' })` |

| Pipe | Example | Result |
|---|---|---|
| `uppercase` / `lowercase` | `{{ 'hi' \| uppercase }}` | `HI` |
| `titlecase` | `{{ 'asha rao' \| titlecase }}` | `Asha Rao` |
| `currency` | `{{ 49.9 \| currency }}` | `$49.90` |
| `number` | `{{ 3.14159 \| number: '1.0-2' }}` | `3.14` |
| `percent` | `{{ 0.25 \| percent }}` | `25%` |
| `date` | `{{ today \| date: 'longDate' }}` | `November 6, 2026` |
| `slice` | `{{ text \| slice: 0:10 }}` | First 10 characters |
| `json` | `{{ user \| json }}` | The object as JSON text |
| `async` | `{{ data$ \| async }}` | The latest value of an Observable |

```ts
@Pipe({ name: 'initials' })
export class InitialsPipe implements PipeTransform {
  transform(name: string) {
    return name.split(' ').map(part => part[0]).join('');
  }
}
```

## Event Handling

| Event | Example |
|---|---|
| Click | `(click)="add()"` |
| Key filter | `(keyup.enter)="search()"`, `(keydown.escape)="close()"` |
| Input text | `(input)="text = $any($event.target).value"` |
| Change | `(change)="onSelect($event)"` |
| Form submit | `(ngSubmit)="save()"` |
| Parameters | `(click)="remove(item.id)"` |
| Stop the browser action | `event.preventDefault()` |

## Component Communication

```ts
// Child
@Input() product!: Product;                       // parent → child
@Output() added = new EventEmitter<Product>();    // child → parent
this.added.emit(this.product);
```

```html
<!-- Parent -->
<app-product-card [product]="selected" (added)="addToCart($event)" />

<!-- Two-way: @Input() size + @Output() sizeChange -->
<app-font-size [(size)]="fontSize" />
```

## Lifecycle Hooks

| Hook | Runs |
|---|---|
| `constructor` | When the class is created. Inject services here |
| `ngOnChanges(changes)` | Before `ngOnInit`, and when an `@Input()` gets a new value |
| `ngOnInit()` | Once, after the first inputs are set. Start loading data here |
| `ngDoCheck()` | On every change detection run |
| `ngAfterContentInit()` | Once, after projected content is ready |
| `ngAfterContentChecked()` | After every check of projected content |
| `ngAfterViewInit()` | Once, after the component's view is ready |
| `ngAfterViewChecked()` | After every check of the view |
| `ngOnDestroy()` | Before the component is removed. Clean up here |
| `afterNextRender(fn)` | Once, in the browser only, after the next render |

## Template-Driven Forms and Reactive Forms

| Point | Template-driven | Reactive |
|---|---|---|
| Import | `FormsModule` | `ReactiveFormsModule` |
| Defined in | The template, with `ngModel` | The class, with `FormGroup` / `FormBuilder` |
| Binding | `[(ngModel)]="user.name" name="name"` | `[formGroup]="form"`, `formControlName="name"` |
| Best for | Small, simple forms | Larger forms, dynamic fields, testing |

```ts
private fb = inject(FormBuilder);

form = this.fb.nonNullable.group({
  name: ['', Validators.required],
  phones: this.fb.array([this.fb.control('')]),
});

this.form.setValue({ ... });     // all values
this.form.patchValue({ name });  // some values
this.form.reset();
this.form.getRawValue();
```

## Form Validation

| Validator | Error key |
|---|---|
| `Validators.required` | `required` |
| `Validators.minLength(8)` / `maxLength(20)` | `minlength` / `maxlength` |
| `Validators.min(0)` / `max(100)` | `min` / `max` |
| `Validators.email` | `email` |
| `Validators.pattern(/^[0-9]{6}$/)` | `pattern` |

| State | `true` when |
|---|---|
| `valid` / `invalid` | All rules pass / a rule fails |
| `touched` / `untouched` | The user left the field / did not |
| `dirty` / `pristine` | The user changed the value / did not |
| `pending` | An async validator is running |

```ts
// Custom validator: return null when valid
export function noSpaces(control: AbstractControl): ValidationErrors | null {
  return String(control.value ?? '').includes(' ') ? { noSpaces: true } : null;
}

control.hasError('minlength');
form.markAllAsTouched();          // show all messages on submit
control.setValidators([...]); control.updateValueAndValidity();
```

## Services

```ts
@Injectable({ providedIn: 'root' })
export class CartService {
  private readonly _items = signal<string[]>([]);
  readonly items = this._items.asReadonly();

  add(item: string) {
    this._items.update(list => [...list, item]);
  }
}

// In a component
private cart = inject(CartService);
```

## Dependency Injection

| Provider | Gives |
|---|---|
| `CartService` | A new `CartService` |
| `{ provide: A, useClass: B }` | A `B` when `A` is requested |
| `{ provide: TOKEN, useValue: 20 }` | A fixed value |
| `{ provide: A, useFactory: () => new A(1) }` | The result of a function |
| `{ provide: Old, useExisting: New }` | The same object as `New` |

```ts
export const API_URL = new InjectionToken<string>('API_URL');
providers: [{ provide: API_URL, useValue: 'https://api.shop.com' }]
url = inject(API_URL);
analytics = inject(AnalyticsService, { optional: true });  // null if not provided
```

| Where | Scope and lifetime |
|---|---|
| `providedIn: 'root'` | One object for the app, until the app closes |
| Component `providers` | One object per component, removed with it |

## Routing

```ts
export const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: Home, title: 'Home' },
  { path: 'products/:id', component: ProductDetail },
  { path: 'settings', component: Settings, children: [{ path: 'profile', component: Profile }] },
  { path: 'admin', canActivate: [roleGuard], data: { roles: ['admin'] },
    loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES) },
  { path: 'reports', loadComponent: () => import('./reports/reports').then(m => m.Reports) },
  { path: '**', component: NotFound },
];

provideRouter(routes, withComponentInputBinding())
```

| Task | Code |
|---|---|
| Outlet | `<router-outlet />` |
| Link | `<a routerLink="/products" routerLinkActive="active">` |
| Link with parameter | `[routerLink]="['/products', product.id]"` |
| Query parameters | `[queryParams]="{ category: 'audio' }"` |
| Navigate in code | `inject(Router).navigate(['/orders', id])` |
| Read a parameter | `@Input() id = '';` (with `withComponentInputBinding()`) |

## RxJS Fundamentals

| API | Purpose |
|---|---|
| `of(1, 2)`, `from([...])`, `interval(1000)` | Create Observables |
| `source$.subscribe({ next, error, complete })` | Receive values |
| `sub.unsubscribe()` | Stop receiving |
| `pipe(map(), filter(), tap())` | Change, filter, side effects |
| `switchMap()` | Switch to a new request, cancel the old one |
| `catchError()` / `finalize()` | Replace an error / run at the end |
| `Subject` / `BehaviorSubject` | Send values / send and store the current value |
| `takeUntilDestroyed()` | Stop when the component is destroyed |
| `{{ data$ \| async }}` | Subscribe in the template |

## API Integration

```ts
provideHttpClient(withInterceptors([authInterceptor]))

private http = inject(HttpClient);
this.http.get<User[]>('/api/users');
this.http.post<User>('/api/users', newUser);
this.http.put<User>(`/api/users/${user.id}`, user);
this.http.delete<void>(`/api/users/${id}`);
this.http.get('/api/users', { headers: new HttpHeaders({ 'Accept-Language': 'en' }) });

export const authInterceptor: HttpInterceptorFn = (req, next) =>
  next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
```

| Status | Meaning |
|---|---|
| 200 / 201 / 204 | OK / created / OK with no body |
| 400 | Wrong request data |
| 401 / 403 | Not logged in / not allowed |
| 404 | Not found |
| 500 | Server error |
| 0 | No connection |

## Angular Signals

```ts
count = signal(0);                                  // writable
count();                                            // read
count.set(5);                                       // replace
count.update(n => n + 1);                           // from the current value
items.update(list => [...list, item]);              // new list, never push()
double = computed(() => this.count() * 2);          // derived, read-only
effect(() => console.log(this.count()));            // side effect (in the constructor)
readonly value = this._value.asReadonly();          // read-only view
```

## State Management

| Kind | Where |
|---|---|
| Component state | A signal in the component |
| Shared state | A store service with private signals, read-only access and methods |
| RxJS state | A private `BehaviorSubject` with `asObservable()` |
| Large apps | NgRx: actions → reducer → store → selectors |

```ts
// NgRx
export const addItem = createAction('[Cart] Add Item', props<{ name: string }>());
export const cartReducer = createReducer(initialState, on(addItem, (s, a) => ({ items: [...s.items, a.name] })));
export const selectCount = createSelector(selectCart, cart => cart.items.length);
store.dispatch(addItem({ name: 'Pen' }));
```

## Render Modes

| Mode | HTML is created | `RenderMode` | Best for |
|---|---|---|---|
| CSR | In the browser | `RenderMode.Client` | Apps behind a login |
| SSR | On the server, per request | `RenderMode.Server` | Public pages with changing data |
| SSG | At build time | `RenderMode.Prerender` | Docs, blogs, marketing pages |

```ts
// app.routes.server.ts
{ path: 'docs/:slug', renderMode: RenderMode.Prerender,
  async getPrerenderParams() { return DOCS.map(d => ({ slug: d.slug })); } }

provideClientHydration(withEventReplay())   // app.config.ts
isPlatformBrowser(inject(PLATFORM_ID))      // browser or server?
afterNextRender(() => { /* browser-only code */ });
```

## User Access Control

| Concept | Summary |
|---|---|
| Authentication | Who is the user? (login) |
| Authorization | What may the user do? (roles) |
| JWT | `header.payload.signature`, sent as `Authorization: Bearer <token>` |
| Expiration | The `exp` claim, in seconds; log out when it passes |

```ts
export const authGuard: CanActivateFn = () =>
  inject(AuthService).isLoggedIn() ? true : inject(Router).createUrlTree(['/login']);

{ path: 'admin', component: Admin, canActivate: [roleGuard], data: { roles: ['admin'] } }
```

Always check permissions on the server too.

## Error Handling

| Error | Handle with |
|---|---|
| Missing value | `?.`, `??`, `@if` |
| A function can throw | `try { } catch (error) { } finally { }` |
| HTTP request fails | `catchError()` or the `error` callback |
| Unknown URL | `{ path: '**', component: NotFound }` |
| Missing provider (`NG0201`) | `providedIn: 'root'` or `providers` |
| Everything else | A custom `ErrorHandler` |

```ts
@Injectable()
export class AppErrorHandler implements ErrorHandler {
  handleError(error: unknown) {
    console.error(error);        // developers
    // log to a server, show a fallback message to users
  }
}

providers: [{ provide: ErrorHandler, useClass: AppErrorHandler }]
```

Look up `NG` error codes at angular.dev/errors.

## Performance Optimization

| Technique | Code |
|---|---|
| Lazy loading | `loadComponent` / `loadChildren` |
| Budgets | `"budgets"` in `angular.json` |
| `OnPush` | `changeDetection: ChangeDetectionStrategy.OnPush` |
| New objects for inputs | `this.item = { ...this.item, price: 30 }` |
| No heavy methods in templates | `total = computed(() => ...)` |
| Track list items | `@for (o of orders; track o.id)` / `trackBy: trackById` |
| Measure | Lighthouse, Performance tab, Angular DevTools Profiler, `performance.now()` |
