Skip to main content

What is Angular?

Angular is a TypeScript-based platform built by Google for creating dynamic client-side web applications using a component-driven architecture with built-in dependency injection, routing, and reactive data binding.

Theory

TL;DR

  • Angular is like a city planner's toolkit: pre-built blocks (components) with exact wiring rules (dependency injection), so the app doesn't fall apart at scale
  • Main difference from React: Angular is a full framework with routing, forms, and HTTP client built in; React is a UI library that needs extra packages for everything
  • Team larger than 5 developers or enterprise scale: Angular. Prototype or small startup: React or Vue
  • TypeScript by default means bugs surface at compile time, not in the browser at 2am
  • Zone.js and the Ivy renderer handle change detection automatically: update data, the DOM follows

Quick example

typescript
// Basic Angular component - app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', // renders as <app-root> in HTML template: ` <h1>{{ title }}</h1> <button (click)="update()">Click me</button> ` }) export class AppComponent { title = 'Hello Angular'; // bound to the template update() { this.title = 'Updated!'; // triggers automatic DOM update } } // No document.querySelector needed. // Angular tracks title and re-renders the h1 on every change.

The @Component decorator tells Angular this is a component, which HTML tag it maps to, and what template to render. Change title in the class, and the browser updates instantly. No manual DOM work.

Angular vs a library

React gives you a render function and says: figure out the rest yourself. Angular ships with routing (@angular/router), an HTTP client (HttpClient), forms (ReactiveFormsModule), animation, and a CLI that scaffolds a full project in one command. That is the core tradeoff: more opinions baked in up front, less decision fatigue at scale.

This matters on teams. A 10-person team using React will spend weeks debating which form library, state manager, and folder structure to use. Angular teams open the same files in the same folders from day one. From what I've seen, that consistency alone reduces onboarding time noticeably on large projects.

When to use

  • Enterprise dashboard with auth guards and lazy-loaded feature modules: Angular
  • Team larger than 10 developers: Angular (strict TypeScript catches cross-team bugs early)
  • Mobile app with native-feeling components: Angular + Ionic
  • Small prototype or MVP: React or Vue spin up faster
  • Refactoring legacy jQuery code: Angular directives replace most manual DOM manipulation cleanly

Comparison table

FeatureAngularReactVue
TypeFull frameworkUI libraryFramework
LanguageTypeScriptJS/TSJS/TS
Hello World bundle~65KB gzipped~35KB~20KB
Learning curve1-2 weeks~3 days~1 day
Built-in routingYesNo (React Router)Yes
Built-in formsReactive + template-drivenNo (Formik, RHF)No
When to use50+ components, enterpriseCustom UI, flexible stackRapid start, smaller apps

How the Ivy renderer and Zone.js work

The browser loads index.html containing <app-root>. Angular CLI compiles TypeScript to JS bundles via webpack, then the Ivy renderer (default since Angular 9) parses @Component decorators and builds a component tree in memory. Zone.js patches browser async APIs - setTimeout, Promises, event listeners - so Angular knows exactly when async work finishes and triggers change detection. DOM updates happen in microtasks, not full page re-renders.

Ivy reduced bundle sizes by roughly 40% compared to the older View Engine because it compiles each component independently instead of running one global compilation pass for the whole app. Rebuilds in development got faster too.

Common mistakes

1. *ngFor without trackBy

html
<!-- BAD: Angular destroys and recreates all list items on every array update --> <li *ngFor="let user of users">{{ user.name }}</li> <!-- GOOD: Angular reuses existing DOM nodes by identity --> <li *ngFor="let user of users; trackBy: trackById">{{ user.name }}</li>

On lists with 500+ items the render time difference is visible without profiling tools.

2. Service registered in component providers instead of root

typescript
// BAD: creates a new UserService instance per component - breaks shared state @Component({ providers: [UserService] }) // GOOD: one singleton for the whole app @Injectable({ providedIn: 'root' }) export class UserService {}

3. Missing async pipe on Observables

html
<!-- BAD: crashes when the Observable has not emitted yet --> <div>{{ (user$ | async).name }}</div> <!-- GOOD: safe navigation handles the undefined case --> <div *ngIf="user$ | async as user">{{ user.name }}</div>

4. OnPush change detection with object mutation

typescript
// BAD: same object reference - OnPush skips the check, UI goes stale this.data.value = 'new'; // no UI update! // GOOD: new reference triggers the check this.data = { ...this.data, value: 'new' };

This catches even experienced developers who come from mutable-state backgrounds.

Real-world usage

  • Google Workspace: 100+ Angular components with lazy-loaded modules
  • Microsoft Office Online: Angular 16+ for real-time collaboration inside an Nx monorepo
  • Forbes.com: Angular Universal (SSR) for server-rendered pages and SEO
  • NBA.com: RxJS Observables streaming live scores
  • PayPal checkout: custom Angular directives handling payment form logic

Follow-up questions

Q: What is the difference between template-driven and reactive forms?


A: Template-driven forms use ngModel for two-way binding and work for simple cases. Reactive forms define the model in TypeScript using FormBuilder, are immutable, easier to unit-test, and scale well to complex validation scenarios.

Q: Explain the Angular dependency injection hierarchy.


A: providedIn: 'root' creates one singleton for the whole app. Adding a service to a component's providers: [] creates a new instance scoped to that component and its children. Lazy-loaded modules get their own injector. Understanding this hierarchy explains most "why is my service data stale?" bugs.

Q: What did the Ivy renderer improve over the old View Engine?


A: Ivy compiles each component independently instead of one global pass for the whole app. That cuts bundle sizes by around 40%, improves tree-shaking, and speeds up incremental rebuilds in development.

Q: Why does Angular need Zone.js?


A: Zone.js patches browser async APIs so Angular can detect when asynchronous work finishes and automatically run change detection. Without it you would call ChangeDetectorRef.detectChanges() manually after every HTTP response, timer, or event.

Q: Senior question: with ChangeDetectionStrategy.OnPush and the async pipe, will the component update when a new value arrives from an NgRx selector? Why?


A: Yes. The async pipe internally calls markForCheck() on the component whenever the Observable emits. NgRx select returns a new Observable reference on each state change. So even with OnPush, the pipe marks the component dirty and Angular includes it in the next change detection cycle.

Examples

Basic: click counter

typescript
// counter.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-counter', template: ` <p>Count: {{ count }}</p> <button (click)="increment()">+1</button> ` }) export class CounterComponent { count = 0; increment() { this.count++; // Angular detects the change and updates <p> automatically } }

This shows the core loop: user action calls a method, the method updates data, Angular re-renders the template. No DOM queries anywhere.

Intermediate: user list fetched from an API

typescript
// user.service.ts import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class UserService { constructor(private http: HttpClient) {} getUsers(): Observable<any[]> { return this.http.get<any[]>('https://jsonplaceholder.typicode.com/users'); } } // users.component.ts @Component({ template: ` <ul> <li *ngFor="let user of users; trackBy: trackById"> {{ user.name }} </li> </ul> ` }) export class UsersComponent { users: any[] = []; constructor(private userService: UserService) { this.userService.getUsers().subscribe(data => this.users = data); } trackById(index: number, user: any) { return user.id; // prevents full list re-render when one item changes } } // Renders 10 user names from the API. UserService is auto-injected by Angular DI.

UserService is declared once with providedIn: 'root', so every component that needs it gets the same instance with no extra configuration.

Advanced: OnPush change detection with immutable data

typescript
// This pattern catches most mid-level Angular developers at least once. @Component({ changeDetection: ChangeDetectionStrategy.OnPush // only checks when @Input reference changes }) export class UserCardComponent { @Input() user: { name: string }; } // Parent component - WRONG approach: this.user.name = 'Jane'; // same object reference - OnPush skips the check, UI stays stale! // CORRECT approach: new object reference this.user = { ...this.user, name: 'Jane' }; // OnPush detects new reference, updates UI // Same rule for arrays: // WRONG: this.items.push(newItem) // CORRECT: this.items = [...this.items, newItem]

OnPush is a performance optimization that tells Angular to skip a component during change detection unless one of its @Input values changed by reference. It can cut unnecessary rendering in half on large component trees, but it requires treating all data as immutable.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?