| |

Angular 132 🅰️ Cheatsheet

⏱️ Estimated read: 10 min | |

This cheatsheet is a dense, practical reference for Angular’s core syntax, CLI commands, and modern reactive primitives. It prioritizes the features that appear most frequently in daily development—template binding, component configuration, signals, dependency injection, routing, forms, HTTP, and testing. It reflects Angular 17+ where standalone components are the default, built-in control flow blocks replace the structural directives, and signals are the primary synchronous reactive primitive.

Key point: Angular’s template syntax is HTML augmented with binding brackets and control flow blocks. Signals replace many Observable patterns for synchronous state; RxJS remains the tool for asynchronous streams. Standalone components are the default since Angular 17, and NgModules are optional rather than required.


CLI Commands

CommandAliasPurpose
ng new <name>Create workspace and application
ng new <name> --standaloneExplicit standalone (default in 17+)
ng new <name> --routingInclude routing module
ng serveDev server with live reload
ng serve --port 4201Custom port
ng serve --open-oOpen browser after start
ng buildbCompile into dist/
ng build --configuration productionProduction build (replaces --prod)
ng generate <schematic> <name>gScaffold a file
ng testRun unit tests (Karma/Jest)
ng e2eRun end-to-end tests
ng lintRun linter
ng updateUpdate Angular packages
ng add <package>Install library with auto-config
ng configRead/write workspace config
ng analyticsEnable/disable usage analytics
ng completionEnable shell autocompletion

Generation schematics: component (c), service (s), module (m), directive (d), pipe (p), guard, interceptor, interface, class, enum, resolver, environments.

ng g c features/user-card          # standalone component by default
ng g s core/auth                   # service with providedIn: 'root'
ng g guard auth --implements=CanActivate
ng g interceptor auth --functional  # functional interceptor (v15+)
ng g pipe shared/truncate

Project Structure

PathPurpose
src/app/Application code
src/app/app.component.tsRoot component
src/app/app.config.tsApp-level providers (v15+)
src/app/app.routes.tsRoute definitions
src/main.tsBootstrap entry
src/environments/Environment-specific config
angular.jsonWorkspace build configuration
tsconfig.jsonTypeScript configuration
src/styles.cssGlobal styles
public/Static assets (v17+, was assets/)

Template Syntax

SyntaxPurposeExample
{{ expr }}Interpolation{{ user.name }}
[prop]="expr"Property binding[disabled]="isLoading"
(event)="handler()"Event binding(click)="save()"
[(ngModel)]="prop"Two-way binding[(ngModel)]="email"
[attr.aria-label]="expr"Attribute binding[attr.colspan]="colSpan"
[class.active]="bool"Class binding[class.active]="isActive"
[style.color]="expr"Style binding[style.width.px]="width"
#refTemplate reference<input #inputRef>
expr?.propSafe navigation{{ employer?.companyName }}
expr!Non-null assertion{{ user!.name }}
$eventEvent payload(click)="handle($event)"
(keyup.enter)Filtered event(keyup.enter)="submit()"
(click.stop)Event modifier(click.stop)="handle()"

Template statements support assignment (=) and chaining (;), but not new, ++, --, +=, or bitwise operators.


Control Flow (Modern Blocks, v17+)

@if (user) {
  <p>Welcome, {{ user.name }}</p>
} @else if (user === null) {
  <p>Loading...</p>
} @else {
  <p>Please log in.</p>
}

@for (item of items; track item.id; let i = $index) {
  <li>{{ i }}: {{ item.name }}</li>
} @empty {
  <li>No items.</li>
}

@switch (status) {
  @case ('loading') { <spinner /> }
  @case ('error') { <error-msg /> }
  @default { <content /> }
}

@for implicit variables: $index, $count, $first, $last, $even, $odd. The track expression is required for performance and correctness.

Deferred loading (v17+):

@defer (on viewport) {
  <heavy-chart />
} @placeholder {
  <skeleton />
} @loading {
  <spinner />
} @error {
  <error-msg />
}

Triggers: on idle, on viewport, on interaction, on hover, on timer(5s), when condition.


Component Configuration

@Component({
  selector: 'app-user-card',
  standalone: true,
  imports: [CommonModule, RouterLink, DatePipe],
  templateUrl: './user-card.html',
  styleUrls: ['./user-card.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [UserService],
  host: { '[class.active]': 'isActive' },
})
export class UserCardComponent {
  @Input({ required: true }) name!: string;
  @Input() role: string = 'user';
  @Output() selected = new EventEmitter<string>();
}

Signal-based inputs/outputs (v17.1+):

import { input, output, model } from '@angular/core';

name = input<string>();              // optional
id = input.required<number>();       // required
role = input('user');                // with default
selected = output<string>();         // replaces EventEmitter
value = model(0);                    // two-way signal input

Signals

import { signal, computed, effect, linkedSignal } from '@angular/core';

// Writable signal
const count = signal(0);
count();                    // read
count.set(5);               // write
count.update(c => c + 1);   // derive from previous
count.asReadonly();         // expose read-only view

// Computed (lazy, memoized)
const double = computed(() => count() * 2);

// Linked signal (v19): writable, resets from source
const selection = linkedSignal(() => items()[0]);

// Effect (side effects, runs async during change detection)
effect(() => console.log('count:', count()));

// Untracked read (does not create dependency)
import { untracked } from '@angular/core';
effect(() => console.log(count(), untracked(() => other())));

Bridging RxJS and signals:

import { toSignal, toObservable } from '@angular/core/rxjs-interop';

const users = toSignal(users$, { initialValue: [] });
const query$ = toObservable(searchQuery);

Lifecycle Hooks

HookWhen
ngOnChangesBefore ngOnInit, when @Input changes
ngOnInitOnce, after first ngOnChanges
ngDoCheckEvery change detection run
ngAfterContentInitAfter projected content (ng-content)
ngAfterContentCheckedAfter every check of projected content
ngAfterViewInitAfter component view and children
ngAfterViewCheckedAfter every check of view
ngOnDestroyBefore destruction; unsubscribe here

Functional alternatives:

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

constructor() {
  obs$.pipe(takeUntilDestroyed()).subscribe(...);
}

Services and Dependency Injection

@Injectable({ providedIn: 'root' })
export class DataService {
  private http = inject(HttpClient);

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users');
  }
}

// In component:
private data = inject(DataService);

Injection tokens:

export const API_URL = new InjectionToken<string>('API_URL');

// Provide
providers: [{ provide: API_URL, useValue: 'https://api.example.com' }]

// Inject
private url = inject(API_URL);

inject() restrictions: must be called in an injection context (constructor, field initializer, factory function, or runInInjectionContext).


Routing

// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users', component: UserListComponent },
  { path: 'users/:id', component: UserDetailComponent },
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent),
    canActivate: [authGuard],
  },
  {
    path: 'settings',
    loadChildren: () => import('./settings/settings.routes').then(m => m.routes),
  },
  { path: '**', redirectTo: '' },
];
<router-outlet />
<a routerLink="/users">Users</a>
<a [routerLink]="['/users', id]" routerLinkActive="active">Detail</a>

Reading route parameters:

private route = inject(ActivatedRoute);
private router = inject(Router);

// Snapshot (one-time)
id = this.route.snapshot.paramMap.get('id');

// Observable (changes)
id$ = this.route.paramMap.pipe(map(p => p.get('id')));

// Navigation
this.router.navigate(['/users', 42]);
this.router.navigateByUrl('/users/42');

Functional guards (v15+):

export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  return auth.isLoggedIn() || inject(Router).createUrlTree(['/login']);
};

Resolvers:

export const userResolver: ResolveFn<User> = (route) =>
  inject(UserService).getUser(route.paramMap.get('id')!);

HTTP Client

import { provideHttpClient, withInterceptors } from '@angular/common/http';

// app.config.ts
providers: [
  provideHttpClient(
    withInterceptors([authInterceptor, loggingInterceptor]),
  ),
]
@Injectable({ providedIn: 'root' })
export class ApiService {
  private http = inject(HttpClient);

  get<T>(url: string): Observable<T> {
    return this.http.get<T>(url);
  }

  post<T>(url: string, body: unknown): Observable<T> {
    return this.http.post<T>(url, body);
  }
}

Functional interceptor:

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

HttpParams and HttpHeaders:

let params = new HttpParams()
  .set('page', '1')
  .set('limit', '20');

this.http.get('/api/users', { params });

Forms

Reactive forms (preferred):

import { FormGroup, FormControl, Validators } from '@angular/forms';

form = new FormGroup({
  email: new FormControl('', [Validators.required, Validators.email]),
  age: new FormControl<number>(0, [Validators.min(18)]),
  name: new FormGroup({
    first: new FormControl(''),
    last: new FormControl(''),
  }),
});
<form [formGroup]="form" (ngSubmit)="submit()">
  <input formControlName="email">
  <span *ngIf="form.get('email')?.invalid && form.get('email')?.touched">
    Invalid email
  </span>
  <button [disabled]="form.invalid">Submit</button>
</form>

Template-driven forms:

<form #f="ngForm" (ngSubmit)="submit(f)">
  <input name="email" [(ngModel)]="email" required email>
  <button [disabled]="f.invalid">Submit</button>
</form>

FormBuilder shorthand:

form = inject(FormBuilder).group({
  email: ['', [Validators.required, Validators.email]],
  age: [0, Validators.min(18)],
});

Common validators: required, email, min, max, minLength, maxLength, pattern, nullValidator. Custom validator is a function returning ValidationErrors | null.


Pipes

PipeExampleOutput
date{{ createdAt | date:'dd MMM yyyy' }}28 Jul 2026
currency{{ price | currency:'EUR' }}€29.99
uppercase{{ name | uppercase }}ROHIT
lowercase{{ name | lowercase }}rohit
titlecase{{ name | titlecase }}Rohit
json{{ obj | json }}Pretty JSON
async{{ obs$ | async }}Unwrapped value
percent{{ ratio | percent }}42%
number{{ value | number:'1.2-2' }}1,234.56
slice{{ items | slice:0:5 }}First 5 items

Custom pipe:

@Pipe({ name: 'truncate', standalone: true })
export class TruncatePipe implements PipeTransform {
  transform(value: string, max = 20): string {
    return value.length > max ? value.slice(0, max) + '…' : value;
  }
}

Date patterns: 'yyyy-MM-dd', 'dd/MM/yy HH:mm', 'EEEE' (full weekday), 'MMM d, y'.


Directives

Attribute directive:

@Directive({ selector: '[appHighlight]', standalone: true })
export class HighlightDirective {
  private el = inject(ElementRef);
  @Input() appHighlight = 'yellow';

  @HostListener('mouseenter')
  onEnter() {
    this.el.nativeElement.style.background = this.appHighlight;
  }
}

Structural directive (custom):

@Directive({ selector: '[appIf]', standalone: true })
export class IfDirective {
  private template = inject(TemplateRef);
  private vcr = inject(ViewContainerRef);
  private hasView = false;

  @Input() set appIf(condition: boolean) {
    if (condition && !this.hasView) {
      this.vcr.createEmbeddedView(this.template);
      this.hasView = true;
    } else if (!condition && this.hasView) {
      this.vcr.clear();
      this.hasView = false;
    }
  }
}

Change Detection

StrategyBehavior
DefaultChecks component on every change detection run
OnPushChecks only when inputs change, event fires, or signal changes

ChangeDetectorRef methods:

private cdr = inject(ChangeDetectorRef);

cdr.markForCheck();    // schedule check (OnPush)
cdr.detectChanges();   // run check immediately
cdr.detach();          // stop checking
cdr.reattach();        // resume checking

Zoneless (v18+, experimental):

// app.config.ts
providers: [provideExperimentalZonelessChangeDetection()]

Zoneless change detection relies on signals and explicit markForCheck() calls to schedule updates.


Testing

// Component test
import { TestBed } from '@angular/core/testing';

describe('UserCardComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserCardComponent],
      providers: [{ provide: DataService, useValue: mockService }],
    }).compileComponents();
  });

  it('shows the user name', () => {
    const fixture = TestBed.createComponent(UserCardComponent);
    fixture.componentRef.setInput('name', 'Rohit');
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toContain('Rohit');
  });
});
// Service test with HttpTestingController
import { provideHttpClientTesting } from '@angular/common/http/testing';

it('fetches users', () => {
  const service = TestBed.inject(DataService);
  const http = TestBed.inject(HttpTestingController);

  service.getUsers().subscribe(users => expect(users.length).toBe(2));

  const req = http.expectOne('/api/users');
  req.flush([{ id: 1 }, { id: 2 }]);
});

Test utilities: TestBed.createComponent, ComponentFixture, nativeElement, debugElement, setInput() (v15+), fakeAsync/tick, waitForAsync.


Common Pitfalls

PitfallFix
Using *ngIf in new projectsPrefer @if blocks (v17+)
Forgetting track in @forRequired; use unique ID
Mutating signal objects directlyUse .update() with immutable pattern
Missing imports in standalone componentAdd dependencies to imports: []
Subscribing without unsubscribingasync pipe or takeUntilDestroyed()
ng build --prodUse --configuration production (v13+)
inject() outside injection contextCall in constructor/field initializer only
OnPush component not updatingCall markForCheck() or use signals
Forgetting provideHttpClient()Add to app.config.ts (v15+)
ngModel without name in formAdd name attribute or use reactive forms

Visual: Change Detection with Signals

┌──────────────────────────────────────────────┐
│  SIGNAL UPDATE FLOW                          │
│                                              │
│  signal.set(5)                               │
│       │                                      │
│       ▼                                      │
│  Mark consumers dirty                        │
│       │                                      │
│       ▼                                      │
│  Schedule change detection                   │
│       │                                      │
│       ▼                                      │
│  Check OnPush components that read signal    │
│       │                                      │
│       ▼                                      │
│  Update template bindings                    │
│                                              │
│  Unlike zone.js, no DOM event triggers       │
│  a global check. Only signal consumers       │
│  are marked.                                 │
│                                              │
└──────────────────────────────────────────────┘

Summary

AreaKey Takeaway
CLIng g scaffolds; ng build --configuration production
Structuresrc/app/, app.config.ts, app.routes.ts
Templates{{ }}, [prop], (event), [(ngModel)]
Control flow@if, @for (with track), @switch, @defer
ComponentsStandalone by default; imports: []
Signalssignal(), computed(), effect(), linkedSignal()
Inputs/Outputsinput() / output() / model()
Services@Injectable({ providedIn: 'root' }) + inject()
RoutingLazy load with loadComponent; functional guards
HTTPprovideHttpClient(), functional interceptors
FormsReactive: FormGroup, FormControl, Validators
Pipesdate, currency, async, json; custom via @Pipe
TestingTestBed, HttpTestingController, setInput()
Change detectionOnPush + signals; zoneless in v18+

Angular’s modern surface is signals, standalone components, built-in control flow, and functional interceptors. The cheatsheet is a map; the official Angular documentation at angular.dev is the territory. Angular’s release cadence moves fast—features stabilize across versions, and the cheat sheet reflects the stable core as of Angular 17–19. When a syntax detail matters, verify against the version you are using.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!