Angular CheatSheet
Angular CheatSheet
Angualr Forms
- Template- Driven forms
- Reactive Forms
Template- Driven forms
Key Features
- Use
FormModuleandngModeldirective - Two-way data binding with [(ngModel)]
- Validation attribute in html (required, minlength)
- Form state traking :
touched,valid,invalid,dirty
Define in template
<form #contactForm="ngForm" (ngSubmit)="OnSubmit(contactForm)">
<input name="name" [(ngModel)]="name" required>
<!-- other field and error we can defiene -->
<button type="submit">Submit</button>
</form>
Usage
- Simple forms with basic validation, Quick response, Forms that mirror data directly, minimal component logic needed
Reactive Forms
Key Features
- Uses
ReactiveFormsModule,FormGroup,FormControl - Programmatic control and validation
- Custom validation:
noSpaceValidatorandabusiveWorldValidator - Better testability and scalability
Example
Template
<form [formGroup]="login" (ngSubmit)="onSubmit()">
<input id="username" formControlName="username">
@if(username.invalid && username.touched){
@if(username.errors?.['required']){
Username is required
}
@if(username.error?.['usernameTaken']){
Username already taken
}
@if(username.hasError('noSpace')){
Name cannot contain space!
}
}
<!-- other fields -->
</form>
Use:
- Complex forms with dynamic validation
- cross-field validation
- Better control over from state
- Easier unit testing
Form Arrays
FormArray is used to manage a dynamic collection of form controls.
Key Features
- Dynamic form controls (add/ remove skills)
- Uses
FormArrayfor collection - Allow user to add/remove filed dynamically Implementation stapes
- in ts file
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, FromControl FormArray, Validators } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent implements OnInit {
availableSkills = ['C#', 'Jave', 'Angular'];
empFrom = new FormGroup({
name: new FormControl('')
skills: new FormArray([new FormControl('')])
});
// Using FormBuilder
// empForm = this.fb.group({
// name: ['', Validators.required],
// skills: this.fb.array([
// this.fb.control('')
// ])
// });
get skills(){
return this.empForm.get('skills') as FormArray;
// return (<FormArray>this.empForm.get('skills')) as FormArray
}
addSkill(){
this.skills.push(new FormCntrol(''));
// this.skills.push(this.fb.control(''));
}
removeSkill(index: number){
this.skills.RemoveAt(index);
}
onSubmit(){
console.log(this.empForm.value);
}
}
- Bind
FormArrayto template
<form [formGroup]="empForm" (ngSubmit)="onSubmit()">
<input name="name" formControlName="name">
<!-- other form control -->
<div formArrayName="skills">
<button type="button" (click)="AddSkill()">Add Skill</button>
@for(skill of skills.controls; track $index){
<input list ="skillsList" [formControlName]="$index">
<button (click)="removeSkill($index)">Remove Skill</button>
}
<!-- data list for skills -->
<datalist id="skillsList">
@for(skill of availableSkills; track skill){
<option [value]="skill">{{skill}}</option>
}
</datalist>
</div>
</form>
Validators
- Built-in Validators
- Custom Validators
- Async Validators
Built-in Validators
Angular provides a robust set of built-in validators for both Reactive and Template-Driven forms. These are accessible via the Validators class in the @angular/forms package
| Validator | Description | Reactive Syntax | Template Attribute |
|---|---|---|---|
| Required | Field cannot be empty | Validators.required | required |
| Must match email format | Validators.email | email | |
| Min Length | Minimum character count | Validators.minLength(5) | minlength="5" |
| Max Length | Maximum character count | Validators.maxLength(10) | maxlength="10" |
| Pattern | Match Regex | Validators.pattern('^[a-z]+$') | pattern="^[a-z]+$" |
| Min/Max | Numeric range | Validators.min(18) | min="18" |
Custom Validators
a custom validator is a function that you define to implement specific validation logic not covered by the built-in validators.
note: we can use custom validators in both tmplate-driven and reactive form as well.
Custom validators for reactive forms
Implementation stapes:
- Create the Validator Function
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function abusivewordValidator(control : AbstractControl): ValicationError | null {
const abusivewords = ['idot', 'dumb','fool'];
const controlValue = (control.value as string).toLowerCase();
const containAbusivewords = abusivewords.some(word => controlValue.include(word)
);
return containAbusivewords ? { abusiveword: true }: null;
}
- Add to a Form Control
empFrom = new FormGroup({
name: new FormControl('', Validators.required,abusivewordValidator )
skills: new FormArray([new FormControl('')])
});
// Using FormBuilder
// empForm = this.fb.group({
// name:['', [Validators.required, abusivewordValidator]],
// skills: this.fb.array([
// this.fb.control('')
// ])
// });
Custom validators for Template driven forms
- Create the Validator Directive
ng generate directive shared/abusive-word-validator
- Write Validator function in directive
import { Directive, Input } from '@angular/core';
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn, Validators } from '@angular/forms';
@Directive({
selector: '[appAbusiveWordValidator]',
providers: [{ provide: NG_VALIDATORS, useExisting: AbusiveWordValidatorDirective, multi: true }]
})
export class AbusiveWordValidatorDirective implements Validator {
// Input property to receive the list of abusive words from the template
const abusivewords = ['idot', 'dumb','fool'];
const controlValue = (control.value as string).toLowerCase();
const containAbusivewords = abusivewords.some(word => controlValue.include(word)
);
return containAbusivewords ? { abusiveword: true }: null;
}
- Add the Directive to the Template
<input name="name" [(ngModel)]="name" appAbusiveWordValidator required>
Async Validators
Async validators in Angular are used for validation that requires an asynchronous operation. Async Validators return Promise or Observable and used for server-side validation:
Key Charaterstics:
- Return
Observable<ValidationErrors | null>orPromise<ValidationErrors | null> - Execute AFTER all sync validators pass
- Provide a
pendingstate during validation - Run on value changes with debouncing support
**NOTE: ** - Promises: Single value, not cancellable, simplar for one-time checks - Observable: Multipale value, cancellable(important for preventing race conditions), better for reactive programing with RxJS operators (recommendation).
// Custom async Validators
function userNameAvailable(userService: UserService): AsyncValidatorFn{
return (control: AbstractControl): Observable<ValidationError | null > => {
return userService.checkUserName(control.value).pipe(
map(available => available ? null : {usernameTaken: true}),
catchError(() => of(null))
);
};
}
//Usage
this.form = new FormGroup({
username: new FormControl({
'',
[Validators.required], // sync validators
[usernameAvailableValidator(this.userService)] // Async Validators
})
});
When to use:
- Check if username/email already exists
- Validate data against database
- External API validation (Credit Card verification, Address Verification)
- Any validation required server call (like: Promo code verification, Phonenumber varification).
When NOT to Use:
- Simple formate validation
- Regex pattern (use
Validators.pattern()) - Min/Max length check (use sync validators)
- Required filed validation
Important: Async validators run AFTER sync validators pass.
Note:
- Performance: By default, async validators run on every value change. To avoid excessive API calls (e.g., on every keystroke), you can set the updateOn property to 'blur' or 'submit', or use RxJS operators like
debounceTime(ms),distinctUntillChanged(), switchMap()` check empty value before making API call. UndateOn: 'blur' or 'submit': Validate only when user leaves the field or submit form.username: new FormControl('', { validators: [Validaotrs.required, Valifators.minLength(3)], asyncValidators: [usernameAsyncValidators()], updateOn: 'blur' // Only validate when user leaves the field or // updateOn: 'submit' // Only validate on form submission }) - pending State: While an async validation is in progress, the form control enters a PENDING status. This state can be used in the template to show a loading indicator (e.g., a spinner).
- Signature: An async validator function (implementing the AsyncValidatorFn interface) takes an AbstractControl instance as a parameter and returns an
Observable<ValidationErrors | null>orPromise<ValidationErrors | null>.
Angular Directives
Directive: are maker on a DOM element (such as an attribute, element name, comment, or CSS class) that tell Angular's HTML compiler to attach a spescified behaviour to that element or even transform the DOM element and its children.
Key Charaterstics:
- Extend HTML with new functionally
- Add or modify DOM element behavior
- Can add style, event listners, and attributes
- Reusable across components
- Three types Component, Structural, and Attribute
Classification:
Directives
|- Components (special directives with templates)
|- Structural Directive (modify DOM structure)
|- *ngIf
|- *ngFor
|- *ngSwitch
|-Custom structural directives
|- Attribute Directive (change element behaviour/appearance)
|- Built-in: ngClass, ngStyle, ngModel
|- Custom attribute directive
Type of Directives
Component Directive
A component directive is spacial kind with a trmplate, styling, a component logics.
@Component({
selector: 'app-user',
template: `<div>{{name}}</div>`,
styles: ['div {color: blue}']
})
export class UserComponent {
@Input() name: String;
}
Structural Directives
Modify the DOM structure by adding or removing elements.
<div *ngIf="isVisable">Visible</div>
<!-- ng-template use -->
<div *ngIf="isVisible; thenblock else elseBlock"></div>
<ng-template #thenBolck>Then Block</ng-template>
<ng-template #elseBolck>Else Block</ng-template>
<!-- ngfor -->
<!-- basic -->
<div *ngFor="let item of items">{{item}}</div>
<!-- with index -->
<div *ngfor =" let item of items; let i=index">
<!-- with first, last, odd, even, count -->
<div *ngFor="let item of items; let first=fisrt; let last = last; let odd = odd; let even = even; let count = count ">
<!-- Track by performance -->
<div *ngfor =" let item of items; trackBy: tracByFn">
<!-- for trackBy implement in component
// Without trackBy : Re-renders all items when list changes
// With trackBy: Only re-enders changed items
trackByFn(index: number, item: User): number {
return item.id; // Returns unique identifier
}
// Alternative: Using aeeow function
trackByFn = (index: number, item: User) => item.id;
-->
<!-- Angular 15+ :new Syntex -->
<!-- basic @for -->
@for(item of items track; item.id) { }
<!-- index @for -->
@for(item of items track; let i=$index; track item.id) { }
<!-- odd, even @for -->
@for(item of items track; let first=$first; let last=$last track user.id) { }
<!-- Empaty state with @Empty -->
@for(item of items track; item.id) {
} @Empty{
<p>No item is available</p>
}
<!-- ngSwitch -->
<div [ngSwitch]="status">
<div *ngSwitchCase="'active'">
<div *ngSwitchDefault>
<!-- ngTemplateOutlate -> Randers a template dynamically-->
<ng-container *ngTemplateOutlate="haderTemplate"></ng-container>
<ng-container *ngTemplateOutlate="contentTemplate"></ng-container>
<ng-template #haderTemplate>Hraders Template</ng-template>
<ng-template #contentTemplate>Content Template</ng-template>
Note: Benefits of TrackBy:
- Improve perfomance with large lists
- Prevents re-rendering unchange items
- Maintains forms state and focus
Attribute Directives
Modify the appearance or behavior of DOM elements without changing structure.
<div [ngClass]="{ active: isActive }">
<div [ngStyle]="{color: dynamicColor}">
<input [(ngModel)]="name">
difference in ngClass and class Binding
| ngClass | class Binding |
|---|---|
| Multiple class at once | Single class binding |
| Conditional class assignment | Direct property binding |
| Can accept object, string, or array | Simple syntex |
When to use
ngClass: Multiple condition classesClass binding: Single class or static classes
Custom Directives
Creating Custom Attribute Directives
Key Concept:
- Implement @Directive decorator
- Use @Input for properties
- Use ElementRef to access element
- Use Randerer2 for safe DOM manipulation
Step 1: Generate directives
ng generate directive directives/highlight
#or
ng g d directives/highlight
Step 2: Implemetnt Custom directive
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]', // Attribute selector: use as <p appHighlight>
standalone: true // Directives are standalone by default in modern Angular
})
export class HighlightDirective {
// Define an input property to allow specifying a custom highlight color
@Input() highlightColor: string = 'yellow';
// Inject ElementRef to access the host element
constructor(private el: ElementRef) {}
// Listen for the mouseenter event on the host element
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor);
}
// Listen for the mouseleave event on the host element
@HostListener('mouseleave') onMouseLeave() {
this.highlight(''); // Clear the highlight
}
// Private helper method to apply the style
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
Step 3: Use the Directive in a Component
// In Component
imports: [HighlightDirective], // Add the directive to the imports array
// In Template
<!-- Use the directive with the default yellow color -->
<p appHighlight>Hover over me for default highlight!</p>
<!-- Use the directive and bind an input property for a custom color -->
<p [appHighlight]="favoriteColor">Hover over me for lightblue highlight!</p>
Component/Directive Lifecycle Hook
| Hook | Directive | Component | Purpuse |
|---|---|---|---|
| constructor | ✔ | ✔ | Initialize dependencies |
| ngOnChange | ✔ | ✔ | Detect input change |
| ngOnInit | ✔ | ✔ | Initialize component/directive |
| ngDoCheck | ✔ | ✔ | Custome change detection |
| ngAfterContentInit | ✔ | ✔ | After content initialization |
| ngAfterViewInit | X | ✔ | After view initilazition |
| ngOnDestroy | ✔ | ✔ | Cleanup |
Directive Don't have:
ngAfterViewInit- Directives have no viewngAfterViewChecked- Directives have no view
Angular Pipes
Pipe are simple functions that accept an input value and return a transformed output value.
Built-in PipesCustom PipeAsync Pipe
Key Charaterstics:
- Transform data in templates without changing the original data
- Can be chained together
- Can accept parameters
- Can be pure or impure
- Reusable across the application
Built-in Pipes
Angualr provides several built-in pipes for common transformation:
- DatePipe
- CurrencyPipe
- NumberPipe
- PercentagePipe
- Uppercase/LowerCase/TitleCasePipe
- SlicePipe
- JsonPipe
Custom Pipe
Step 1: Generate Pipe
ng generate pipe pipes/custom-pipe-name
#or
ng g p pipes/custom-pipe-name
Step 2: Implement Pipe logic
import {Pipe, PipeTrasform } from '@angular/core';
@pipe({
name: 'reverse',
standalone: true
})
export class ReversePipe implements Pipetransform {
transform(value: string): string {
if(!value) return value;
return value.split('').reverse().join('');
}
}
Usage
{{ 'Angular' | reverse }}
Pure vs Impure Pipe
PurePipe
Charateristics:
- Called only when Angular detects a pure change to the input value
- Pure changes: primitive value (String, Number, boolean) or object reference
- More performant as they're colled less frequently
- Default behaviour (pure: true)
Example
@pipe({
name: 'purePipe',
pure: true // Default
})
export class AppComponent{
items = ['Apple', 'Banana', 'Cherry'];
addItem(){
// This won't trigger pure pipe (same reference)
this.items.push('Date');
//This will trigger pure pipe (new refrence)
this.items = [...this.item, 'date'];
}
}
Impure Pipes
Charateristics:
- Called on every change detection cycle
- Detects change within objects or array
- Less performent but more responsive to data change
- Must explicitly set pure: false
@pipe({
name: 'purePipe',
pure: false // Impure pipe
})
export class ImpurePipe implements PipeTrasform{
transform(item: any[], searchText: string): any[]{
if(!items || !searchtext) return items;
return items.filter(item =>
item.toLowerCase().includes(searchText.toLowerCase())
);
}
}
When to Use Impure Pipe
- Filtering/ Sorting dynamic array
- Real-time dat transformation
- When we need to detech changes within object/arrays
Async Pipe
This Async Pipe is a spacial built-in pipe that subscribes to Observables or Promises and return the letest value.
Key Benifits
- Automatic subscription Management: Automattically scubscribe and Unsubscribe
- Memory Leak Preventation: Prevent memory leaks
- Cleaner Code: No need for manual subscription in component
Observable Example:
// Component
//Observable that emit every second
time$: Observable<Date> = interval(1000).pipe(
map(()=> new Date())
);
//Template
// <h3> Current Time: {{time$ | async | date: 'medium'}}
//Observable form Http call
user$: Observable<User> = this.http.get<User>('/api/user');
constructor(private http: HttpClient ){}
//Template
// <h3> USer: {{user$ | async | json }}
Promise Example
// Component
dataPromise: Promise<string>;
constructor(){
this.dataPromise = this.fatchData();
}
fatchData(): Promise<string>{
return new Promise((resolve)=> {
setTimeout(()=> resolve('Data loaded!'), 2000);
});
}
// Template
<div>{{dtaPromise | async }} </div>
Pipe Life Cycle
- Construction
- Transform method calls
- Destruction
OnPush OnPush change detection strategy is a performance optimization that instructs Angular to skip checking a component (and its children) unless specific triggers occur.
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-on-push-demo',
templateUrl: './on-push-demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent { }
Comunication Method in Angular
Type of Communication
| Type | Direction | Use Case |
|---|---|---|
| @Input | Parent -> Child | Pass sata down the component tree |
| @Output | Child -> Parent | Emit events up the component tree |
| Service | Any -> Any | Shared data accross components |
| ViewChild | Parent -> Child Template | Direct DOM/Component access |
| Template Ref Variables | Template | Local template access |
| Subject | Any <-> Any | Complex state management |
@Input - Passing data ot Child component (one way data binding)
// Child component ts
import { Component, Input } from '@angular/core';
@Input() message: string ='';
// Input with alias
@Input('message') data: any;
// Required input (Angular 16+)
@Input({ required: true }) title: string;
// parent component ts
ParentMessage =' Hello from Parent';
<app-child [message]="ParentMessage"></app-child>
@Output - Emitting Events from Child t Parent.
//child component ts
import { Component, Output, EventEmitter} from '@angular/core';
@Output() messageEvent = new EventEmitter<string>();
sentMesage(){
this.messageEvent.emit('Hello from child!');
}
//Parent component
receivedMessage (message: string){
console.log(message);
}
<app-child (messageEvent)="receivedMessage($event)">
ViewChild/ViewChildren/ContentChild/ContentChildren
These decorater allow perent component to access child component instances or DOM elements directly.
ViewChild - Single Child Reference
// parent component ts
//-------------------------------
import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child-component';
@ViewChild('ChildComponent') child!: ChildComponent;
// OR by template reference variable:
//@ViewChild('ChildRef') childRef!: ChildComponent;
ngAfterViewInit(){
//child is now available
console.log(this.child.message); // output: Hello from the child!
}
callChildMethod() {
this.child.updateMessage('Message updated by parent!');
}
// parent template
//---------------------
<app-child></app-child>
// or
//<app-child #ChildRef></app-child>
// Child Component ts
//--------------------------------
message = 'Hello from the child!';
public updateMessage(newMessage: string) {
this.message = newMessage;
}
Key Points:
@ViewChildis available afterngAfterViewInit()- Use for direct child components in the template.
- Single reference only.
ViewChildren - Multiple Child References
// Parent Template html
<app-child *ngFor="let item of items" #children></app-child>
// Parent component ts
import { AfterViewInit, Component, QueryList, ViewChildren } from '@angular/core';
import { ChildComponent } from './child.component';
@ViewChildren('children') children!: QueryList<ChildComponent>;
ngAfterViewInit() {
// Access and interact with each child component
this.children.forEach(child => {
console.log(child); // Access properties or call methods
// child.someMethod();
});
// Subscribe to changes in the list of children
this.children.changes.subscribe((newList: QueryList<ChildComponent>) => {
console.log('Children list updated:', newList.toArray());
});
}
ContentChild - Access Projected component
Used to access child component/elements that are projected via <ng-content>.
@ContentChild (or its signal-based equivalent, contentChild()) is a decorator used to access the first matching element, component, or directive that has been projected into a component using the
//1. Parent Component- The parent component provides the content to be projected.
// app.component.html
//------------------------------------
<app-container>
<app-header><app-header>
</app-container>
// or
//<app-container #headerRef>
// <app-header><app-header>
// </app-container>
//2. Child Component (Container) - The child component uses <ng-content> to display the projected content and @ContentChild to access it in its TypeScript class.
// container.component.ts
//-------------------------------------
import { AfterContentInit, ContentChild, ElementRef, Component } from '@angular/core';
@Component({
selector: 'app-container',
template: `
<div class="container">
<ng-content></ng-content> <!-- Projection slot -->
</div>
`,
})
export class ContainerComponent implements AfterContentInit {
@ContentChild('HeaderComponent') header!: HeaderComponent;
// Query for the element with the template reference variable '#headerRef'
// @ContentChild('headerRef') projectedHeaderEl!: ElementRef;
ngAfterContentInit() {
// The projected content is available here
if (this.projectedHeaderEl) {
this.heaader.highlight();
//or
// console.log('Projected content text:', this.projectedHeaderEl.nativeElement.textContent);
// You can also manipulate the DOM here, e.g., change style
// this.projectedHeaderEl.nativeElement.style.color = 'blue';
}
}
}
ContentChildren - Multiple Projected items
import { Component, ContentChildren, QueryList} from '@angular/core';
import { TabComponent } from './tab.component';
@Component({
selector: 'app-tab-group',
template: `
<div class="tab">
<ng-content></ng-content>
</div>
`
})
export class TabGroupComponent implements AfterContentInit {
@ContentChildren(TabComponent) tabs!: QueryList<TabComponent>;
AfterContentInit(){
this.tab.forEach((tab, index) => {
tab.id = index;
});
}
}
Comparison Table
| Decorator | Target | When Available | Usage |
|---|---|---|---|
| ViewChild | Direct child in Template | AfterViewInit | Single refrence |
| ViewChildren | Multiple direct children | AfterViewInit | QueryList |
| ContentChild | Projected content child | AfterContentInit | Single reference |
| ContentChildren | Multiple projected children | AfterContentInit | QueryList |
Template Reference variables
Template reference variables allow to refrence DOM elements or components in the template.
//Basic usage
//-----------------------
//app component html
<input #nameInput type="text">
<button (click)="onSubmit(nameInput.value)">Submit</button>
// app component ts
onSubmit(value: string){
console.log(value);
}
//Getting Dom Element Reference
//-----------------------
//app component html
<input #nameElement type="text">
<button (click)="focusInput()">Focus Input</button>
// app component ts
import { ViewChild, ElementRef } from '@angular/core';
@ViewChild('nameElement') inputElement! = ElementRef<HTMLInputElement>;
focusInput() {
this.inputElement.nevigateElement.focus();
}
// Template ref with ng-template
//-------------------------------------
//app component html
<div *ngIf="!isloading; else loadingTemplate">
<ng-template #loadingTemplate>
<p>Loading....</p>
</ng-template>
Event Emitter vs Subjects
Both are used for event-driven communication, but they have different use cases and characteristics.
EventEmitter
EventEmitter is a subclass of subject specifically designed for component outputs.
Charaterstics:
- Part of
@angular/core - Meant for
@Output()decoretors - Synchronous by default
- Limited API comared to Subject
Subject
Subject is RxJS class that act as bot an observable and observer.
Type
Subject:- No Initial valueBehaviourSubject:- Has initial valueReplaySubject:- Buffer ValuesAsyncSubject:- Emits value on Complate
| Feature | EventEmitter | Subject |
|---|---|---|
| Use case | @Output decorator | Service-based communication |
| Memory Effcient | Yes | Depends on type |
| Complate on unsubscribe | yes | No |
| Can Complate | yes | yes |
| RxJs operator | Limited | Full RxJS Suport |
| Asnyc capability | No | Yes(with async operation) |
Angular Dependency Injection
DI is a design pattern where a class receives its dependencies from external source rather then creating them itself. Angular's DI framework provides dependencies to class upon instantiation.
- Makes code more testable ( can inject mocks)
- Promotes loose coupling
- Enables singleton pattern for service
- Simplify component code
- Makes dependencies explicit and manageable.
Benifits:
- Testability: Easy to mock demendencies in unit tests
- Maintainability: Loose coupling b/w components
- Reusability: Service can be shared accross the application.
- Flexiblility: Easy to swap implementations
DI is differ from manual dependency injection as below:
- Automatic: framework handles instantiation and injection
- Hierarchical: Multi-level injector tree
- Declarative: Use decorators (@Injectable, Inject)
- Optimized: Tree-shakeable and laz-loadable
- Type-safe: Leverages Typescript types as tokens
Manual DI requires explicit instatiation and passing of dependencies.
Hierarchical Injector System
a core part of its Dependency Injection (DI) mechanism, creating a tree of injectors that mirrors the application's component tree.
Tree Structure: Every component instance can have its own injector, which is a child of its parent component's injector
Injector Hierarchy levels
NullInjector (top) -> throws error if token not found
⇩
PlatformInjector -> Platform level dependencies
⇩
RootInjector (Application/ root) -> Singletons Application wise
⇩
ModuleInjector (lazy-loaded modules) -> Lazy loaded module scope
⇩
ElementInjector (Component/Directive)
⇩
ElementInjector (Child component)
Provider Scopes
1. Root Scope
Singleton accross entire application
@Injectable({ provideIn: 'root' })
charaterstics:
- Tree-shakable ( removed if not used)
- Lazy instantiation (created when first inject)
- Single instance app-wide
when use
- Service manages global state (AuthService, ConfigService)
- Service should be singleton (HttpClient, Logger) -Service used accross multiple features
- want tree-shaking benfits
2. Platform Scope
Shared accross multiple Angular application on the same page
@Injectable({ provideIn: 'platform' })
Use case: Rare, typically for microfrontends.
3. Module Scope
Singleton within module
//Eager-loaded module
@NgModule({
provides: [ModuleService]
})
// or using priovdeIn
@Injectable({ provideIn: 'FeatureMudle' })
4. Component Scope
New instance per component
@Component({
....
providers: [ComponentService] // New instance
})
constructor(private service : ComponentService){}
When use
- Service manages component specific state
- Need different instance for different component
- Service lifecycle tide to omponent
- want isolation ( FormService for eatch form)
Example:-
- Root: AuthService, HttpClient, GlobalErrorHandler
- Component: FormService, LocalFilterService, WidgetStateService
@Injectable() Decorator Options
Basic Syntax
@Injectable({
provideIn?:Type<any>|'root'|'platform'|'any'| null,
useClass?:Type<any>,
useFactory?:Functon,
useExisting?:any,
deps?:any[]
})
export class myService {}
Options Explained
- provideIn : Tree-shakeable for scope: root (service removed if not used)
- useClass : Class Provider Provides a class to instantiate
- Angular creates instance
- Can use DI in class constructor
- E.g: { provide: Logger, useClass: ConsoleLogger }
- useFactory- Provide a factory function
- Custom intantiation logic
- Can have conditional logic
- Can inject dependencies via deps array
- Exapmle: useFactory: (http)=> new DataService(http), deps:[HttpClient]
- useValue - provide a pre-created value
- No instantiation needed
- For constants, configuration, or existing objects -E.g: {provide: 'API_URL', useValue:'https://api.com' }
- useExisting: Creates analias to existing provider
- Points to another token
- Same instance as original
- Example {provide: OldService, useExisting: NewService }
Injection Token?
Cannot use interfaces or primitives as DI tokens directly. InjectionToken creates a unique token for non-class dependencies.
in other word In Angular, an InjectionToken is a way to create a unique, Type-safe key for dependency injection - especially when you want to inject something that doesn't have a runtime type (like an interface, a primitive value, or a configuration object).
Why use
- Interfaces in TypeScript are erased at runtime, so Angular can't use them as DI tokens.
- String tokens can cause naming collisions.
- InjectionToken ensures uniqueness and Type safety.
import { InjectionToken } from '@angular/core';
//Define token
export const API_URL = new InjectionToken<string>('API_URL');
//provide value
providers: [
{provide: API_URL, useValue: 'https://api.example.com'}
]
// Inject
constructor(@Inject(API_URL) private apiUrl: string){
console.log(this.apiUrl);
}
Angular Error Handling
Error Handling is process of catching, managing and responding to errors that occur during application execution.
Types of error Handling
- Client-side errors- JavaScript errors, null references, undefined variables
- Server-side errors - HTTP errors (4xx, 5xx status code)
- Network error - Connection timeouts, network unavialability
- Application-error - Business logic errors, validation errors
Global Error Handling
Angullar provides a built-in ErrorHandler class that catches all errors in the application.
Creating a Custom Global Error class
import{ ErrorHandler, Injectable, Injector } from '@angular/core';
import{ HttpErrorResponse } from '@angular/common/http';
import{ Router} from '@angular/router';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector){}
handleError(error: Error |HttpErrorResponse): void {
const route = injector.get('Router');
if(error instanceof HttpErrorResponse){
// Server side or network error
if(!navigator.onLine){
console.error('No Internet connection');
}else{
// HTTP error
console.error(`Http Error': ${error.status}`, error.message);
this.handleServerError(error);
}
} else {
// Client error
console.error('Client error:', error);
this.handleClientError(error);
}
// Log error to minitoring service
this.logError(error);
}
private handleClientError( error: Error): void {
console.error('Client error:', error.massage);
console.error('Error stack:', error.stack);
}
private handleServerError( error: HttpResponse): void{
console.error('Client error:', {
status: error.status.
message: error.message
url: error.url
});
}
private logError(error: Error | HttpErrorResponse): void {
// Send error to logging service
}
}
Registering the Global Error Handler
// app.config.ts in providers
providers: [
{provide: ErrorHandler, useClass: GlobalErrorHandler}
]
// or in app.module.ts
providers: [
{provide: ErrorHandler, useClass: GlobalErrorHandler}
]
Angular Router
Angular Router is a powerful navigation library that enables navigation from one view to another based on URL changes. It interprets browser URLs as instructions to navigate to a client-generated view.
Routing & Navigation (Summary)
- Purpose: Client-side navigation, mapping URLs to views, and enabling lazy-loaded features.
- Core APIs:
Router,ActivatedRoute,routerLink,router.navigate. - Key concepts: route params (snapshot vs observable), query params, child routes, lazy loading, route guards (
canActivate,canLoad,canDeactivate), resolvers, and navigation extras (queryParamsHandling,replaceUrl). - Patterns & best practices: prefer route param observables when params can change, use resolvers to fetch data before activation, limit eager-loading of large feature modules, and use preloading strategies for UX.
RxJS (Key Concepts Summary)
- Core building blocks:
Observable,Observer,Subscription. - Subjects:
Subject,BehaviorSubject(state + initial value),ReplaySubject(buffer),AsyncSubject(emit last on complete). - Common operators:
pipe,map,filter,tap,switchMap,mergeMap,concatMap,debounceTime,distinctUntilChanged,catchError,retry,shareReplay. - Patterns: use
BehaviorSubjectfor shared state,switchMapfor latest-only async work,takeUntilfor unsubscribe patterns, andshareReplayfor caching API streams. - Best practices: prefer cancellable
Observableflows overPromisefor repeated or long-lived async work and always manage subscriptions (useasyncpipe ortakeUntil).
HttpClient (Essentials)
- Setup:
provideHttpClient()(standalone) or importHttpClientModulein NgModule. - Features: typed responses (
get<T>()), RxJSObservable-based API, interceptors, progress events, and automatic JSON parsing. - Common options:
HttpHeaders,HttpParams,responseType(json|text|blob),observe(body|response|events). - Methods:
get,post,put,patch,delete,head,options. - Error handling & patterns: handle
HttpErrorResponsewithcatchError, useretry/retryWhenfor transient errors, cancel requests via unsubscribe ortakeUntil, and useobserve: 'response'when you need headers/status.
HTTP Interceptors (Overview)
- Role: Global middleware for HTTP requests/responses — add auth headers, log traffic, centralize error handling, show/hide loading indicators, cache responses, and retry failures.
- Implement: implement
HttpInterceptoror useHttpInterceptorFnand theintercept(req, next)function; clone requests before modifying (req.clone(...)). - Common operator usage:
tapfor logging,catchErrorfor centralized error handling,finalizeto hide loading UI,retry/retryWhenfor retries, andof()to return cachedHttpResponse`. - Registration: register via
provideHttpClient(withInterceptors(...))(standalone) or add interceptor providers (multi: true) in module providers; interceptors run in the order provided.
Comments
Post a Comment