Introduction
Angular is a full-featured front-end platform for building large, maintainable single-page applications. It uses TypeScript, a strict component model, and a powerful compiler to turn templates into efficient JavaScript.
Core ideas include:
- Component-driven UI: each feature is isolated with its own template, logic, and styles
- Declarative templates: bindings, directives, and pipes keep UI in sync with data
- Dependency injection: services are shared cleanly across the app
- Reactive programming: RxJS streams handle async data, events, and state
Setup & Installation
# Install Node.js & npm # Install Angular CLI globally npm install -g @angular/cli # Create a new project ng new my-app cd my-app ng serve --open
Angular CLI creates a workspace with a build system, TypeScript config, linting, testing, and environment files. It also supports code generation for components, services, and modules.
Advanced setup tips:
- Use
ng new --routing --style=scssto enable routing and SCSS at creation - Configure file replacements for environment-specific builds
- Use
ng build --configuration=productionfor optimized output
# Generate a component and service ng g c features/profile ng g s services/auth # Build with production optimizations ng build --configuration=production
Components
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: ``,
})
export class AppComponent {
title = 'Hello Angular';
}
Components encapsulate UI, behavior, and styling. The Angular compiler links templates to component classes and tracks updates using change detection.
Key component concepts:
- Inputs and outputs for parent-child communication
- Template and style encapsulation by default
- Lifecycle hooks to manage setup and teardown
@Component({
selector: 'app-card',
template: `
`
})
export class CardComponent {
@Input() title = '';
@Input() id = 0;
@Output() select = new EventEmitter();
}
Modules
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
bootstrap: [AppComponent]
})
export class AppModule {}
Modules group related functionality and control compilation scope. Feature modules keep apps organized and can be lazy‑loaded for performance.
Best practices:
- Keep shared UI in a
SharedModule(components, pipes) - Use feature modules for domains like
OrdersModule - Lazy‑load large sections with the router
@NgModule({
declarations: [ProfileComponent],
imports: [CommonModule, ProfileRoutingModule]
})
export class ProfileModule {}
Directives
// Structural <div *ngIf="isVisible">Visible</div> // Attribute <div [class.active]="isActive">Dynamic Class</div>
Directives extend HTML with custom behavior. Structural directives change layout by adding or removing elements, while attribute directives modify element appearance or behavior.
Common structural directives:
*ngIf,*ngFor,*ngSwitchng-containerfor grouping without extra DOM nodes
<ul> <li *ngFor="let item of items; trackBy: trackById"></li> </ul>
Data Binding
// Interpolation <h1></h1> // Property binding <img [src]="imageUrl"> // Event binding <button (click)="doSomething()">Click</button>
Angular supports one‑way data flow and optional two‑way binding. Templates stay in sync with component state through bindings evaluated in change detection cycles.
Advanced binding patterns:
- Two‑way binding via
[(ngModel)]or custom@Input/@Output - Property binding for attributes and DOM properties (e.g.,
[disabled]) - Event binding with arguments and
$event
<input [value]="name" (input)="name = $event.target.value"> <app-toggle [(state)]="isOn"></app-toggle>
Services & Dependency Injection
import { Injectable } from '@angular/core';
@Injectable({providedIn:'root'})
export class DataService {
getData(){ return ['A','B','C']; }
}
constructor(private dataService: DataService){}
ngOnInit(){ console.log(this.dataService.getData()); }
Services hold shared logic and state. Angular's DI container manages lifecycles and allows easy mocking in tests.
Key DI concepts:
- Singleton services via
providedIn: 'root' - Scoped providers in feature modules or components
- Injection tokens for non-class dependencies
export const API_URL = new InjectionToken('apiUrl'); @NgModule({ providers: [{ provide: API_URL, useValue: '/api' }] }) export class CoreModule {}
Routing
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent }
];
@NgModule({imports:[RouterModule.forRoot(routes)],exports:[RouterModule]})
The Angular Router maps URLs to components. It supports guards, lazy loading, parameters, and nested routes for complex layouts.
Common routing features:
- Route params with
/users/:id - Route guards for auth and permissions
- Lazy loading with
loadChildren
const routes: Routes = [
{ path: 'users/:id', component: UserComponent, canActivate: [AuthGuard] },
{ path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
];
Forms
// Template-driven
<form #myForm="ngForm">
<input name="name" [(ngModel)]="name">
</form>
// Reactive
this.form = this.fb.group({name:['']});
Template-driven forms are simple and declarative, while reactive forms scale better for complex validation, dynamic fields, and testability.
Reactive form advantages:
- Fine-grained validation and custom validators
- Explicit form state in code
- Reactive value changes via observables
this.form = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]]
});
this.form.valueChanges.subscribe(value => console.log(value));
HTTP Client
import { HttpClient } from '@angular/common/http';
this.http.get('https://api.example.com/data')
.subscribe(data => console.log(data));
HttpClient returns observables and integrates with RxJS operators. It supports typed responses, interceptors, and error handling.
Advanced usage:
- Interceptors for auth tokens, logging, or retries
- Typed responses for compile-time safety
- Centralized error handling with
catchError
this.http.get('/api/users') .pipe(catchError(err => of([]))) .subscribe(users => this.users = users);
Pipes
{ today | date:'fullDate' }
{ amount | currency:'USD' }
Pipes transform values for display. Angular provides built‑in pipes for dates, numbers, JSON, and strings, and you can create custom pipes.
Custom pipes should be pure by default for performance.
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
transform(value: string, limit = 20): string {
return value.length > limit ? value.slice(0, limit) + '...' : value;
}
}
Component Lifecycle
ngOnInit() {}
ngOnChanges(changes) {}
ngOnDestroy() {}
Lifecycle hooks let you tap into component creation, updates, and destruction. This is essential for subscriptions, timers, and DOM interactions.
Common hooks:
ngOnInitfor initializationngOnChangesto react to input changesngOnDestroyfor cleanup
private sub?: Subscription;
ngOnInit() {
this.sub = this.service.stream$.subscribe();
}
ngOnDestroy() {
this.sub?.unsubscribe();
}
Observables & RxJS
import { of } from 'rxjs';
of(1,2,3).subscribe(x => console.log(x));
Observables represent asynchronous streams that can emit multiple values over time. RxJS provides operators to transform, filter, and combine streams.
Key operators:
map,filter,switchMapdebounceTimefor input handlingcatchErrorfor graceful failures
this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.http.get(`/api/search?q=${term}`))
).subscribe(results => this.results = results);