A comprehensive deep-dive into Google's powerful web frameworks — from the origins of AngularJS to the modern Angular platform.
From a weekend project at Google to one of the world's most widely used frontend frameworks.
Misko Hevery and Adam Abrons create Angular as a side project. Hevery rewrites a 17,000-line app in just 1,500 lines using his framework, convincing Google to adopt it internally.
AngularJS is released publicly on GitHub. It introduces revolutionary concepts like two-way data binding, directives, and dependency injection to the JavaScript world.
After years of development, AngularJS 1.0 is officially released. It rapidly becomes the dominant frontend framework, popularizing MVC/MVVM architecture in SPAs.
At ng-Europe, Google announces a complete rewrite of Angular. The announcement causes controversy as it breaks backward compatibility with AngularJS entirely.
Angular 2 launches as a completely new framework built in TypeScript. Component-based architecture replaces controllers/scopes. Modules, decorators, and RxJS become core concepts.
Angular skips v3 to align router package versions. Introduces semantic versioning with predictable 6-month release cycles. Angular CLI matures significantly.
Ivy becomes the default rendering engine. Dramatically improves bundle sizes, compilation speed, and debugging. Tree-shaking becomes much more effective.
Standalone components, directives, and pipes are introduced, allowing developers to build Angular apps without NgModules. Typed reactive forms also added.
Angular Signals introduced as a new reactivity primitive. Angular 17 launches a new documentation site (angular.dev), deferrable views, and major SSR/hydration improvements.
Experimental zoneless change detection with Signals. Incremental hydration, route-level rendering modes, and resource API preview. Angular continues modernizing its DX.
Click any card to expand it and learn more about each Angular concept.
The fundamental building block of Angular. A component controls a view — a region of the screen — through a class decorated with @Component.
selector — CSS selector identifying this component in a templatetemplate/templateUrl — The HTML viewstyles/styleUrls — Scoped CSSstandalone: true — Opt out of NgModule (Angular 14+)Angular's HTML-based template syntax extends HTML with directives, binding, and interpolation to connect the view to the component's data.
{{ expression }} — Interpolation[property]="expr" — Property binding(event)="handler()" — Event binding[(ngModel)]="prop" — Two-way binding*ngIf / *ngFor — Structural directives@if / @for — New control flow (v17+)
Angular's powerful DI system allows services and other dependencies to be injected into components, promoting separation of concerns and testability.
Singleton classes that provide shared functionality — data fetching, business logic, state management — across multiple components.
@Injectable()providedIn: 'root' for app-wide singletonsHttpClient for HTTP requestsContainers that group related components, directives, pipes, and services. The root AppModule bootstraps the application.
declarations — Components/directives/pipesimports — Other modules neededexports — Public API of this moduleproviders — Servicesbootstrap — Root componentAngular's powerful client-side navigation system enables single-page application routing with lazy loading, guards, and resolvers.
RouterModule.forRoot(routes) — Root routingRouterModule.forChild(routes) — Feature routingrouter-outlet — Where routes renderrouterLink — Navigation directiveAngular is deeply integrated with RxJS for reactive programming. HTTP requests, events, and state changes flow as observable streams.
map, filter, tap — Transform streamsswitchMap, mergeMap, concatMap — Flatten observablesdebounceTime, distinctUntilChanged — Rate limitingtakeUntil, take, first — Completion controlcombineLatest, forkJoin, zip — Combine streamsAngular Signals are a new reactive primitive that synchronously track state changes, enabling fine-grained reactivity without Zone.js.
signal(value) — Create a writable signalcomputed(() => expr) — Derived reactive valueeffect(() => {}) — Side effects.set(v), .update(fn), .mutate(fn) — Update signalClasses that add behavior to DOM elements. Angular has three types: Component directives, Structural directives, and Attribute directives.
@Directive({ selector: '[appHighlight]' })HostListener for events and HostBinding for properties.
Transform displayed values in templates. Pure pipes only execute when input changes; impure pipes run on every change detection cycle.
DatePipe — Format datesCurrencyPipe — Format currencyDecimalPipe — Format numbersUpperCasePipe / LowerCasePipeAsyncPipe — Unwrap Observables/PromisesJsonPipe — Serialize to JSONPipeTransform interface and its transform() method.
Angular provides two approaches to handling user input: Template-driven forms and Reactive forms, each with different trade-offs.
FormControl — Single fieldFormGroup — Group of controlsFormArray — Dynamic list of controlsFormBuilder — Factory helperAngular's mechanism to sync the view with the model. Zone.js intercepts async ops and triggers detection; Signals offer a zoneless future.
Default — Check entire component treeOnPush — Check only on @Input changes or observables emittingChangeDetectorRef.detectChanges()ChangeDetectorRef.markForCheck()NgZone.runOutsideAngular()Angular's layered architecture separates concerns cleanly from templates to services to the platform.
Real-world code patterns across components, services, routing, and more.
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { UserService } from './user.service'; import { User } from './user.model'; @Component({ selector: 'app-user-profile', standalone: true, imports: [CommonModule], template: ` <div class="profile" *ngIf="user; else loading"> <img [src]="user.avatar" [alt]="user.name" /> <h2>{{ user.name }}</h2> <p>{{ user.email }}</p> <button (click)="onEdit()">Edit Profile</button> </div> <ng-template #loading>Loading...</ng-template> `, styleUrls: ['./user-profile.component.scss'] }) export class UserProfileComponent implements OnInit { @Input() userId!: string; @Output() editRequested = new EventEmitter<User>(); user: User | null = null; constructor(private userService: UserService) {} ngOnInit(): void { this.userService .getUserById(this.userId) .subscribe(user => this.user = user); } onEdit(): void { if (this.user) this.editRequested.emit(this.user); } }
<!-- New @if / @for / @switch syntax (Angular 17+) --> <div class="user-list"> @if (users().length > 0) { <ul> @for (user of users(); track user.id) { <li> {{ user.name }} — {{ user.role }} @if (user.isAdmin) { <span class="badge">Admin</span> } </li> } @empty { <li>No users found</li> } </ul> } @else { <p>Loading users...</p> } </div> <!-- Deferrable Views (v17+) --> @defer (on viewport) { <app-heavy-widget /> } @placeholder { <div class="skeleton"></div> } @loading (minimum 300ms) { <app-spinner /> }
import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable, BehaviorSubject, throwError } from 'rxjs'; import { catchError, map, tap, retry } from 'rxjs/operators'; import { User } from './user.model'; @Injectable({ providedIn: 'root' // Singleton at root level }) export class UserService { private readonly API = 'https://api.example.com/users'; private currentUser$ = new BehaviorSubject<User | null>(null); /** Public observable for current user */ currentUser = this.currentUser$.asObservable(); constructor(private http: HttpClient) {} getUsers(page = 1, limit = 10): Observable<User[]> { const params = new HttpParams() .set('page', page) .set('limit', limit); return this.http.get<{ data: User[] }>(this.API, { params }).pipe( map(res => res.data), retry(2), catchError(err => throwError(() => err)) ); } getUserById(id: string): Observable<User> { return this.http.get<User>(`${this.API}/${id}`).pipe( tap(user => this.currentUser$.next(user)) ); } updateUser(id: string, data: Partial<User>): Observable<User> { return this.http.patch<User>(`${this.API}/${id}`, data); } logout(): void { this.currentUser$.next(null); } }
import { Routes } from '@angular/router'; import { authGuard } from './guards/auth.guard'; import { userResolver } from './resolvers/user.resolver'; export const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', loadComponent: () => import('./home/home.component') .then(m => m.HomeComponent) }, { path: 'users', canActivate: [authGuard], // Route guard loadChildren: () => import('./users/users.routes') // Lazy module .then(m => m.USER_ROUTES) }, { path: 'profile/:id', loadComponent: () => import('./profile/profile.component') .then(m => m.ProfileComponent), resolve: { user: userResolver // Pre-fetch data } }, { path: '**', loadComponent: () => import('./not-found/not-found.component') .then(m => m.NotFoundComponent) } ]; // Functional guard (Angular 15+) export const authGuard = () => { const auth = inject(AuthService); const router = inject(Router); return auth.isLoggedIn() || router.parseUrl('/login'); };
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'app-registration', standalone: true, imports: [ReactiveFormsModule], template: ` <form [formGroup]="form" (ngSubmit)="onSubmit()"> <input formControlName="email" placeholder="Email" /> <div *ngIf="email?.invalid && email?.touched"> <span *ngIf="email?.errors?.['required']">Email is required</span> <span *ngIf="email?.errors?.['email']">Invalid email</span> </div> <div formGroupName="passwords"> <input formControlName="password" type="password" /> <input formControlName="confirm" type="password" /> <span *ngIf="passwords?.errors?.['mismatch']">Passwords must match</span> </div> <button type="submit" [disabled]="form.invalid">Register</button> </form> ` }) export class RegistrationComponent implements OnInit { form!: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ email: ['', [Validators.required, Validators.email]], passwords: this.fb.group({ password: ['', [Validators.required, Validators.minLength(8)]], confirm: ['', Validators.required] }, { validators: this.matchPasswords }) }); } matchPasswords(group: AbstractControl) { const pw = group.get('password')?.value; const cf = group.get('confirm')?.value; return pw === cf ? null : { mismatch: true }; } get email() { return this.form.get('email'); } get passwords() { return this.form.get('passwords'); } onSubmit() { if (this.form.valid) console.log(this.form.value); } }
import { Component, signal, computed, effect } from '@angular/core'; import { toSignal, toObservable } from '@angular/core/rxjs-interop'; @Component({ selector: 'app-counter', standalone: true, template: ` <div> <h2>Count: {{ count() }}</h2> <h3>Doubled: {{ doubled() }}</h3> <p>Status: {{ status() }}</p> <button (click)="increment()">+</button> <button (click)="decrement()">−</button> <button (click)="reset()">Reset</button> </div> ` }) export class CounterComponent { // Writable signal count = signal(0); // Computed (derived) signal — updates automatically doubled = computed(() => this.count() * 2); status = computed(() => { const n = this.count(); if (n > 10) return 'High'; if (n < 0) return 'Negative'; return 'Normal'; }); // Effect — runs when signal values change logEffect = effect(() => { console.log(`Count changed to: ${this.count()}`); }); increment() { this.count.update(n => n + 1); } decrement() { this.count.update(n => n - 1); } reset() { this.count.set(0); } } // RxJS interop — convert signal ↔ observable const obs$ = toObservable(count); // Signal → Observable const sig = toSignal(someObs$); // Observable → Signal
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core'; @Directive({ selector: '[appHighlight]', standalone: true }) export class HighlightDirective { @Input('appHighlight') highlightColor = 'yellow'; @Input() defaultColor = 'transparent'; constructor( private el: ElementRef, private renderer: Renderer2 ) {} @HostListener('mouseenter') onMouseEnter() { this.highlight(this.highlightColor); } @HostListener('mouseleave') onMouseLeave() { this.highlight(this.defaultColor); } private highlight(color: string) { this.renderer.setStyle( this.el.nativeElement, 'background-color', color ); } } // Usage in template: // <p appHighlight="cyan" defaultColor="lightgray">Hover me</p> // <p [appHighlight]="dynamicColor">Dynamic color</p>
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'truncate', standalone: true, pure: true // Default: only re-runs when input changes }) export class TruncatePipe implements PipeTransform { transform( value: string, limit = 100, trail = '...' ): string { if (!value) return ''; if (value.length <= limit) return value; return value.substring(0, limit) + trail; } } // Built-in pipe examples: // {{ today | date:'longDate' }} // {{ price | currency:'USD':'symbol':'1.2-2' }} // {{ text | truncate:50:'…' }} // {{ userObs$ | async }} ← async pipe auto-subscribes // {{ items | slice:0:5 }} // {{ num | number:'1.0-2' }} // {{ value | uppercase }} // {{ obj | json }} // {{ text | i18nSelect: mapping }}
Angular manages each component's lifecycle from creation to destruction. These hooks let you tap in at key moments.
Class instantiated, DI runs. Don't access @Input values here — they aren't set yet. Keep it lean: DI only.
Fires when any @Input property changes. Receives a SimpleChanges object with previous and current values. Runs before ngOnInit.
Runs once after the first ngOnChanges. Perfect for fetching data, initializing forms, and setting up subscriptions. Most common hook.
Runs on every change detection cycle. Use sparingly — it fires frequently. Good for detecting changes Angular can't catch (deep object mutations).
Fires after Angular projects external content into the component via <ng-content>. @ContentChild/@ContentChildren are accessible here.
Runs after every content change detection check. Avoid heavy logic here. Called after every ngDoCheck for projected content.
Fires after the component's view (and child views) are initialized. @ViewChild/@ViewChildren are now accessible. Good for DOM manipulation.
Runs after every view change detection. Be careful — modifying bound data here can trigger ExpressionChangedAfterChecked errors.
Called before Angular destroys the component. Unsubscribe from Observables, clear timers, detach event listeners. Critical to prevent memory leaks.
The Angular CLI is an essential tool for initializing, developing, scaffolding, and maintaining Angular applications.
Angular (v2+) is a complete rewrite of AngularJS (v1). They share a name but little else.
| Feature | AngularJS (v1) | Angular (v2+) |
|---|---|---|
| Language | JavaScript | TypeScript (primary) |
| Architecture | MVC / MVVM | Component-based |
| Data Binding | Two-way (ng-model) | One-way + explicit two-way [(ngModel)] |
| Rendering | Digest cycle ($scope) | Ivy renderer + Zone.js / Signals |
| DI System | Service-based, single injector | Hierarchical injector tree |
| Routing | ngRoute / ui-router | @angular/router (built-in) |
| Mobile | Not optimized | Mobile-first, PWA ready |
| Performance | $watch overhead | AOT compilation, tree-shaking |
| Testing | Jasmine + Karma | Jasmine + Karma + Jest + Testing Library |
| CLI | None official | Angular CLI (powerful) |
| Server-Side Rendering | Not built-in | Angular Universal / built-in SSR (v17+) |
| Lazy Loading | Manual, complex | Native, route-based |
| Animations | CSS + ngAnimate | @angular/animations (state machine) |
| Forms | Template-driven only | Template-driven + Reactive |
| Internationalization | Manual | Built-in i18n + Angular Localize |
| EOL Status | End of Life Dec 2021 | Actively maintained |
Angular is an opinionated, batteries-included framework designed for large-scale enterprise applications.
Built with and for TypeScript. Enjoy static typing, interfaces, generics, and advanced IDE support out of the box.
The Ivy compiler eliminates unused code, resulting in dramatically smaller bundle sizes for production builds.
Ahead-of-Time compilation converts HTML and TypeScript into efficient JavaScript during the build process, before the browser downloads it.
Load feature modules only when needed, drastically reducing initial bundle size and improving Time-to-Interactive.
Server-Side Rendering and Static Site Generation for improved SEO, performance, and social sharing previews.
Angular sanitizes data by default, preventing XSS attacks. HttpClient includes XSRF/CSRF protection. Content Security Policy support.
ARIA attributes, keyboard navigation, and the Angular CDK A11y package provide powerful accessibility primitives.
Angular CLI provides first-class PWA support via @angular/pwa — adding service workers and a web manifest in one command.
Dependency injection and the TestBed utility make unit testing straightforward. Angular strongly encourages test-driven development.
Official Material Design component library with 50+ accessible, customizable UI components following the Material 3 spec.
Deep integration with Reactive Extensions for JavaScript enables powerful async patterns, event streams, and state management.
Angular works beautifully with Nx for monorepo architectures, enabling code sharing across multiple apps and libraries.
Angular Localize provides compile-time i18n with support for XLIFF, XMB/XTB formats and ICU expressions for pluralization.
Functional interceptors (v15+) allow transforming requests/responses globally — perfect for auth tokens, logging, and caching.
The official NgRx library brings Redux-inspired state management with Signals integration for predictable, scalable data flow.
Chrome extension for profiling Angular applications, visualizing component trees, and debugging change detection performance.
10 questions to see how well you know Angular. Click an option to answer.