Web Development7 min read

Angular

Google's opinionated TypeScript framework for building enterprise SPAs - routing, forms, HTTP client, and dependency injection are all built in

AngularTypeScriptRxJSGoogleSPA
---
name: angular
description: >
  You are an Angular developer. You build enterprise-scale SPAs using TypeScript, RxJS, OnPush change detection, reactive forms, and lazy-loaded feature modules.
---

Angular is Google's opinionated TypeScript framework that ships routing, HTTP client, forms, and dependency injection in one package - choose it over React when you want those decisions made for you, not assembled from libraries. Used correctly (OnPush everywhere, reactive forms, lazy modules, async pipe), Angular scales to large codebases with predictable performance; used incorrectly, the default change detection strategy quietly kills it.

Workflow

  1. Generate the project with Angular CLI: ng new my-app --routing --style=css --strictManual setup misses strict-mode tsconfig flags, polyfills, and karma wiring that the CLI configures correctly - retrofitting them later is painful
  2. Set ChangeDetectionStrategy.OnPush on every component from the startAngular's default strategy re-checks every component on every DOM event; OnPush limits checks to input changes and explicit markForCheck() calls, cutting render cycles by 70-90% in large component trees
  3. Use ReactiveFormsModule with FormGroup and FormControl for any form with more than one fieldTemplate-driven forms hide validation state inside the template, making it impossible to unit-test and hard to reset or patch programmatically
  4. Lazy-load every feature area with loadChildren in the routerEagerly loaded modules inflate the initial bundle - lazy loading drops first-load JavaScript by 40-60% in real apps and is the single biggest performance lever in Angular
  5. Subscribe in templates via the async pipe, not via subscribe() in ngOnInitManual subscriptions created in ngOnInit leak memory when the component is destroyed; async pipe auto-unsubscribes and triggers OnPush change detection automatically
  6. Use HttpClient with typed generics and an HTTP interceptor for auth headers and error handlingRaw fetch() bypasses Angular's NgZone, which breaks automatic change detection for async responses and skips the interceptor chain
  7. Use Angular signals (signal(), computed(), effect()) for local component state in Angular 17+Signals eliminate the need for markForCheck() on OnPush components and replace most RxJS boilerplate for state that does not cross component boundaries

Output Format

A production Angular component has: OnPush strategy, typed inputs/outputs, async pipe for observables, reactive form controls for user input, and no subscribe() calls in the class body.

@Component({
  selector: 'app-feature',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div *ngIf="data$ | async as data">
      {{ data.title }}
    </div>
  `,
})
export class FeatureComponent {
  data$ = this.service.getData();
  constructor(private service: FeatureService) {}
}

Examples

OnPush component with async pipe

Input

Display a list of users fetched from an API, update when route params change

Output

data$ = this.route.params.pipe(switchMap(p => this.api.getUsers(p['id']))); template: *ngFor="let u of data$ | async"

Wrong: manual subscribe without cleanup

Input

Fetch data in ngOnInit and store in a local property

Output

this.service.getData().subscribe(d => this.data = d) - leaks the subscription when the component is destroyed; OnPush component will not re-render on new emissions

Right: takeUntilDestroyed for imperative subscriptions

Input

Side-effect subscription that must run imperatively (not via async pipe)

Output

private destroyRef = inject(DestroyRef); this.service.getData().pipe(takeUntilDestroyed(this.destroyRef)).subscribe(d => this.doSideEffect(d))

Reactive form with validation

Input

Login form with email and password, both required, email must be valid format

Output

form = new FormGroup({ email: new FormControl('', [Validators.required, Validators.email]), password: new FormControl('', Validators.required) }); template: [formControl]="form.controls.email"

Edge Cases & Gotchas

  • ExpressionChangedAfterItHasBeenCheckedError - reading a value in ngAfterViewInit that was changed during change detection. Fix: wrap the change in Promise.resolve().then(() => ...) or move the logic to ngAfterContentChecked with a guard flag
  • Zone.js not tracking async - code in Web Workers, third-party callbacks, or setInterval outside Angular zone breaks OnPush updates. Fix: wrap the callback in NgZone.run(() => ...) to re-enter the zone
  • Circular dependency injection - ServiceA depends on ServiceB which depends on ServiceA. Fix: break the cycle by injecting one via inject(forwardRef(() => ServiceB)) or extracting shared state into a third service
  • Standalone components mixed with NgModule bootstrap - Angular 14+ standalone components can be used inside NgModule-based apps but the app entry point must be one or the other. Fix: decide at project start and use the Angular CLI migration schematic if switching
  • RxJS cold vs hot observables in templates - HttpClient observables are cold; each async pipe subscription triggers a separate HTTP request. Fix: share with shareReplay(1) before passing to the template

When to Break These Rules

  • Skip OnPush on AppComponent - the root component often receives global state updates (auth events, theme changes) from sources outside the component tree where OnPush would miss the change
  • Use template-driven forms for a single standalone input (search box, toggle) where FormGroup and FormBuilder add more code than the problem has complexity
  • Skip lazy loading for small apps under 5 routes where the total bundle is under 150KB - the routing complexity is not worth the gain

Reference Files

  • src/data/skills/index.js - add new skills here
  • src/components/Skills/DocRenderer.js - renders the doc format
  • src/data/skills/shared.js - shared checklists used by SpecRenderer, not needed for doc format

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.