Summary
Angular is a TypeScript-based web application framework developed by Google that uses a component-based architecture with declarative templates, dependency injection, and reactive programming patterns. This cheatsheet covers essential Angular concepts, patterns, and best practices for building scalable web applications and succeeding in technical interviews.
Angular Basics
What is Angular?
- TypeScript-based open-source framework by Google
- Component-based architecture
- Uses RxJS for reactive programming
- Built-in features: routing, forms, HTTP client, testing
Angular CLI Commands
ng new app-name # Create new project
ng serve # Run dev server
ng generate component # Generate component
ng build --configuration production # Production build
ng test # Run unit tests
ng e2e # Run e2e tests
Components
Basic Component Structure
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
title = 'Hello Angular';
}
Component Communication
Input (Parent to Child)
// Child
@Input() message: string;
// Parent template
<app-child [message]="parentMessage"></app-child>
Output (Child to Parent)
// Child
@Output() notify = new EventEmitter<string>();
onClick() { this.notify.emit('data'); }
// Parent template
<app-child (notify)="handleNotify($event)"></app-child>
ViewChild
@ViewChild(ChildComponent) child: ChildComponent;
ngAfterViewInit() {
console.log(this.child.someProperty);
}
Templates & Data Binding
Interpolation
<h1>{{ title }}</h1>
<p>{{ 'Hello ' + name }}</p>
Property Binding
<img [src]="imageUrl">
<button [disabled]="isDisabled">Click</button>
Event Binding
<button (click)="onClick()">Click me</button>
<input (keyup)="onKey($event)">
Two-way Binding
<input [(ngModel)]="name">
Template Reference Variables
<input #phone placeholder="phone">
<button (click)="callPhone(phone.value)">Call</button>
Directives
Structural Directives
ngIf
<div *ngIf="isVisible">Visible content</div>
<div *ngIf="user; else loading">{{ user.name }}</div>
<ng-template #loading>Loading...</ng-template>
ngFor
<li *ngFor="let item of items; let i = index">
{{ i }}: {{ item }}
</li>
ngSwitch
<div [ngSwitch]="value">
<p *ngSwitchCase="1">Case 1</p>
<p *ngSwitchCase="2">Case 2</p>
<p *ngSwitchDefault>Default</p>
</div>
Attribute Directives
ngClass
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}"></div>
<div [ngClass]="currentClasses"></div>
ngStyle
<div [ngStyle]="{'color': color, 'font-size': size + 'px'}"></div>
Custom Directive
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
constructor(private el: ElementRef) {}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
Services & Dependency Injection
Basic Service
@Injectable({
providedIn: 'root' // Singleton service
})
export class DataService {
getData() {
return ['data1', 'data2'];
}
}
Using Service in Component
constructor(private dataService: DataService) {}
ngOnInit() {
this.data = this.dataService.getData();
}
Injection Tokens
// Token definition
export const API_URL = new InjectionToken<string>('api.url');
// Provider
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' }
]
// Usage
constructor(@Inject(API_URL) private apiUrl: string) {}
Routing
Basic Route Configuration
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'user/:id', component: UserComponent },
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
Router Outlet & Links
<router-outlet></router-outlet>
<a routerLink="/about" routerLinkActive="active">About</a>
Programmatic Navigation
constructor(private router: Router) {}
navigate() {
this.router.navigate(['/user', userId]);
// With query params
this.router.navigate(['/search'], { queryParams: { q: 'angular' } });
}
Route Guards
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(): boolean {
return this.authService.isAuthenticated();
}
}
// Route config
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
Lazy Loading
{
path: 'feature',
loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
}
Forms
Template-Driven Forms
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
<input name="email" ngModel required email>
<span *ngIf="f.controls.email?.invalid && f.controls.email?.touched">
Email is invalid
</span>
<button [disabled]="f.invalid">Submit</button>
</form>
Reactive Forms
// Component
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]]
});
constructor(private fb: FormBuilder) {}
onSubmit() {
if (this.form.valid) {
console.log(this.form.value);
}
}
// Template
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="email">
<div *ngIf="form.get('email')?.invalid && form.get('email')?.touched">
Email is required
</div>
</form>
Custom Validator
function emailValidator(control: AbstractControl): ValidationErrors | null {
const email = control.value;
if (!email || (email.includes('@') && email.includes('.'))) {
return null;
}
return { invalidEmail: true };
}
// Usage
form = this.fb.group({
email: ['', [Validators.required, emailValidator]]
});
HTTP & Observables
Basic HTTP Requests
constructor(private http: HttpClient) {}
// GET
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
// POST
createUser(user: User): Observable<User> {
return this.http.post<User>('/api/users', user);
}
// With headers
const headers = new HttpHeaders().set('Authorization', 'Bearer token');
this.http.get('/api/data', { headers });
Error Handling
getUsers().pipe(
catchError(error => {
console.error('Error:', error);
return throwError(() => error);
})
).subscribe();
HTTP Interceptor
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${this.auth.getToken()}` }
});
return next.handle(authReq);
}
}
Lifecycle Hooks
Common Lifecycle Hooks
export class MyComponent implements OnInit, OnDestroy {
ngOnChanges(changes: SimpleChanges) { } // Input properties change
ngOnInit() { } // Component initialization
ngDoCheck() { } // Change detection runs
ngAfterContentInit() { } // Content projection init
ngAfterContentChecked() { } // After content checked
ngAfterViewInit() { } // View initialized
ngAfterViewChecked() { } // After view checked
ngOnDestroy() { } // Before destruction
}
Best Practices
- ngOnInit: Initialize data, make HTTP calls
- ngOnDestroy: Unsubscribe, clear timers
- ngOnChanges: React to input changes
- ngAfterViewInit: Access ViewChild elements
Change Detection
Change Detection Strategies
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
Manual Change Detection
constructor(private cd: ChangeDetectorRef) {}
updateData() {
this.data = newData;
this.cd.markForCheck(); // OnPush strategy
// or
this.cd.detectChanges(); // Force check
}
Pipes
Built-in Pipes
{{ date | date:'short' }}
{{ price | currency:'USD' }}
{{ name | uppercase }}
{{ text | slice:0:10 }}
{{ users | json }}
Custom Pipe
@Pipe({ name: 'exponential' })
export class ExponentialPipe implements PipeTransform {
transform(value: number, exponent = 1): number {
return Math.pow(value, exponent);
}
}
// Usage: {{ 2 | exponential:10 }}
Async Pipe
<div *ngIf="user$ | async as user">
{{ user.name }}
</div>
RxJS Operators
Common Operators
// map - Transform values
source$.pipe(
map(x => x * 2)
)
// filter - Filter values
source$.pipe(
filter(x => x > 10)
)
// tap - Side effects
source$.pipe(
tap(x => console.log(x))
)
// switchMap - Cancel previous, switch to new
search$.pipe(
switchMap(term => this.http.get(`/search?q=${term}`))
)
// mergeMap - Handle all
clicks$.pipe(
mergeMap(() => this.http.get('/api/data'))
)
// debounceTime - Delay emissions
input$.pipe(
debounceTime(300)
)
// distinctUntilChanged - Emit only unique
input$.pipe(
distinctUntilChanged()
)
// takeUntil - Unsubscribe pattern
private destroy$ = new Subject<void>();
ngOnInit() {
this.service.data$.pipe(
takeUntil(this.destroy$)
).subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
Performance Optimization
TrackBy Function
trackByFn(index: number, item: any) {
return item.id;
}
<li *ngFor="let item of items; trackBy: trackByFn">
Lazy Loading Images
<img [src]="imageUrl" loading="lazy">
OnPush Strategy
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
Preloading Strategies
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})
Bundle Size Optimization
- Tree shaking
- Lazy load modules
- Use production builds
- Remove unused imports
Testing
Unit Testing (Jasmine/Karma)
describe('Component: Example', () => {
let component: ExampleComponent;
let fixture: ComponentFixture<ExampleComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ExampleComponent]
});
fixture = TestBed.createComponent(ExampleComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render title', () => {
component.title = 'Test';
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Test');
});
});
Service Testing
it('should return data', () => {
const service = TestBed.inject(DataService);
expect(service.getData()).toEqual(['data1', 'data2']);
});
HTTP Testing
it('should fetch users', () => {
const httpMock = TestBed.inject(HttpTestingController);
service.getUsers().subscribe(users => {
expect(users.length).toBe(2);
});
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush([{id: 1}, {id: 2}]);
});
Advanced Concepts
Content Projection
<!-- Parent -->
<app-card>
<h1 card-title>Title</h1>
<p card-content>Content</p>
</app-card>
<!-- Child template -->
<ng-content select="[card-title]"></ng-content>
<ng-content select="[card-content]"></ng-content>
Dynamic Components
constructor(private viewContainerRef: ViewContainerRef) {}
loadComponent() {
// Modern approach (Angular 13+)
const componentRef = this.viewContainerRef.createComponent(DynamicComponent);
componentRef.instance.data = 'Dynamic Data';
// Legacy approach (pre-Angular 13)
// const factory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
// const componentRef = this.viewContainerRef.createComponent(factory);
}
HostBinding & HostListener
@HostBinding('class.active') isActive = false;
@HostListener('click') onClick() {
this.isActive = !this.isActive;
}
Renderer2 (Safe DOM Manipulation)
constructor(private renderer: Renderer2, private el: ElementRef) {}
changeColor() {
this.renderer.setStyle(this.el.nativeElement, 'color', 'red');
}
NgZone
constructor(private ngZone: NgZone) {}
runOutsideAngular() {
this.ngZone.runOutsideAngular(() => {
// Heavy computation that doesn't need change detection
});
}
Angular Elements
const element = createCustomElement(ButtonComponent, { injector });
customElements.define('custom-button', element);
Standalone Components (Angular 14+)
@Component({
selector: 'app-standalone',
standalone: true,
imports: [CommonModule, FormsModule],
template: '<p>Standalone component</p>'
})
export class StandaloneComponent { }
// Bootstrap
bootstrapApplication(StandaloneComponent, {
providers: [
importProvidersFrom(HttpClientModule)
]
});
Signals (Angular 16+)
// Signal creation
name = signal('John');
count = signal(0);
// Computed signals
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
// Effects
effect(() => {
console.log(`Count is: ${this.count()}`);
});
// Update signals
updateName() {
this.name.set('Jane');
this.count.update(val => val + 1);
}
Key Interview Concepts
Angular Architecture
- Framework vs Library: Angular is a full framework providing structure, routing, HTTP client, testing utilities
- MVC Pattern: Model-View-Controller architecture with components as controllers
- SPA: Single Page Application with client-side routing
- TypeScript: Strongly typed superset of JavaScript with compile-time error checking
Data Binding Types
- Interpolation:
{{ value }}
- One-way, component to template - Property Binding:
[property]="value"
- One-way, component to template - Event Binding:
(event)="handler()"
- One-way, template to component - Two-way Binding:
[(ngModel)]="value"
- Bidirectional data flow
Change Detection Strategy
- Default: Checks all components on any event
- OnPush: Only checks when inputs change or events fire
- Manual: Use ChangeDetectorRef for fine-grained control
- Immutable Objects: Required for OnPush to work effectively
Dependency Injection Hierarchy
- Root Level:
providedIn: 'root'
- Singleton across app - Module Level: Providers array in module
- Component Level: Providers in component metadata
- Injection Tokens: For non-class dependencies
Observable vs Promise
- Single vs Multiple: Promise handles one value, Observable handles streams
- Cancellation: Observables can be unsubscribed, Promises cannot
- Operators: RxJS operators for transformation, filtering, combination
- Lazy: Observables are lazy (cold) until subscribed
Security Considerations
- Sanitization: Angular sanitizes values automatically in templates
- Trusted Values: Use DomSanitizer for trusted HTML/URLs
- CSRF Protection: Use HttpClientXsrfModule
- Content Security Policy: Implement CSP headers
Modern Angular Features
- Standalone Components: Simplified bootstrapping without NgModules (Angular 14+)
- Signals: Reactive primitives for fine-grained reactivity (Angular 16+)
- Control Flow: New @if, @for, @switch syntax (Angular 17+)
- SSR & Hydration: Improved server-side rendering with client hydration
Development Best Practices
Component Design
- Single responsibility principle
- Input/Output for communication
- Smart (container) vs Dumb (presentational) components
- Minimal template logic
State Management
- Services for shared state
- BehaviorSubject for reactive state
- NgRx for complex application state
- Immutable state updates
Performance Optimization
- OnPush change detection strategy
- TrackBy functions in ngFor
- Lazy loading modules and components
- Tree shaking and dead code elimination
- Virtual scrolling for large lists
- Image lazy loading
- Preloading strategies
Code Organization
- Feature modules for related functionality
- Shared modules for common components
- Core module for singleton services
- Barrel exports for clean imports
Testing Strategy
- Unit tests for components and services
- Integration tests for component interactions
- E2E tests for critical user flows
- Test-driven development approach