Top 100 Angular Interview Questions and Answers for Developers
Interview preparation · Technical guide
Angular Interview Questions and Answers
Angular concepts, application patterns, and interview examples. Older NgModule, Zone.js, and Jasmine examples are identified as version-specific; current guidance is included where behavior has changed.
Examples are independent teaching snippets and may require application types, imports, packages, schema, and configuration. Framework behavior is version-dependent. Corrections address identified issues; the complete source code collection has not been compiled or integration-tested.
1. Explain the differences between AngularJS and Angular 2+
AngularJS is the older 1.x framework built around scopes, controllers, and a digest cycle. Angular is a separate, rewritten framework built around components, TypeScript, dependency injection, and compiled templates. Angular supports both one-way bindings and explicit two-way bindings. Mobile frameworks such as Ionic can use Angular, but native mobile support is not an intrinsic distinction between the two frameworks. Migration is a redesign, not a drop-in version upgrade.
2. What is the difference between Angular and React?
Angular provides an integrated application framework with templates, dependency injection, forms, HTTP, and routing. React focuses on composing UI with components; it includes local state and context, while application frameworks and libraries supply other concerns. Both work with TypeScript. Learning effort, bundle size, and runtime performance depend on the application and build configuration; neither is universally faster or easier.
3. Explain Angular's component-based architecture
Angular's component-based architecture is built around reusable, self-contained units that encapsulate logic, template, and styling.
Core Concepts:
Component Structure: - Template: HTML with Angular directives - Class: TypeScript logic and data - Metadata: Decorators defining component behavior - Styles: Component-specific CSS
Code Example - Component Architecture:
// Parent Component
@Component({
selector: 'app-dashboard',
template: `
<app-header [title]="dashboardTitle"></app-header>
<app-sidebar [menuItems]="menuItems"></app-sidebar>
<main>
<app-widget-container
[widgets]="widgets"
(widgetClick)="handleWidgetClick($event)">
</app-widget-container>
</main>
`
})
export class DashboardComponent {
dashboardTitle = 'My Dashboard';
menuItems = [
{ label: 'Home', route: '/home' },
{ label: 'Analytics', route: '/analytics' }
];
widgets = [
{ id: 1, type: 'chart', data: chartData },
{ id: 2, type: 'table', data: tableData }
];
handleWidgetClick(widget: Widget): void {
console.log('Widget clicked:', widget);
}
}
// Child Component
@Component({
selector: 'app-widget-container',
template: `
<div class="widget-grid">
<app-widget
*ngFor="let widget of widgets"
[widget]="widget"
(click)="onWidgetClick(widget)">
</app-widget>
</div>
`
})
export class WidgetContainerComponent {
@Input() widgets: Widget[] = [];
@Output() widgetClick = new EventEmitter<Widget>();
onWidgetClick(widget: Widget): void {
this.widgetClick.emit(widget);
}
}
4. What are the differences between components, directives, and pipes?
Components:
- Purpose: Create reusable UI elements with template, logic, and styling
- Usage: Primary building blocks of Angular applications
- Example:
<app-user-card>,<app-navigation>
Directives:
- Purpose: Add behavior to existing DOM elements or create reusable behavior
- Types: Structural (
*ngIf,*ngFor) and Attribute (ngClass,ngStyle) - Usage: Modify DOM structure or element behavior
Pipes:
- Purpose: Transform data for display
- Usage: Format data in templates
- Example: Date formatting, currency, text transformation
Code Examples:
Component:
@Component({
selector: 'app-user-card',
template: `
<div class="user-card">
<img [src]="user.avatar" [alt]="user.name">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button (click)="onEdit()">Edit</button>
</div>
`
})
export class UserCardComponent {
@Input() user: User;
@Output() edit = new EventEmitter<User>();
onEdit(): void {
this.edit.emit(this.user);
}
}
Directive:
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input() appHighlight: string = 'yellow';
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight);
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string | null) {
this.el.nativeElement.style.backgroundColor = color;
}
}
// Usage: <div appHighlight="lightblue">Hover me!</div>
Pipe:
@Pipe({
name: 'capitalize'
})
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
}
}
// Usage: {{ 'hello world' | capitalize }} // Output: "Hello world"
5. Explain Angular modules and their purpose
NgModules group declarations, imports, exports, and providers in module-based applications. Modern Angular also supports standalone components with direct imports; an NgModule is not required for every application. An ES module organizes JavaScript imports/exports and is a different mechanism. In current Angular, a component declared in an NgModule must be configured as non-standalone.
Angular modules are containers that group related components, directives, pipes, and services together.
Purpose:
- Organization: Group related functionality
- Encapsulation: Control what's exposed to other modules
- Lazy Loading: Enable code splitting and performance optimization
- Dependency Management: Manage imports and exports
Code Example - Module Structure:
// Feature Module
@NgModule({
declarations: [
UserListComponent,
UserDetailComponent,
UserCardComponent,
HighlightDirective,
CapitalizePipe
],
imports: [
CommonModule,
FormsModule,
RouterModule.forChild([
{ path: '', component: UserListComponent },
{ path: ':id', component: UserDetailComponent }
])
],
exports: [
UserCardComponent, // Exported for use in other modules
CapitalizePipe
],
providers: [
UserService,
{ provide: 'API_URL', useValue: 'https://api.example.com' }
]
})
export class UserModule { }
// Root Module
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
FooterComponent
],
imports: [
BrowserModule,
HttpClientModule,
RouterModule.forRoot([
{ path: 'users', loadChildren: () => import('./user/user.module').then(m => m.UserModule) }
])
],
providers: [
{ provide: APP_INITIALIZER, useFactory: initializeApp, deps: [ConfigService], multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
6. What is the difference between NgModule and JavaScript modules?
NgModules group declarations, imports, exports, and providers in module-based applications. Modern Angular also supports standalone components with direct imports; an NgModule is not required for every application. An ES module organizes JavaScript imports/exports and is a different mechanism. In current Angular, a component declared in an NgModule must be configured as non-standalone.
NgModule (Angular):
- Purpose: Angular's dependency injection and compilation context
- Scope: Angular-specific (components, services, etc.)
- Compilation: Angular compiler uses this for AOT compilation
- Lifecycle: Managed by Angular's dependency injection system
JavaScript Modules:
- Purpose: Code organization and encapsulation at language level
- Scope: General JavaScript/TypeScript code
- Compilation: Standard ES6 module system
- Lifecycle: Managed by JavaScript runtime
Code Example - Comparison:
JavaScript Module:
// user.service.ts
export interface User {
id: number;
name: string;
email: string;
}
export class UserService {
private users: User[] = [];
getUsers(): User[] {
return this.users;
}
addUser(user: User): void {
this.users.push(user);
}
}
// user.utils.ts
export function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// index.ts
export * from './user.service';
export * from './user.utils';
NgModule:
// user.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserService } from './user.service';
import { UserComponent } from './user.component';
@NgModule({
declarations: [UserComponent],
imports: [CommonModule],
providers: [UserService], // Angular DI container
exports: [UserComponent]
})
export class UserModule { }
7. Explain Angular's dependency injection system
Angular's DI system provides instances of classes (services) to components and other services that need them.
Key Concepts:
- Injector: Creates and manages service instances
- Provider: Tells injector how to create a service
- Token: Identifier for a dependency
- Scope: Where services are available (root, module, component)
Code Examples:
Service Definition:
@Injectable({
providedIn: 'root' // Singleton service available app-wide
})
export class DataService {
private data: any[] = [];
getData(): any[] {
return this.data;
}
setData(newData: any[]): void {
this.data = newData;
}
}
// Alternative providers
@Injectable({
providedIn: 'any' // New instance for each lazy-loaded module
})
export class LoggerService {
log(message: string): void {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
Component Injection:
@Component({
selector: 'app-data-display',
template: `<div>{{ data | json }}</div>`
})
export class DataDisplayComponent {
data: any[] = [];
constructor(
private dataService: DataService,
private logger: LoggerService,
@Inject('API_URL') private apiUrl: string
) {
this.data = this.dataService.getData();
this.logger.log('DataDisplayComponent initialized');
}
}
Custom Provider:
@NgModule({
providers: [
DataService,
{ provide: 'API_URL', useValue: 'https://api.example.com' },
{ provide: 'CONFIG', useFactory: () => ({ timeout: 5000 }) },
{ provide: LoggerService, useClass: ConsoleLoggerService },
{ provide: 'CACHE_SIZE', useValue: 100 }
]
})
export class AppModule { }
8. What are the differences between services and components?
Components:
- Purpose: Presentational units with UI
- Lifecycle: Tied to DOM elements
- Scope: Component-specific
- Reusability: Limited by template and styling
Services:
- Purpose: Business logic and data management
- Lifecycle: Independent of components
- Scope: Can be shared across components
- Reusability: Highly reusable
Code Example - Comparison:
Component:
@Component({
selector: 'app-user-list',
template: `
<div class="user-list">
<app-user-card
*ngFor="let user of users"
[user]="user"
(edit)="onEditUser($event)">
</app-user-card>
</div>
`
})
export class UserListComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) {}
ngOnInit(): void {
this.loadUsers();
}
loadUsers(): void {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
onEditUser(user: User): void {
this.userService.editUser(user);
}
}
Service:
@Injectable({
providedIn: 'root'
})
export class UserService {
private users$ = new BehaviorSubject<User[]>([]);
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
tap(users => this.users$.next(users))
);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`/api/users/${id}`);
}
createUser(user: User): Observable<User> {
return this.http.post<User>('/api/users', user).pipe(
tap(newUser => {
const currentUsers = this.users$.value;
this.users$.next([...currentUsers, newUser]);
})
);
}
updateUser(user: User): Observable<User> {
return this.http.put<User>(`/api/users/${user.id}`, user).pipe(
tap(updatedUser => {
const currentUsers = this.users$.value;
const index = currentUsers.findIndex(u => u.id === user.id);
if (index !== -1) {
currentUsers[index] = updatedUser;
this.users$.next([...currentUsers]);
}
})
);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`/api/users/${id}`).pipe(
tap(() => {
const currentUsers = this.users$.value;
this.users$.next(currentUsers.filter(u => u.id !== id));
})
);
}
getUsersStream(): Observable<User[]> {
return this.users$.asObservable();
}
}
9. Explain Angular's change detection mechanism
Change detection evaluates bindings in views that need checking and updates affected DOM values. In older Zone.js applications, patched asynchronous activity helps schedule checks. In Angular v21+, zoneless scheduling is the default and relies on Angular notifications such as signal updates, template listeners, and marking views for checking. An arbitrary mutation in a timer does not by itself guarantee a zoneless update. Reference: Zoneless Angular.
10. What is the difference between OnPush and Default change detection?
OnPush allows Angular to skip eligible subtrees. A changed bound input, a handled event, a signal read by the template changing, or an explicit notification such as markForCheck can make a view eligible for checking. Mutating an input object without changing its reference does not by itself mark the child. Do not describe OnPush as “only inputs” or as a guaranteed speedup. Default/eager behavior and scheduling defaults depend on Angular version and whether Zone.js is used. Reference: Zoneless Angular.
11. Explain Angular Components and Their Lifecycle Hooks
Angular Components are the fundamental building blocks of Angular applications. They encapsulate the template, styles, and logic for a specific piece of functionality.
Lifecycle Hooks are methods that get called at specific moments in a component's lifecycle:
import { Component, OnInit, OnDestroy, OnChanges, AfterViewInit, AfterContentInit } from '@angular/core';
@Component({
selector: 'app-user-profile',
template: `
<div *ngIf="user">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
`
})
export class UserProfileComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit, AfterContentInit {
user: any;
private subscription: any;
// Constructor - called first, before any lifecycle hooks
constructor(private userService: UserService) {
console.log('Constructor called');
}
// Called after data-bound properties are initialized
ngOnInit(): void {
console.log('ngOnInit called');
this.subscription = this.userService.getUser().subscribe(user => {
this.user = user;
});
}
// Called when data-bound properties change
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges called', changes);
}
// Called after content is projected into the component
ngAfterContentInit(): void {
console.log('ngAfterContentInit called');
}
// Called after the component's view is initialized
ngAfterViewInit(): void {
console.log('ngAfterViewInit called');
}
// Called before the component is destroyed
ngOnDestroy(): void {
console.log('ngOnDestroy called');
this.subscription?.unsubscribe();
}
}
Lifecycle Hook Order:
1. constructor
2. ngOnChanges (if inputs change)
3. ngOnInit
4. ngDoCheck
5. ngAfterContentInit
6. ngAfterContentChecked
7. ngAfterViewInit
8. ngAfterViewChecked
9. ngOnDestroy
12. Differences Between ngOnInit and Constructor
| Aspect | Constructor | ngOnInit |
|---|---|---|
| Purpose | Initialize class properties, inject dependencies | Initialize component after data binding |
| Timing | Called first, before any lifecycle hooks | Called after data-bound properties are initialized |
| Input Properties | Not available yet | Available and initialized |
| Use Cases | Dependency injection, basic setup | API calls, complex initialization, subscriptions |
@Component({
selector: 'app-example',
template: '<div>{{ message }}</div>'
})
export class ExampleComponent implements OnInit {
@Input() userId: string;
message: string;
constructor(
private userService: UserService, // Dependency injection
private router: Router
) {
// ❌ Don't access @Input properties here
// console.log(this.userId); // undefined
// ✅ Do basic setup
this.message = 'Loading...';
}
ngOnInit(): void {
// ✅ @Input properties are available
console.log(this.userId); // Now available
// ✅ Make API calls, set up subscriptions
if (this.userId) {
this.userService.getUser(this.userId).subscribe(user => {
this.message = `Hello ${user.name}`;
});
}
}
}
13. Component Communication Patterns
1. Parent to Child (@Input)
// Parent Component
@Component({
selector: 'app-parent',
template: `
<app-child [user]="currentUser" [isAdmin]="true"></app-child>
`
})
export class ParentComponent {
currentUser = { name: 'John', email: 'john@example.com' };
}
// Child Component
@Component({
selector: 'app-child',
template: `
<div>
<h3>{{ user.name }}</h3>
<p *ngIf="isAdmin">Admin User</p>
</div>
`
})
export class ChildComponent {
@Input() user: any;
@Input() isAdmin: boolean = false;
}
2. Child to Parent (@Output)
// Child Component
@Component({
selector: 'app-child',
template: `
<button (click)="onUserSelect()">Select User</button>
`
})
export class ChildComponent {
@Output() userSelected = new EventEmitter<any>();
onUserSelect(): void {
this.userSelected.emit({ id: 1, name: 'John' });
}
}
// Parent Component
@Component({
selector: 'app-parent',
template: `
<app-child (userSelected)="handleUserSelection($event)"></app-child>
`
})
export class ParentComponent {
handleUserSelection(user: any): void {
console.log('Selected user:', user);
}
}
3. Service-based Communication
// Shared Service
@Injectable({
providedIn: 'root'
})
export class CommunicationService {
private messageSource = new BehaviorSubject<string>('');
currentMessage = this.messageSource.asObservable();
changeMessage(message: string): void {
this.messageSource.next(message);
}
}
// Component A
@Component({
selector: 'app-component-a',
template: `<button (click)="sendMessage()">Send Message</button>`
})
export class ComponentA {
constructor(private commService: CommunicationService) {}
sendMessage(): void {
this.commService.changeMessage('Hello from Component A!');
}
}
// Component B
@Component({
selector: 'app-component-b',
template: `<div>Received: {{ message }}</div>`
})
export class ComponentB implements OnInit, OnDestroy {
message: string;
private subscription: Subscription;
constructor(private commService: CommunicationService) {}
ngOnInit(): void {
this.subscription = this.commService.currentMessage.subscribe(
message => this.message = message
);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
4. ViewChild/ViewChildren
@Component({
selector: 'app-parent',
template: `
<app-child #childRef></app-child>
<button (click)="callChildMethod()">Call Child Method</button>
`
})
export class ParentComponent {
@ViewChild('childRef') childComponent: ChildComponent;
callChildMethod(): void {
this.childComponent.doSomething();
}
}
14. Difference Between @Input and @Output
| Aspect | @Input | @Output |
|---|---|---|
| Direction | Parent → Child | Child → Parent |
| Type | Property decorator | Property decorator |
| Data Flow | One-way binding | Event emission |
| Usage | Pass data down | Emit events up |
| Syntax | [property]="value" |
(event)="handler()" |
// Child Component
@Component({
selector: 'app-user-card',
template: `
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button (click)="onEdit()">Edit</button>
<button (click)="onDelete()">Delete</button>
</div>
`
})
export class UserCardComponent {
@Input() user: User; // Receive data from parent
@Output() edit = new EventEmitter<User>(); // Send event to parent
@Output() delete = new EventEmitter<User>(); // Send event to parent
onEdit(): void {
this.edit.emit(this.user);
}
onDelete(): void {
this.delete.emit(this.user);
}
}
// Parent Component
@Component({
selector: 'app-user-list',
template: `
<app-user-card
*ngFor="let user of users"
[user]="user"
(edit)="handleEdit($event)"
(delete)="handleDelete($event)">
</app-user-card>
`
})
export class UserListComponent {
users: User[] = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' }
];
handleEdit(user: User): void {
console.log('Edit user:', user);
}
handleDelete(user: User): void {
console.log('Delete user:', user);
}
}
15. Template Syntax and Data Binding
Angular provides four types of data binding:
1. Interpolation (One-way: Component → Template)
@Component({
selector: 'app-example',
template: `
<h1>{{ title }}</h1>
<p>{{ getFullName() }}</p>
<span>{{ 2 + 2 }}</span>
<div>{{ user?.name || 'No name' }}</div>
`
})
export class ExampleComponent {
title = 'Hello Angular';
user = { firstName: 'John', lastName: 'Doe' };
getFullName(): string {
return `${this.user.firstName} ${this.user.lastName}`;
}
}
2. Property Binding (One-way: Component → Template)
@Component({
selector: 'app-example',
template: `
<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isDisabled">Submit</button>
<div [class.active]="isActive" [style.color]="textColor">
Dynamic styling
</div>
<input [value]="inputValue" [placeholder]="placeholder">
`
})
export class ExampleComponent {
imageUrl = 'assets/logo.png';
imageAlt = 'Company Logo';
isDisabled = false;
isActive = true;
textColor = 'blue';
inputValue = 'Default value';
placeholder = 'Enter text...';
}
3. Event Binding (One-way: Template → Component)
@Component({
selector: 'app-example',
template: `
<button (click)="onClick()">Click me</button>
<input (input)="onInput($event)" (keyup.enter)="onEnter()">
<form (submit)="onSubmit($event)">
<button type="submit">Submit</button>
</form>
<div (mouseenter)="onMouseEnter()" (mouseleave)="onMouseLeave()">
Hover me
</div>
`
})
export class ExampleComponent {
onClick(): void {
console.log('Button clicked');
}
onInput(event: any): void {
console.log('Input value:', event.target.value);
}
onEnter(): void {
console.log('Enter pressed');
}
onSubmit(event: any): void {
event.preventDefault();
console.log('Form submitted');
}
onMouseEnter(): void {
console.log('Mouse entered');
}
onMouseLeave(): void {
console.log('Mouse left');
}
}
4. Two-way Binding
@Component({
selector: 'app-example',
template: `
<input [(ngModel)]="name" placeholder="Enter name">
<p>Hello, {{ name }}!</p>
<!-- Custom two-way binding -->
<app-custom-input [(value)]="customValue"></app-custom-input>
`
})
export class ExampleComponent {
name = '';
customValue = '';
}
16. Differences Between Interpolation, Property Binding, and Event Binding
| Aspect | Interpolation | Property Binding | Event Binding |
|---|---|---|---|
| Syntax | {{ expression }} |
[property]="expression" |
(event)="handler()" |
| Direction | Component → Template | Component → Template | Template → Component |
| Use Cases | Display text content | Set element properties | Handle user interactions |
| Performance | Faster for simple text | Better for complex expressions | N/A |
| Security | Automatically escaped | Depends on property type | N/A |
@Component({
selector: 'app-comparison',
template: `
<!-- Interpolation: Simple text display -->
<h1>{{ title }}</h1>
<p>{{ user.name }} is {{ user.age }} years old</p>
<!-- Property Binding: Set element properties -->
<img [src]="user.avatar" [alt]="user.name">
<button [disabled]="!isFormValid">Submit</button>
<div [class.highlight]="isSelected" [style.background-color]="bgColor">
Dynamic styling
</div>
<!-- Event Binding: Handle user interactions -->
<button (click)="onSubmit()">Submit</button>
<input (input)="onInputChange($event)" (keyup.enter)="onEnter()">
<div (mouseenter)="onHover()" (mouseleave)="onLeave()">
Interactive element
</div>
`
})
export class ComparisonComponent {
title = 'Data Binding Examples';
user = { name: 'John', age: 30, avatar: 'assets/avatar.jpg' };
isFormValid = true;
isSelected = false;
bgColor = '#f0f0f0';
onSubmit(): void {
console.log('Form submitted');
}
onInputChange(event: any): void {
console.log('Input changed:', event.target.value);
}
onEnter(): void {
console.log('Enter pressed');
}
onHover(): void {
this.isSelected = true;
}
onLeave(): void {
this.isSelected = false;
}
}
17. Structural Directives and Their Usage
The * syntax desugars a structural directive onto an ng-template. Modern templates can also use built-in @if, @for, and @switch control flow. These built-in blocks are not user-defined structural directives. The examples below illustrate the older directive syntax.
Structural directives change the DOM structure by adding, removing, or manipulating elements.
1. *ngIf
@Component({
selector: 'app-ngif-example',
template: `
<!-- Basic usage -->
<div *ngIf="isVisible">This content is visible</div>
<!-- With else template -->
<div *ngIf="user; else noUser">
<h2>Welcome, {{ user.name }}!</h2>
<p>Email: {{ user.email }}</p>
</div>
<ng-template #noUser>
<p>No user logged in</p>
</ng-template>
<!-- Multiple conditions -->
<div *ngIf="user && user.isAdmin; else regularUser">
<h3>Admin Dashboard</h3>
<button>Manage Users</button>
</div>
<ng-template #regularUser>
<div *ngIf="user">
<h3>User Dashboard</h3>
<p>Welcome, {{ user.name }}</p>
</div>
</ng-template>
`
})
export class NgIfExampleComponent {
isVisible = true;
user: any = null;
}
2. *ngFor
@Component({
selector: 'app-ngfor-example',
template: `
<!-- Basic iteration -->
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<!-- With index -->
<ul>
<li *ngFor="let item of items; let i = index">
{{ i + 1 }}. {{ item }}
</li>
</ul>
<!-- With trackBy for performance -->
<ul>
<li *ngFor="let user of users; trackBy: trackByUserId">
{{ user.name }} ({{ user.id }})
</li>
</ul>
<!-- Nested iteration -->
<div *ngFor="let category of categories">
<h3>{{ category.name }}</h3>
<ul>
<li *ngFor="let product of category.products">
{{ product.name }} - ${{ product.price }}
</li>
</ul>
</div>
`
})
export class NgForExampleComponent {
items = ['Apple', 'Banana', 'Orange'];
users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Bob' }
];
categories = [
{
name: 'Electronics',
products: [
{ name: 'Laptop', price: 999 },
{ name: 'Phone', price: 599 }
]
},
{
name: 'Books',
products: [
{ name: 'Angular Guide', price: 29 },
{ name: 'TypeScript Handbook', price: 19 }
]
}
];
trackByUserId(index: number, user: any): number {
return user.id;
}
}
3. *ngSwitch
@Component({
selector: 'app-ngswitch-example',
template: `
<div [ngSwitch]="userRole">
<div *ngSwitchCase="'admin'">
<h2>Admin Panel</h2>
<button>Manage Users</button>
<button>System Settings</button>
</div>
<div *ngSwitchCase="'manager'">
<h2>Manager Dashboard</h2>
<button>View Reports</button>
<button>Team Management</button>
</div>
<div *ngSwitchCase="'user'">
<h2>User Dashboard</h2>
<button>View Profile</button>
<button>Edit Settings</button>
</div>
<div *ngSwitchDefault>
<h2>Guest Access</h2>
<p>Please log in to access features</p>
</div>
</div>
`
})
export class NgSwitchExampleComponent {
userRole = 'admin';
}
18. Difference Between *ngIf and [hidden]
| Aspect | *ngIf | [hidden] |
|---|---|---|
| DOM Presence | Element removed from DOM | Element remains in DOM |
| Performance | Better for rarely shown elements | Better for frequently toggled elements |
| Memory Usage | Lower (no DOM element) | Higher (element always present) |
| CSS Dependencies | None | Requires CSS to work |
| Use Cases | Heavy components, conditional rendering | Simple show/hide, frequent toggling |
@Component({
selector: 'app-ngif-vs-hidden',
template: `
<!-- *ngIf: Element is completely removed from DOM -->
<div *ngIf="showWithNgIf" class="heavy-component">
<h2>Heavy Component with *ngIf</h2>
<p>This component has complex logic and many child elements</p>
<app-heavy-child></app-heavy-child>
</div>
<!-- [hidden]: Element remains in DOM but is hidden -->
<div [hidden]="!showWithHidden" class="light-component">
<h2>Light Component with [hidden]</h2>
<p>This component is simple and frequently toggled</p>
</div>
<!-- Toggle buttons -->
<button (click)="toggleNgIf()">Toggle *ngIf</button>
<button (click)="toggleHidden()">Toggle [hidden]</button>
`,
styles: [`
.heavy-component {
background: lightblue;
padding: 20px;
margin: 10px 0;
}
.light-component {
background: lightgreen;
padding: 20px;
margin: 10px 0;
}
`]
})
export class NgIfVsHiddenComponent {
showWithNgIf = false;
showWithHidden = false;
toggleNgIf(): void {
this.showWithNgIf = !this.showWithNgIf;
}
toggleHidden(): void {
this.showWithHidden = !this.showWithHidden;
}
}
19. Attribute Directives and Custom Directives
Attribute directives change the appearance or behavior of an element.
Built-in Attribute Directives
@Component({
selector: 'app-attribute-directives',
template: `
<!-- ngClass -->
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}">
Dynamic classes
</div>
<div [ngClass]="getClasses()">
Dynamic classes with method
</div>
<!-- ngStyle -->
<div [ngStyle]="{'color': textColor, 'font-size': fontSize}">
Dynamic styles
</div>
<div [ngStyle]="getStyles()">
Dynamic styles with method
</div>
<!-- ngModel (two-way binding) -->
<input [(ngModel)]="name" placeholder="Enter name">
<p>Hello, {{ name }}!</p>
`
})
export class AttributeDirectivesComponent {
isActive = true;
isDisabled = false;
textColor = 'blue';
fontSize = '16px';
name = '';
getClasses(): any {
return {
'btn': true,
'btn-primary': this.isActive,
'btn-disabled': this.isDisabled
};
}
getStyles(): any {
return {
'color': this.textColor,
'font-size': this.fontSize,
'font-weight': 'bold'
};
}
}
Custom Attribute Directive
// highlight.directive.ts
import { Directive, ElementRef, Input, OnInit, HostListener } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective implements OnInit {
@Input() appHighlight: string = 'yellow';
@Input() defaultColor: string = 'transparent';
constructor(private el: ElementRef) {}
ngOnInit(): void {
this.highlight(this.appHighlight);
}
@HostListener('mouseenter') onMouseEnter(): void {
this.highlight(this.appHighlight);
}
@HostListener('mouseleave') onMouseLeave(): void {
this.highlight(this.defaultColor);
}
private highlight(color: string): void {
this.el.nativeElement.style.backgroundColor = color;
}
}
// Usage in component
@Component({
selector: 'app-custom-directive',
template: `
<p appHighlight="yellow">Highlighted text</p>
<p [appHighlight]="'lightblue'">Custom color</p>
<p [appHighlight]="highlightColor" [defaultColor]="'white'">
Dynamic highlighting
</p>
`
})
export class CustomDirectiveComponent {
highlightColor = 'lightgreen';
}
Advanced Custom Directive
// tooltip.directive.ts
import { Directive, ElementRef, Input, OnInit, Renderer2, HostListener } from '@angular/core';
@Directive({
selector: '[appTooltip]'
})
export class TooltipDirective implements OnInit {
@Input() appTooltip: string = '';
@Input() tooltipPosition: 'top' | 'bottom' | 'left' | 'right' = 'top';
private tooltipElement: HTMLElement | null = null;
constructor(
private el: ElementRef,
private renderer: Renderer2
) {}
ngOnInit(): void {
this.createTooltip();
}
@HostListener('mouseenter') onMouseEnter(): void {
this.showTooltip();
}
@HostListener('mouseleave') onMouseLeave(): void {
this.hideTooltip();
}
private createTooltip(): void {
this.tooltipElement = this.renderer.createElement('div');
this.renderer.setProperty(this.tooltipElement, 'innerHTML', this.appTooltip);
this.renderer.addClass(this.tooltipElement, 'tooltip');
this.renderer.addClass(this.tooltipElement, `tooltip-${this.tooltipPosition}`);
this.renderer.setStyle(this.tooltipElement, 'position', 'absolute');
this.renderer.setStyle(this.tooltipElement, 'display', 'none');
this.renderer.setStyle(this.tooltipElement, 'background', 'black');
this.renderer.setStyle(this.tooltipElement, 'color', 'white');
this.renderer.setStyle(this.tooltipElement, 'padding', '5px');
this.renderer.setStyle(this.tooltipElement, 'border-radius', '3px');
this.renderer.setStyle(this.tooltipElement, 'font-size', '12px');
this.renderer.setStyle(this.tooltipElement, 'z-index', '1000');
}
private showTooltip(): void {
if (this.tooltipElement) {
this.renderer.appendChild(document.body, this.tooltipElement);
this.renderer.setStyle(this.tooltipElement, 'display', 'block');
this.positionTooltip();
}
}
private hideTooltip(): void {
if (this.tooltipElement) {
this.renderer.removeChild(document.body, this.tooltipElement);
}
}
private positionTooltip(): void {
const rect = this.el.nativeElement.getBoundingClientRect();
if (this.tooltipElement) {
switch (this.tooltipPosition) {
case 'top':
this.renderer.setStyle(this.tooltipElement, 'top', `${rect.top - 30}px`);
this.renderer.setStyle(this.tooltipElement, 'left', `${rect.left}px`);
break;
case 'bottom':
this.renderer.setStyle(this.tooltipElement, 'top', `${rect.bottom + 5}px`);
this.renderer.setStyle(this.tooltipElement, 'left', `${rect.left}px`);
break;
// Add other positions as needed
}
}
}
}
20. Differences Between Structural and Attribute Directives
| Aspect | Structural Directives | Attribute Directives |
|---|---|---|
| Purpose | Change DOM structure | Change element appearance/behavior |
| Syntax | Use * prefix |
No prefix |
| DOM Manipulation | Add/remove elements | Modify existing elements |
| Examples | ngIf, ngFor, *ngSwitch | ngClass, ngStyle, ngModel |
| Implementation | Use TemplateRef and ViewContainerRef | Use ElementRef and Renderer2 |
| Use Cases | Conditional rendering, loops | Styling, event handling, validation |
// Structural Directive Example
@Directive({
selector: '[appUnless]'
})
export class UnlessDirective {
private hasView = false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
// Attribute Directive Example
@Directive({
selector: '[appBorder]'
})
export class BorderDirective {
@Input() appBorder: string = '1px solid black';
constructor(
private el: ElementRef,
private renderer: Renderer2
) {}
ngOnInit(): void {
this.renderer.setStyle(this.el.nativeElement, 'border', this.appBorder);
}
}
// Usage in component
@Component({
selector: 'app-directive-comparison',
template: `
<!-- Structural Directive: Changes DOM structure -->
<div *ngIf="showContent">This content can be removed from DOM</div>
<div *appUnless="hideContent">This content uses custom structural directive</div>
<!-- Attribute Directive: Modifies existing element -->
<div [ngClass]="{'active': isActive}">Dynamic classes</div>
<div [appBorder]="'2px solid red'">Custom border directive</div>
`
})
export class DirectiveComparisonComponent {
showContent = true;
hideContent = false;
isActive = true;
}
Angular Services & Dependency Injection
21. Explain Angular services and their singleton nature
A service is not inherently a singleton. Its instance belongs to the injector that provides it. providedIn: root typically supplies one shared instance in the application root injector; a component provider can create a separate instance for each component. Child providers can shadow parent providers. Keep per-user server-rendering state out of shared process-wide services.
22. What are the differences between providedIn: 'root' and providers array?
Answer:
| Aspect | providedIn: 'root' | providers array |
|---|---|---|
| Scope | Application-wide singleton | Module/Component level |
| Tree-shaking | Yes, unused services can be removed | No, always included |
| Performance | Better bundle optimization | Potentially larger bundles |
| Flexibility | Fixed singleton behavior | Configurable scope |
Coding Example:
// Root-level service (singleton)
@Injectable({
providedIn: 'root'
})
export class GlobalService {
private data = new BehaviorSubject<string>('');
setData(value: string) {
this.data.next(value);
}
}
// Module-level service (module singleton)
@Injectable()
export class ModuleService {
constructor() {
console.log('ModuleService created for this module');
}
}
@NgModule({
providers: [ModuleService] // Module-level singleton
})
export class FeatureModule { }
// Component-level service (new instance per component)
@Component({
selector: 'app-child',
providers: [LocalService] // New instance for each component
})
export class ChildComponent {
constructor(private localService: LocalService) {}
}
23. Explain dependency injection tokens and their usage
Answer: DI tokens are unique identifiers used to register and retrieve dependencies. They can be strings, classes, or InjectionToken objects.
Types of Tokens: 1. Type tokens (most common) 2. String tokens 3. InjectionToken (for complex configurations)
Coding Example:
// InjectionToken for configuration
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');
// String token
export const LOGGER_TOKEN = 'logger';
// Service using InjectionToken
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(@Inject(API_CONFIG) private config: ApiConfig) {}
}
// Configuration provider
const apiConfig: ApiConfig = {
baseUrl: 'https://api.example.com',
timeout: 5000
};
@NgModule({
providers: [
{ provide: API_CONFIG, useValue: apiConfig },
{ provide: LOGGER_TOKEN, useClass: ConsoleLogger }
]
})
export class AppModule { }
// Usage in component
@Component({})
export class MyComponent {
constructor(
@Inject(LOGGER_TOKEN) private logger: Logger,
private apiService: ApiService
) {}
}
24. What is the difference between useClass, useValue, and useFactory?
Answer:
| Provider | Purpose | Use Case |
|---|---|---|
| useClass | Creates new instance of class | Services, classes |
| useValue | Provides existing value/object | Constants, configurations |
| useFactory | Executes function to create dependency | Complex initialization, conditional logic |
Coding Example:
// useClass - Creates new instance
{ provide: Logger, useClass: ConsoleLogger }
// useValue - Provides existing value
{ provide: 'API_URL', useValue: 'https://api.example.com' }
// useFactory - Executes function
{
provide: DatabaseConnection,
useFactory: (config: AppConfig) => {
return new DatabaseConnection(config.databaseUrl);
},
deps: [AppConfig]
}
// Complete example
@NgModule({
providers: [
// useClass
{ provide: Logger, useClass: ConsoleLogger },
// useValue
{ provide: 'APP_VERSION', useValue: '1.0.0' },
// useFactory with dependencies
{
provide: HttpClient,
useFactory: (config: AppConfig) => {
return new HttpClient({
baseURL: config.apiUrl,
timeout: config.timeout
});
},
deps: [AppConfig]
}
]
})
export class AppModule { }
25. Explain hierarchical dependency injection
Answer: Angular's DI system follows a hierarchical structure where providers can be configured at different levels, and the injector searches up the hierarchy to find dependencies.
Hierarchy Levels: 1. Element Injector (Component/Directive level) 2. Module Injector (NgModule level) 3. Root Injector (Application level)
Coding Example:
// Root service
@Injectable({ providedIn: 'root' })
export class GlobalService {
getData() { return 'Global Data'; }
}
// Module service
@Injectable()
export class FeatureService {
getData() { return 'Feature Data'; }
}
// Component service
@Injectable()
export class LocalService {
getData() { return 'Local Data'; }
}
@NgModule({
providers: [FeatureService]
})
export class FeatureModule { }
@Component({
selector: 'app-parent',
providers: [LocalService]
})
export class ParentComponent {
constructor(
private global: GlobalService,
private feature: FeatureService,
private local: LocalService
) {}
}
@Component({
selector: 'app-child'
})
export class ChildComponent {
constructor(
private global: GlobalService, // From root
private feature: FeatureService, // From module
private local: LocalService // From parent component
) {}
}
26. What are the differences between module-level and component-level providers?
Component providers are resolved through the element injector and typically create an instance per component instance. NgModule provider scope depends on how the module is loaded: eagerly imported module providers commonly join the root injector, while lazy-loaded contexts can introduce child injectors. It is inaccurate to promise a separate instance for every eagerly loaded module.
27. Explain service lifecycle and memory management
Answer: Angular services have different lifecycles based on their provider scope. Root services live for the entire application lifecycle, while module/component services are tied to their respective lifecycles.
Lifecycle Phases: 1. Creation - When first injected 2. Usage - During application runtime 3. Destruction - When scope is destroyed
Coding Example:
// Root service (lives for entire app)
@Injectable({ providedIn: 'root' })
export class AppService implements OnDestroy {
private subscription = new Subscription();
constructor() {
console.log('AppService created');
}
ngOnDestroy() {
console.log('AppService destroyed (only on app shutdown)');
this.subscription.unsubscribe();
}
}
// Module service (lives with module)
@Injectable()
export class ModuleService implements OnDestroy {
constructor() {
console.log('ModuleService created');
}
ngOnDestroy() {
console.log('ModuleService destroyed when module is destroyed');
}
}
// Component service (lives with component)
@Injectable()
export class ComponentService implements OnDestroy {
constructor() {
console.log('ComponentService created');
}
ngOnDestroy() {
console.log('ComponentService destroyed when component is destroyed');
}
}
@Component({
providers: [ComponentService]
})
export class MyComponent implements OnDestroy {
constructor(
private appService: AppService,
private moduleService: ModuleService,
private componentService: ComponentService
) {}
ngOnDestroy() {
// ComponentService will be destroyed automatically
console.log('Component destroyed');
}
}
28. What is the difference between services and factories?
Answer: In Angular, services are classes that can be injected, while factories are functions that create and return dependencies. Factories provide more flexibility in dependency creation.
Coding Example:
// Service approach
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
getUsers() {
return this.http.get<User[]>('/api/users');
}
}
// Factory approach
export function userServiceFactory(http: HttpClient, config: AppConfig) {
return new UserService(http, config.apiUrl);
}
// Provider configuration
@NgModule({
providers: [
// Service provider
UserService,
// Factory provider
{
provide: 'UserServiceFactory',
useFactory: userServiceFactory,
deps: [HttpClient, AppConfig]
}
]
})
export class AppModule { }
// Usage
@Component({})
export class UserComponent {
constructor(
private userService: UserService, // Service injection
@Inject('UserServiceFactory') private userFactory: UserService // Factory injection
) {}
}
29. Explain custom providers and their configuration
Answer: Custom providers allow you to configure how dependencies are created and injected. They provide flexibility in dependency resolution.
Coding Example:
// Custom provider configurations
@NgModule({
providers: [
// Basic class provider
{ provide: Logger, useClass: ConsoleLogger },
// Value provider
{ provide: 'API_BASE_URL', useValue: 'https://api.example.com' },
// Factory provider with dependencies
{
provide: HttpClient,
useFactory: (config: AppConfig) => {
return new HttpClient({
baseURL: config.apiUrl,
headers: { 'Authorization': `Bearer ${config.apiKey}` }
});
},
deps: [AppConfig]
},
// Existing provider alias
{ provide: 'OldLogger', useExisting: Logger },
// Conditional provider
{
provide: StorageService,
useFactory: (config: AppConfig) => {
return config.useLocalStorage
? new LocalStorageService()
: new SessionStorageService();
},
deps: [AppConfig]
}
]
})
export class AppModule { }
// Usage with custom providers
@Component({})
export class MyComponent {
constructor(
@Inject('API_BASE_URL') private apiUrl: string,
@Inject('OldLogger') private logger: Logger,
private storage: StorageService
) {}
}
30. What are the differences between injectable and non-injectable services?
@Injectable supplies Angular compiler metadata for constructing a class and can declare how it is provided. It does not make a service available unless a provider exists. A plain class without injected constructor dependencies can be registered with a provider, and useValue/useFactory can supply a dependency without decorating its implementation class. Interfaces are erased at runtime, so use an InjectionToken for interface-shaped values.
31. Explain Angular routing and its configuration
Answer: Angular routing enables navigation between different views/components without page reload. It uses the HTML5 History API and provides a declarative way to define navigation paths.
Key Concepts: - Routes: Array of route definitions - RouterOutlet: Placeholder for routed components - RouterLink: Directive for navigation - Route Guards: Control access to routes
Coding Example:
// app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'users', component: UserListComponent },
{ path: 'users/:id', component: UserDetailComponent },
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard] },
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// app.component.html
<nav>
<a routerLink="/home" routerLinkActive="active">Home</a>
<a routerLink="/users" routerLinkActive="active">Users</a>
<a routerLink="/admin" routerLinkActive="active">Admin</a>
</nav>
<router-outlet></router-outlet>
// Component with navigation
@Component({})
export class UserListComponent {
constructor(private router: Router) {}
navigateToUser(id: number) {
this.router.navigate(['/users', id]);
}
navigateWithQuery() {
this.router.navigate(['/users'], {
queryParams: { page: 1, size: 10 }
});
}
}
32. What are the differences between RouterModule and Router?
Answer:
| Aspect | RouterModule | Router |
|---|---|---|
| Type | Module | Service |
| Purpose | Configuration | Navigation logic |
| Usage | Import in modules | Inject in components |
| Functionality | Route definitions | Programmatic navigation |
Coding Example:
// RouterModule - Configuration
const routes: Routes = [
{ path: 'users', component: UserListComponent },
{ path: 'users/:id', component: UserDetailComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UserRoutingModule { }
// Router - Service for navigation
@Component({})
export class UserComponent {
constructor(private router: Router) {}
navigateToUser(id: number) {
this.router.navigate(['/users', id]);
}
navigateWithParams() {
this.router.navigate(['/users'], {
queryParams: { page: 1 },
fragment: 'top'
});
}
getCurrentUrl() {
return this.router.url;
}
}
33. Explain route parameters and their types
Answer: Route parameters are dynamic segments in URLs that can be extracted and used in components. Angular supports several types of parameters.
Parameter Types:
1. Path Parameters (:id)
2. Query Parameters (?page=1)
3. Matrix Parameters (;id=123)
4. Optional Parameters
Coding Example:
// Route configuration with different parameter types
const routes: Routes = [
{ path: 'users/:id', component: UserDetailComponent },
{ path: 'products/:category/:id', component: ProductDetailComponent },
{ path: 'search', component: SearchComponent }
];
// Component handling different parameter types
@Component({})
export class UserDetailComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
// Path parameters
this.route.params.subscribe(params => {
const userId = params['id'];
this.loadUser(userId);
});
// Query parameters
this.route.queryParams.subscribe(queryParams => {
const page = queryParams['page'] || 1;
const size = queryParams['size'] || 10;
});
// Matrix parameters
this.route.params.subscribe(params => {
const matrixParams = this.route.snapshot.paramMap.get('id');
});
// Optional parameters
this.route.paramMap.subscribe(params => {
const id = params.get('id');
const optional = params.get('optional');
});
}
// Navigation with different parameter types
navigateWithParams() {
// Path parameters
this.router.navigate(['/users', 123]);
// Query parameters
this.router.navigate(['/users'], {
queryParams: { page: 1, size: 10 }
});
// Matrix parameters
this.router.navigate(['/users', { id: 123 }]);
}
}
34. What is the difference between route params and query params?
Answer:
| Aspect | Route Params | Query Params |
|---|---|---|
| URL Structure | /users/123 |
/users?page=1&size=10 |
| Required | Yes (for route matching) | No (optional) |
| Order | Position matters | Order doesn't matter |
| Use Case | Resource identification | Filtering, pagination, sorting |
Coding Example:
// Route configuration
const routes: Routes = [
{ path: 'users/:id', component: UserDetailComponent }, // Route param
{ path: 'users', component: UserListComponent } // Query params
];
// Component handling both types
@Component({})
export class UserComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Route parameters (required, in URL path)
this.route.params.subscribe(params => {
const userId = params['id']; // From /users/123
this.loadUser(userId);
});
// Query parameters (optional, after ?)
this.route.queryParams.subscribe(queryParams => {
const page = queryParams['page'] || 1; // From ?page=1
const size = queryParams['size'] || 10; // From &size=10
const sort = queryParams['sort'] || 'name'; // From &sort=name
this.loadUsers(page, size, sort);
});
}
// Navigation examples
navigateToUser(id: number) {
// Route parameter
this.router.navigate(['/users', id]);
}
navigateWithFilters(page: number, size: number) {
// Query parameters
this.router.navigate(['/users'], {
queryParams: { page, size },
queryParamsHandling: 'merge' // Preserve existing query params
});
}
}
35. Explain route guards and their implementation
Client-side guards control navigation only. Enforce access checks in the server API. Current functional guards can return booleans, UrlTree/RedirectCommand, or asynchronous equivalents; prefer returning a redirect rather than navigating as a side effect. CanMatch controls route matching. Reference: Route guards.
Answer: Route guards are interfaces that allow you to control the accessibility of routes. They can prevent navigation, redirect users, or allow navigation based on conditions.
Guard Types: 1. CanActivate - Control route activation 2. CanDeactivate - Control route deactivation 3. CanLoad - Control lazy-loaded modules 4. Resolve - Pre-fetch data before activation
Coding Example:
// Authentication Guard
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
if (this.authService.isAuthenticated()) {
return true;
}
// Redirect to login
return this.router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
}
}
// Role-based Guard
@Injectable({ providedIn: 'root' })
export class RoleGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate(route: ActivatedRouteSnapshot): boolean {
const requiredRole = route.data['role'];
const userRole = this.authService.getUserRole();
return userRole === requiredRole;
}
}
// Deactivation Guard
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<EditComponent> {
canDeactivate(component: EditComponent): boolean {
if (component.hasUnsavedChanges()) {
return confirm('You have unsaved changes. Are you sure you want to leave?');
}
return true;
}
}
// Route configuration with guards
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard, RoleGuard],
data: { role: 'admin' }
},
{
path: 'edit/:id',
component: EditComponent,
canDeactivate: [UnsavedChangesGuard]
}
];
36. What are the differences between CanActivate and CanDeactivate?
Answer:
| Aspect | CanActivate | CanDeactivate |
|---|---|---|
| Timing | Before route activation | Before route deactivation |
| Purpose | Control access to route | Prevent leaving route |
| Use Case | Authentication, authorization | Unsaved changes, confirmation |
| Return Type | boolean | UrlTree | Observable | boolean | Observable |
Coding Example:
// CanActivate - Controls access to route
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
if (this.authService.isAuthenticated()) {
return true;
}
// Redirect to login
return this.router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
}
}
// CanDeactivate - Controls leaving route
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<EditComponent> {
canDeactivate(component: EditComponent): boolean | Observable<boolean> {
if (component.hasUnsavedChanges()) {
return new Observable(observer => {
const result = confirm('You have unsaved changes. Leave anyway?');
observer.next(result);
observer.complete();
});
}
return true;
}
}
// Route configuration
const routes: Routes = [
{
path: 'protected',
component: ProtectedComponent,
canActivate: [AuthGuard] // Check before entering
},
{
path: 'edit',
component: EditComponent,
canDeactivate: [UnsavedChangesGuard] // Check before leaving
}
];
37. Explain lazy loading and its benefits
Answer: Lazy loading is a technique that loads modules only when they are needed, improving initial application load time and performance.
Benefits: - Faster initial load - Only load required modules - Better performance - Reduce bundle size - Improved user experience - Faster startup time - Better resource utilization - Load on demand
Coding Example:
// Main routing with lazy loading
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
},
{
path: 'users',
loadChildren: () => import('./users/users.module').then(m => m.UsersModule)
}
];
// Admin module (lazy loaded)
@NgModule({
imports: [
CommonModule,
AdminRoutingModule
],
declarations: [AdminComponent, UserManagementComponent]
})
export class AdminModule { }
// Admin routing
const adminRoutes: Routes = [
{ path: '', component: AdminComponent },
{ path: 'users', component: UserManagementComponent }
];
@NgModule({
imports: [RouterModule.forChild(adminRoutes)],
exports: [RouterModule]
})
export class AdminRoutingModule { }
// Preloading strategy
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules // Preload all lazy modules
})
],
exports: [RouterModule]
})
export class AppRoutingModule { }
38. What is the difference between eager and lazy loading?
Answer:
| Aspect | Eager Loading | Lazy Loading |
|---|---|---|
| Load Time | At application startup | On demand |
| Bundle Size | Larger initial bundle | Smaller initial bundle |
| Performance | Slower startup | Faster startup |
| Memory Usage | Higher initial memory | Lower initial memory |
| Use Case | Core features | Feature modules |
Coding Example:
// Eager loading (default)
@NgModule({
imports: [
CommonModule,
HomeModule, // Loaded immediately
SharedModule // Loaded immediately
]
})
export class AppModule { }
// Lazy loading
const routes: Routes = [
{ path: 'home', component: HomeComponent }, // Eager
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) // Lazy
}
];
// Custom preloading strategy
@Injectable({ providedIn: 'root' })
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) {
return load();
}
return of(null);
}
}
// Route with preload option
const routes: Routes = [
{
path: 'important-feature',
loadChildren: () => import('./important/important.module').then(m => m.ImportantModule),
data: { preload: true } // Preload this module
}
];
39. Explain route resolvers and their usage
A resolver loads route data before activation; it is distinct from a guard. A resolver can return a value, Promise, or Observable. Navigation waits for the resolved result, so define error, cancellation, and loading behavior. A resolver is not a replacement for server authorization.
40. What are the differences between route guards and resolvers?
Answer:
| Aspect | Route Guards | Resolvers |
|---|---|---|
| Purpose | Control navigation | Pre-fetch data |
| Timing | Before route activation | Before route activation |
| Return Type | boolean | UrlTree | Observable | Promise |
| Use Case | Access control, validation | Data loading |
| Component Access | No data passed to component | Data available in component |
Coding Example:
// Route Guard - Controls access
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean | UrlTree {
if (this.authService.isAuthenticated()) {
return true;
}
return this.router.createUrlTree(['/login']);
}
}
// Route Resolver - Pre-fetches data
@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve<User> {
constructor(private userService: UserService) {}
resolve(route: ActivatedRouteSnapshot): Observable<User> {
const userId = route.paramMap.get('id');
return this.userService.getUser(userId);
}
}
// Route configuration
const routes: Routes = [
{
path: 'protected',
component: ProtectedComponent,
canActivate: [AuthGuard] // Guard - controls access
},
{
path: 'users/:id',
component: UserDetailComponent,
resolve: { user: UserResolver } // Resolver - pre-fetches data
}
];
// Component usage
@Component({})
export class UserDetailComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Guard ensures we can access this route
// Resolver ensures user data is available
this.route.data.subscribe(data => {
this.user = data['user']; // Data from resolver
});
}
}
Angular Reactive Forms
41. Explain Angular reactive forms and their benefits
Reactive forms use an explicit control model. FormControl/FormGroup instances are mutable: methods such as setValue, patchValue, enable, and disable update their state. Immutable application-data practices are useful, but do not mean the form control objects themselves are immutable.
Answer: Angular reactive forms provide a model-driven approach to handling form inputs and validation. They use explicit, immutable data structures to manage form state.
Key Benefits: - Type Safety: Strong typing with TypeScript - Testability: Easy to unit test form logic - Predictable: Immutable data flow - Scalable: Better for complex forms - Reactive: Leverage RxJS for advanced scenarios
Example:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-user-form',
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="name" placeholder="Name">
<input formControlName="email" placeholder="Email">
<button type="submit" [disabled]="!userForm.valid">Submit</button>
</form>
`
})
export class UserFormComponent implements OnInit {
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]]
});
}
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
}
}
}
42. What are the differences between template-driven and reactive forms?
Answer:
| Aspect | Template-Driven | Reactive |
|---|---|---|
| Setup | Declarative in template | Programmatic in component |
| Data Model | Two-way binding | Explicit form model |
| Validation | Template-based | Component-based |
| Testing | Harder to test | Easy to unit test |
| Complexity | Simple forms | Complex forms |
| Performance | Less predictable | More predictable |
Template-Driven Example:
// Template
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm.value)">
<input name="name" [(ngModel)]="user.name" required #name="ngModel">
<input name="email" [(ngModel)]="user.email" required email #email="ngModel">
</form>
// Component
export class UserComponent {
user = { name: '', email: '' };
onSubmit(formValue: any) {
console.log(formValue);
}
}
Reactive Example:
// Template
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="name">
<input formControlName="email">
</form>
// Component
export class UserComponent {
userForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
}
43. Explain form validation and custom validators
Answer: Form validation ensures data integrity by checking input values against defined rules.
Built-in Validators:
import { Validators } from '@angular/forms';
this.form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
age: ['', [Validators.min(18), Validators.max(65)]],
password: ['', [Validators.required, Validators.minLength(8)]]
});
Custom Validator Example:
// Custom validator function
export function passwordStrengthValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (!value) return null;
const hasUpperCase = /[A-Z]/.test(value);
const hasLowerCase = /[a-z]/.test(value);
const hasNumbers = /\d/.test(value);
const hasSpecialChar = /[!@#$%^&*]/.test(value);
const valid = hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar;
return valid ? null : { passwordStrength: true };
};
}
// Usage
this.form = this.fb.group({
password: ['', [Validators.required, passwordStrengthValidator()]]
});
Async Validator Example:
export function emailExistsValidator(http: HttpClient): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return http.get(`/api/check-email/${control.value}`).pipe(
map(response => response ? { emailExists: true } : null),
catchError(() => of(null))
);
};
}
44. What is the difference between synchronous and asynchronous validators?
Answer:
| Aspect | Synchronous Validators | Asynchronous Validators |
|---|---|---|
| Execution | Immediate | After async operation |
| Return Type | ValidationErrors | null | Observable<ValidationErrors | null> |
| Use Cases | Format, length, pattern | Server validation, API calls |
| Performance | Fast | Slower (network calls) |
| Order | Run first | Run after sync validators |
Example:
export class UserFormComponent {
userForm = this.fb.group({
email: ['',
[Validators.required, Validators.email], // Sync validators
[this.emailExistsValidator()] // Async validator
]
});
emailExistsValidator(): AsyncValidatorFn {
return (control: AbstractControl) => {
return this.http.get(`/api/check-email/${control.value}`).pipe(
map(response => response ? { emailExists: true } : null),
catchError(() => of(null))
);
};
}
}
45. Explain form controls and form groups
Answer: - FormControl: Manages a single form control - FormGroup: Manages a collection of form controls
FormControl Example:
export class SimpleFormComponent {
nameControl = new FormControl('', Validators.required);
emailControl = new FormControl('', [Validators.required, Validators.email]);
ngOnInit() {
// Listen to value changes
this.nameControl.valueChanges.subscribe(value => {
console.log('Name changed:', value);
});
// Check validation status
this.emailControl.statusChanges.subscribe(status => {
console.log('Email status:', status);
});
}
}
FormGroup Example:
export class UserFormComponent {
userForm = new FormGroup({
personalInfo: new FormGroup({
firstName: new FormControl('', Validators.required),
lastName: new FormControl('', Validators.required)
}),
contactInfo: new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
phone: new FormControl('', Validators.pattern(/^\d{10}$/))
})
});
onSubmit() {
if (this.userForm.valid) {
console.log('Personal Info:', this.userForm.get('personalInfo')?.value);
console.log('Contact Info:', this.userForm.get('contactInfo')?.value);
}
}
}
46. What are the differences between FormControl and FormGroup?
Answer:
| Aspect | FormControl | FormGroup |
|---|---|---|
| Purpose | Single form field | Collection of form controls |
| Value | Single value | Object with multiple values |
| Validation | Individual validation | Group-level validation |
| Structure | Leaf node | Parent node |
| Access | Direct value access | Nested property access |
Example:
// FormControl - single field
const emailControl = new FormControl('', Validators.email);
console.log(emailControl.value); // 'user@example.com'
// FormGroup - multiple fields
const userGroup = new FormGroup({
name: new FormControl('John'),
email: new FormControl('john@example.com')
});
console.log(userGroup.value); // { name: 'John', email: 'john@example.com' }
// Nested access
console.log(userGroup.get('name')?.value); // 'John'
47. Explain form arrays and their usage
Answer: FormArray manages a collection of form controls dynamically, useful for lists or dynamic forms.
Example:
export class DynamicFormComponent {
userForm = this.fb.group({
name: ['', Validators.required],
emails: this.fb.array([]) // FormArray
});
get emails() {
return this.userForm.get('emails') as FormArray;
}
addEmail() {
const emailControl = this.fb.control('', [Validators.required, Validators.email]);
this.emails.push(emailControl);
}
removeEmail(index: number) {
this.emails.removeAt(index);
}
// Initialize with existing data
loadUserData(userData: any) {
this.userForm.patchValue({ name: userData.name });
// Clear existing emails
while (this.emails.length) {
this.emails.removeAt(0);
}
// Add emails from data
userData.emails.forEach((email: string) => {
this.emails.push(this.fb.control(email, [Validators.required, Validators.email]));
});
}
}
Template Example:
<form [formGroup]="userForm">
<input formControlName="name" placeholder="Name">
<div formArrayName="emails">
<div *ngFor="let email of emails.controls; let i=index">
<input [formControlName]="i" placeholder="Email">
<button type="button" (click)="removeEmail(i)">Remove</button>
</div>
</div>
<button type="button" (click)="addEmail()">Add Email</button>
</form>
48. What is the difference between FormArray and FormGroup?
Answer:
| Aspect | FormArray | FormGroup |
|---|---|---|
| Structure | Indexed array | Named properties |
| Access | Numeric index | String key |
| Use Case | Dynamic lists | Fixed structure |
| Validation | Array-level validation | Group-level validation |
| Iteration | ngFor with index | Object iteration |
Example:
// FormArray - dynamic list
const skillsArray = new FormArray([
new FormControl('Angular'),
new FormControl('React'),
new FormControl('Vue')
]);
// FormGroup - fixed structure
const userGroup = new FormGroup({
firstName: new FormControl('John'),
lastName: new FormControl('Doe'),
email: new FormControl('john@example.com')
});
// Access patterns
console.log(skillsArray.at(0).value); // 'Angular'
console.log(userGroup.get('firstName')?.value); // 'John'
49. Explain custom form controls and their implementation
Answer: Custom form controls allow you to create reusable form components that integrate with Angular's reactive forms.
Example - Custom Rating Component:
// rating.component.ts
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-rating',
template: `
<div class="rating">
<span
*ngFor="let star of stars; let i = index"
(click)="setRating(i + 1)"
[class.active]="i < value"
class="star">
★
</span>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RatingComponent),
multi: true
}
]
})
export class RatingComponent implements ControlValueAccessor {
value = 0;
stars = [1, 2, 3, 4, 5];
onChange = (value: number) => {};
onTouched = () => {};
setRating(rating: number) {
this.value = rating;
this.onChange(rating);
this.onTouched();
}
writeValue(value: number): void {
this.value = value || 0;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
// Handle disabled state
}
}
Usage:
// In parent component
this.form = this.fb.group({
rating: [0, [Validators.required, Validators.min(1)]]
});
// In template
<form [formGroup]="form">
<app-rating formControlName="rating"></app-rating>
</form>
50. What are the differences between ControlValueAccessor and Validator?
Answer:
| Aspect | ControlValueAccessor | Validator |
|---|---|---|
| Purpose | Bridge between DOM and form control | Validate form values |
| Interface | writeValue, registerOnChange, etc. | validate method |
| Use Case | Custom input components | Custom validation logic |
| Integration | NG_VALUE_ACCESSOR provider | ValidatorFn or AsyncValidatorFn |
| Timing | Value changes | Validation checks |
Example - Combined Implementation:
@Component({
selector: 'app-custom-input',
template: `<input [value]="value" (input)="onInput($event)">`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent implements ControlValueAccessor, Validator {
value = '';
onChange = (value: string) => {};
onTouched = () => {};
onInput(event: any) {
this.value = event.target.value;
this.onChange(this.value);
this.onTouched();
}
// ControlValueAccessor methods
writeValue(value: string): void {
this.value = value || '';
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {}
// Validator method
validate(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value) return null;
return value.length < 3 ? { minLength: true } : null;
}
}
HTTP & Data Services
51. Explain Angular HttpClient and its features
HttpClient provides Observable-based HTTP calls, automatic JSON parsing, interceptors, and request options. A type parameter describes the expected result to TypeScript; it does not validate server data at runtime. Each subscription to a normal request Observable can send a new request. Unsubscribing aborts an in-progress client request, but cannot undo work already committed by the server. Reference: HttpClient.
52. What are the differences between Http and HttpClient?
Answer:
| Aspect | Http (Legacy) | HttpClient (Modern) |
|---|---|---|
| JSON Handling | Manual parsing required | Automatic JSON parsing |
| Type Safety | Limited | Full TypeScript support |
| Error Handling | Manual error handling | Built-in error handling |
| Interceptors | Not supported | Full interceptor support |
| Request Options | Complex configuration | Simplified options |
| Response Types | Response object | Typed responses |
Http (Legacy) Example:
// Old Http approach
constructor(private http: Http) {}
getUsers(): Observable<User[]> {
return this.http.get('/api/users')
.map((response: Response) => response.json())
.catch((error: Response) => {
return Observable.throw(error.json());
});
}
HttpClient (Modern) Example:
// Modern HttpClient approach
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users')
.pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
return throwError(() => new Error('Something went wrong'));
}
53. Explain HTTP interceptors and their usage
Answer: HTTP interceptors allow you to transform HTTP requests and responses globally. They're useful for authentication, logging, error handling, and request/response transformation.
Authentication Interceptor:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.authService.getToken();
if (token) {
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next.handle(authReq);
}
return next.handle(req);
}
}
Logging Interceptor:
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const startTime = Date.now();
return next.handle(req).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
const duration = Date.now() - startTime;
console.log(`${req.method} ${req.url} - ${duration}ms`);
}
})
);
}
}
Error Handling Interceptor:
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private router: Router) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// Handle unauthorized
this.router.navigate(['/login']);
} else if (error.status === 403) {
// Handle forbidden
console.error('Access denied');
}
return throwError(() => error);
})
);
}
}
Registration:
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: LoggingInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true
}
]
})
export class AppModule { }
54. What is the difference between interceptors and guards?
Answer:
| Aspect | Interceptors | Guards |
|---|---|---|
| Purpose | HTTP request/response transformation | Route protection |
| Scope | All HTTP requests | Route navigation |
| Timing | Before/after HTTP calls | Before route activation |
| Use Cases | Authentication headers, logging, error handling | Route access control, data loading |
| Return Type | Observable<HttpEvent> | boolean | UrlTree | Observable |
Interceptor Example (HTTP Level):
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.getItem('token');
if (token) {
req = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
}
return next.handle(req);
}
}
Guard Example (Route Level):
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.authService.isAuthenticated()) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
55. Explain error handling in HTTP requests
Answer: Error handling in HTTP requests involves catching and processing various types of errors that can occur during network communication.
Comprehensive Error Handling:
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = 'An error occurred';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = `Client Error: ${error.error.message}`;
} else {
// Server-side error
switch (error.status) {
case 400:
errorMessage = 'Bad Request - Invalid data provided';
break;
case 401:
errorMessage = 'Unauthorized - Please login';
break;
case 403:
errorMessage = 'Forbidden - Access denied';
break;
case 404:
errorMessage = 'Not Found - Resource not available';
break;
case 500:
errorMessage = 'Server Error - Please try again later';
break;
default:
errorMessage = `Server Error: ${error.status} - ${error.message}`;
}
}
console.error(errorMessage);
return throwError(() => new Error(errorMessage));
}
}
Component-Level Error Handling:
@Component({
selector: 'app-users',
template: `
<div *ngIf="error" class="error">{{ error }}</div>
<div *ngIf="loading" class="loading">Loading...</div>
<ul *ngIf="users">
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class UsersComponent implements OnInit {
users: User[] = [];
error: string = '';
loading = false;
constructor(private userService: UserService) {}
ngOnInit() {
this.loadUsers();
}
loadUsers() {
this.loading = true;
this.error = '';
this.userService.getUsers().pipe(
finalize(() => this.loading = false)
).subscribe({
next: (users) => {
this.users = users;
},
error: (error) => {
this.error = error.message;
}
});
}
}
56. What are the differences between catchError and retry operators?
Answer:
| Aspect | catchError | retry |
|---|---|---|
| Purpose | Handle errors gracefully | Retry failed requests |
| Behavior | Catches and transforms errors | Reattempts the request |
| Use Case | Error recovery, fallback values | Network issues, temporary failures |
| Return | New Observable | Same Observable retried |
| Configuration | Error handler function | Retry count or conditions |
catchError Example:
import { catchError, of } from 'rxjs';
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
catchError(error => {
console.error('Failed to fetch users:', error);
// Return fallback data
return of([]);
})
);
}
// With error transformation
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
catchError(error => {
if (error.status === 404) {
return of([]); // Return empty array for 404
}
return throwError(() => new Error('Failed to load users'));
})
);
}
retry Example:
import { retry, delay } from 'rxjs';
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
retry(3) // Retry 3 times
);
}
// Retry with delay
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
retry({
count: 3,
delay: (error, retryCount) => {
console.log(`Retry attempt ${retryCount}`);
return timer(retryCount * 1000); // Exponential backoff
}
})
);
}
// Retry only for specific errors
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
retry({
count: 3,
delay: 1000,
resetOnSuccess: true
}),
catchError(error => {
console.error('All retry attempts failed');
return throwError(() => error);
})
);
}
57. Explain HTTP request caching strategies
Answer: HTTP request caching strategies help improve performance and reduce server load by storing responses locally.
Browser Cache Strategy:
@Injectable({
providedIn: 'root'
})
export class CachedUserService {
private cache = new Map<string, { data: any; timestamp: number }>();
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
const cacheKey = 'users';
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {
return of(cached.data);
}
return this.http.get<User[]>('/api/users').pipe(
tap(users => {
this.cache.set(cacheKey, {
data: users,
timestamp: Date.now()
});
})
);
}
invalidateCache(key?: string) {
if (key) {
this.cache.delete(key);
} else {
this.cache.clear();
}
}
}
HTTP Cache Headers Strategy:
@Injectable({
providedIn: 'root'
})
export class HttpCacheService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
const headers = new HttpHeaders({
'Cache-Control': 'max-age=300', // 5 minutes
'ETag': this.getETag()
});
return this.http.get<User[]>('/api/users', { headers });
}
private getETag(): string {
return localStorage.getItem('users-etag') || '';
}
}
RxJS ShareReplay Strategy:
@Injectable({
providedIn: 'root'
})
export class SharedUserService {
private users$ = this.http.get<User[]>('/api/users').pipe(
shareReplay(1, 5 * 60 * 1000) // Cache last value for 5 minutes
);
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.users$;
}
}
58. What is the difference between browser cache and application cache?
Answer:
| Aspect | Browser Cache | Application Cache |
|---|---|---|
| Storage | Browser's HTTP cache | Application memory/storage |
| Control | Limited control | Full control |
| Persistence | Browser-managed | Application-managed |
| Scope | Global browser | Application-specific |
| Configuration | HTTP headers | Application logic |
Browser Cache (HTTP Headers):
@Injectable({
providedIn: 'root'
})
export class BrowserCacheService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
const headers = new HttpHeaders({
'Cache-Control': 'max-age=3600', // 1 hour
'ETag': 'users-v1',
'Last-Modified': new Date().toUTCString()
});
return this.http.get<User[]>('/api/users', { headers });
}
}
Application Cache (Memory/Storage):
@Injectable({
providedIn: 'root'
})
export class AppCacheService {
private cache = new Map<string, CacheEntry>();
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
const cacheKey = 'users';
const cached = this.getFromCache(cacheKey);
if (cached) {
return of(cached);
}
return this.http.get<User[]>('/api/users').pipe(
tap(users => this.setCache(cacheKey, users))
);
}
private getFromCache(key: string): any {
const entry = this.cache.get(key);
if (entry && Date.now() - entry.timestamp < entry.ttl) {
return entry.data;
}
this.cache.delete(key);
return null;
}
private setCache(key: string, data: any, ttl = 300000): void {
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl
});
}
}
interface CacheEntry {
data: any;
timestamp: number;
ttl: number;
}
59. Explain HTTP request cancellation and cleanup
For HttpClient requests, unsubscribe from the request Observable or use a lifecycle-bound operator such as takeUntilDestroyed. switchMap can cancel stale requests when a newer search arrives. AbortController is directly applicable to fetch; do not assume a fetch-style signal option exists on every Angular HttpClient version.
import { Component, DestroyRef, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({ selector: 'app-users', template: '' })
export class UsersComponent {
private http = inject(HttpClient);
private destroyRef = inject(DestroyRef);
load() {
this.http.get<unknown[]>('/api/users')
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({ next: users => console.log(users), error: console.error });
}
}
// Configure provideHttpClient() at application bootstrap.
60. What are the differences between takeUntil and unsubscribe?
unsubscribe explicitly tears down a subscription. takeUntil completes downstream when its notifier emits; merely completing the notifier without emitting does not trigger it. Neither is an error-handling operator. A common lifecycle notifier calls next() and then complete() during destruction; takeUntilDestroyed is a convenient Angular alternative.
61. Explain Angular state management patterns
Answer: Angular state management patterns help manage application state in a predictable and maintainable way. Here are the key patterns:
1. Service-based State Management:
// user.service.ts
@Injectable({
providedIn: 'root'
})
export class UserService {
private userSubject = new BehaviorSubject<User | null>(null);
public user$ = this.userSubject.asObservable();
setUser(user: User) {
this.userSubject.next(user);
}
getUser(): User | null {
return this.userSubject.value;
}
}
2. Component State Management:
// user-profile.component.ts
@Component({
selector: 'app-user-profile',
template: `
<div *ngIf="user$ | async as user">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
`
})
export class UserProfileComponent {
user$ = this.userService.user$;
constructor(private userService: UserService) {}
}
3. Local Component State:
// form.component.ts
@Component({
selector: 'app-form',
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="name" />
<button type="submit" [disabled]="!form.valid">Submit</button>
</form>
`
})
export class FormComponent {
form = this.fb.group({
name: ['', Validators.required]
});
constructor(private fb: FormBuilder) {}
onSubmit() {
if (this.form.valid) {
console.log(this.form.value);
}
}
}
62. What are the differences between services and NgRx?
Answer:
| Aspect | Services | NgRx |
|---|---|---|
| State Management | Simple, reactive state | Complex, centralized state |
| Architecture | Service-based | Redux-inspired |
| Predictability | Moderate | High |
| Boilerplate | Low | High |
| Use Cases | Small to medium apps | Large, complex apps |
Services Example:
// product.service.ts
@Injectable({
providedIn: 'root'
})
export class ProductService {
private productsSubject = new BehaviorSubject<Product[]>([]);
products$ = this.productsSubject.asObservable();
loadProducts() {
this.http.get<Product[]>('/api/products')
.pipe(
tap(products => this.productsSubject.next(products))
)
.subscribe();
}
}
NgRx Example:
// product.actions.ts
export const loadProducts = createAction('[Product] Load Products');
export const loadProductsSuccess = createAction(
'[Product] Load Products Success',
props<{ products: Product[] }>()
);
// product.reducer.ts
export const productReducer = createReducer(
initialState,
on(loadProducts, state => ({ ...state, loading: true })),
on(loadProductsSuccess, (state, { products }) => ({
...state,
products,
loading: false
}))
);
63. Explain NgRx store and its architecture
Answer: NgRx store follows the Redux pattern with three core principles: 1. Single source of truth - One state tree 2. State is read-only - Only actions can change state 3. Changes are made with pure functions - Reducers
Store Architecture:
// app.state.ts
export interface AppState {
products: ProductState;
user: UserState;
}
export interface ProductState {
products: Product[];
loading: boolean;
error: string | null;
}
// store configuration
@NgModule({
imports: [
StoreModule.forRoot({
products: productReducer,
user: userReducer
}),
EffectsModule.forRoot([ProductEffects, UserEffects])
]
})
export class AppModule {}
Store Usage:
// product-list.component.ts
@Component({
selector: 'app-product-list',
template: `
<div *ngIf="products$ | async as products">
<div *ngFor="let product of products">
{{ product.name }} - {{ product.price }}
</div>
</div>
`
})
export class ProductListComponent {
products$ = this.store.select(selectProducts);
loading$ = this.store.select(selectProductsLoading);
constructor(private store: Store) {
this.store.dispatch(loadProducts());
}
}
64. What is the difference between actions and reducers?
Answer:
Actions are payloads of information that describe what happened:
// product.actions.ts
export const addProduct = createAction(
'[Product] Add Product',
props<{ product: Product }>()
);
export const updateProduct = createAction(
'[Product] Update Product',
props<{ id: number; changes: Partial<Product> }>()
);
export const deleteProduct = createAction(
'[Product] Delete Product',
props<{ id: number }>()
);
Reducers are pure functions that specify how state changes in response to actions:
// product.reducer.ts
export const productReducer = createReducer(
initialState,
on(addProduct, (state, { product }) => ({
...state,
products: [...state.products, product]
})),
on(updateProduct, (state, { id, changes }) => ({
...state,
products: state.products.map(product =>
product.id === id ? { ...product, ...changes } : product
)
})),
on(deleteProduct, (state, { id }) => ({
...state,
products: state.products.filter(product => product.id !== id)
}))
);
65. Explain NgRx effects and side effects
Answer: NgRx effects handle side effects (API calls, timers, etc.) in a reactive way:
// product.effects.ts
@Injectable()
export class ProductEffects {
loadProducts$ = createEffect(() =>
this.actions$.pipe(
ofType(loadProducts),
mergeMap(() =>
this.productService.getProducts().pipe(
map(products => loadProductsSuccess({ products })),
catchError(error => of(loadProductsFailure({ error: error.message })))
)
)
)
);
saveProduct$ = createEffect(() =>
this.actions$.pipe(
ofType(saveProduct),
mergeMap(({ product }) =>
this.productService.saveProduct(product).pipe(
map(savedProduct => saveProductSuccess({ product: savedProduct })),
catchError(error => of(saveProductFailure({ error: error.message })))
)
)
)
);
constructor(
private actions$: Actions,
private productService: ProductService
) {}
}
Side Effects Examples:
// Side effects for navigation
navigateToProduct$ = createEffect(() =>
this.actions$.pipe(
ofType(saveProductSuccess),
tap(() => this.router.navigate(['/products']))
),
{ dispatch: false }
);
// Side effects for notifications
showNotification$ = createEffect(() =>
this.actions$.pipe(
ofType(saveProductSuccess),
tap(() => this.notificationService.show('Product saved successfully!'))
),
{ dispatch: false }
);
66. What are the differences between effects and services?
Answer:
| Aspect | Effects | Services |
|---|---|---|
| Purpose | Handle side effects | Business logic and data |
| Reactivity | Reactive to actions | Manual invocation |
| State Management | Dispatch actions | Direct state updates |
| Testing | Action-based testing | Method-based testing |
| Error Handling | Built-in error actions | Try-catch blocks |
Effects Example:
// product.effects.ts
@Injectable()
export class ProductEffects {
loadProducts$ = createEffect(() =>
this.actions$.pipe(
ofType(loadProducts),
mergeMap(() => this.productService.getProducts()),
map(products => loadProductsSuccess({ products }))
)
);
}
Service Example:
// product.service.ts
@Injectable({
providedIn: 'root'
})
export class ProductService {
private productsSubject = new BehaviorSubject<Product[]>([]);
products$ = this.productsSubject.asObservable();
loadProducts() {
return this.http.get<Product[]>('/api/products').pipe(
tap(products => this.productsSubject.next(products))
);
}
}
67. Explain NgRx selectors and their optimization
Answer: Selectors are pure functions used to extract and derive data from the store:
// product.selectors.ts
export const selectProductState = (state: AppState) => state.products;
export const selectProducts = createSelector(
selectProductState,
(state: ProductState) => state.products
);
export const selectProductsLoading = createSelector(
selectProductState,
(state: ProductState) => state.loading
);
// Optimized selectors with memoization
export const selectProductsByCategory = createSelector(
selectProducts,
(products: Product[], props: { category: string }) =>
products.filter(product => product.category === props.category)
);
export const selectProductById = createSelector(
selectProducts,
(products: Product[], props: { id: number }) =>
products.find(product => product.id === props.id)
);
// Computed selectors
export const selectProductsCount = createSelector(
selectProducts,
(products) => products.length
);
export const selectExpensiveProducts = createSelector(
selectProducts,
(products) => products.filter(product => product.price > 100)
);
Usage in Components:
// product-list.component.ts
@Component({
selector: 'app-product-list',
template: `
<div *ngIf="products$ | async as products">
<div *ngFor="let product of products">
{{ product.name }}
</div>
</div>
`
})
export class ProductListComponent {
products$ = this.store.select(selectProducts);
loading$ = this.store.select(selectProductsLoading);
count$ = this.store.select(selectProductsCount);
constructor(private store: Store) {}
}
68. What is the difference between createSelector and createFeatureSelector?
Answer:
createFeatureSelector selects a feature slice of state:
// product.selectors.ts
export const selectProductState = createFeatureSelector<ProductState>('products');
createSelector creates memoized selectors:
// product.selectors.ts
export const selectProducts = createSelector(
selectProductState,
(state: ProductState) => state.products
);
export const selectProductsLoading = createSelector(
selectProductState,
(state: ProductState) => state.loading
);
Complete Example:
// app.state.ts
export interface AppState {
products: ProductState;
user: UserState;
}
// product.selectors.ts
export const selectProductState = createFeatureSelector<ProductState>('products');
export const selectProducts = createSelector(
selectProductState,
(state: ProductState) => state.products
);
export const selectProductsByCategory = createSelector(
selectProducts,
(products: Product[], props: { category: string }) =>
products.filter(product => product.category === props.category)
);
69. Explain NgRx entities and their benefits
Answer: NgRx entities provide a standardized way to manage collections of objects:
// product.model.ts
export interface Product {
id: number;
name: string;
price: number;
category: string;
}
// product.state.ts
export interface ProductState extends EntityState<Product> {
loading: boolean;
error: string | null;
}
// product.reducer.ts
export const productReducer = createReducer(
initialState,
on(loadProductsSuccess, (state, { products }) =>
adapter.addMany(products, state)
),
on(addProduct, (state, { product }) =>
adapter.addOne(product, state)
),
on(updateProduct, (state, { id, changes }) =>
adapter.updateOne({ id, changes }, state)
),
on(deleteProduct, (state, { id }) =>
adapter.removeOne(id, state)
)
);
// product.selectors.ts
export const {
selectAll: selectAllProducts,
selectEntities: selectProductEntities,
selectIds: selectProductIds,
selectTotal: selectTotalProducts
} = adapter.getSelectors(selectProductState);
export const selectProductById = createSelector(
selectProductEntities,
(entities, props: { id: number }) => entities[props.id]
);
Benefits: - CRUD operations are standardized - Performance optimization with normalized state - Type safety with TypeScript - Reduced boilerplate code
70. What are the differences between NgRx and other state management libraries?
Answer:
| Library | Architecture | Learning Curve | Boilerplate | Use Cases |
|---|---|---|---|---|
| NgRx | Redux-inspired | High | High | Large, complex apps |
| Akita | Object-oriented | Medium | Low | Medium to large apps |
| NGXS | Class-based | Low | Low | Small to medium apps |
| Services | Service-based | Low | Low | Small apps |
Akita Example:
// product.store.ts
@StoreConfig({ name: 'products' })
export class ProductStore extends EntityStore<ProductState> {
constructor() {
super(initialState);
}
}
@Injectable()
export class ProductQuery extends EntityQuery<ProductState> {
constructor(protected store: ProductStore) {
super(store);
}
}
NGXS Example:
// product.state.ts
@State<ProductState>({
name: 'products',
defaults: initialState
})
@Injectable()
export class ProductState {
@Selector()
static products(state: ProductState) {
return state.products;
}
@Action(LoadProducts)
loadProducts(ctx: StateContext<ProductState>) {
return this.productService.getProducts().pipe(
tap(products => ctx.patchState({ products }))
);
}
}
Performance & Optimization
71. Explain Angular performance optimization techniques
Answer: Key Angular performance optimization techniques:
1. OnPush Change Detection:
// optimized.component.ts
@Component({
selector: 'app-optimized',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div>{{ user.name }}</div>
<div>{{ user.email }}</div>
`
})
export class OptimizedComponent {
@Input() user!: User;
}
2. TrackBy Function:
// product-list.component.ts
@Component({
selector: 'app-product-list',
template: `
<div *ngFor="let product of products; trackBy: trackByProductId">
{{ product.name }}
</div>
`
})
export class ProductListComponent {
products: Product[] = [];
trackByProductId(index: number, product: Product): number {
return product.id;
}
}
3. Pure Pipes:
// currency.pipe.ts
@Pipe({
name: 'currency',
pure: true
})
export class CurrencyPipe implements PipeTransform {
transform(value: number, currency: string = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(value);
}
}
4. Lazy Loading:
// app-routing.module.ts
const routes: Routes = [
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
];
72. What are the differences between OnPush and Default change detection?
OnPush allows Angular to skip eligible subtrees. A changed bound input, a handled event, a signal read by the template changing, or an explicit notification such as markForCheck can make a view eligible for checking. Mutating an input object without changing its reference does not by itself mark the child. Do not describe OnPush as “only inputs” or as a guaranteed speedup. Default/eager behavior and scheduling defaults depend on Angular version and whether Zone.js is used. Reference: Zoneless Angular.
73. Explain Angular's trackBy function and its benefits
Answer: TrackBy functions help Angular identify which items have changed in a list:
// product-list.component.ts
@Component({
selector: 'app-product-list',
template: `
<div *ngFor="let product of products; trackBy: trackByProductId">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
</div>
`
})
export class ProductListComponent {
products: Product[] = [];
trackByProductId(index: number, product: Product): number {
return product.id; // Unique identifier
}
// Alternative: track by multiple properties
trackByProduct(index: number, product: Product): string {
return `${product.id}-${product.name}`;
}
}
Benefits: - Performance improvement - Angular reuses DOM elements - Reduced memory usage - No unnecessary DOM recreation - Better user experience - Smoother animations and transitions
74. What is the difference between trackBy and key functions?
Answer:
TrackBy is Angular-specific and used with *ngFor:
// Angular trackBy
@Component({
template: `
<div *ngFor="let item of items; trackBy: trackByFn">
{{ item.name }}
</div>
`
})
export class MyComponent {
trackByFn(index: number, item: any): any {
return item.id;
}
}
Key functions are used in other frameworks (React, Vue):
// React key prop
function ProductList({ products }) {
return (
<div>
{products.map(product => (
<ProductItem key={product.id} product={product} />
))}
</div>
);
}
// Vue key
<template>
<div v-for="product in products" :key="product.id">
{{ product.name }}
</div>
</template>
75. Explain Angular's pure pipes and their optimization
Answer: Pure pipes are optimized pipes that only recalculate when their input changes:
// pure.pipe.ts
@Pipe({
name: 'filter',
pure: true // Default behavior
})
export class FilterPipe implements PipeTransform {
transform(items: any[], filter: string): any[] {
if (!items || !filter) {
return items;
}
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}
}
// impure.pipe.ts
@Pipe({
name: 'impureFilter',
pure: false
})
export class ImpureFilterPipe implements PipeTransform {
transform(items: any[], filter: string): any[] {
// This pipe runs on every change detection cycle
console.log('Impure pipe executed');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}
}
Usage:
// component.ts
@Component({
selector: 'app-product-list',
template: `
<input [(ngModel)]="searchTerm" placeholder="Search products">
<div *ngFor="let product of products | filter:searchTerm">
{{ product.name }}
</div>
`
})
export class ProductListComponent {
products: Product[] = [];
searchTerm = '';
}
76. What are the differences between pure and impure pipes?
Answer:
| Aspect | Pure Pipes | Impure Pipes |
|---|---|---|
| Execution | Only when input changes | Every change detection cycle |
| Performance | Better | Worse |
| Memory Usage | Lower | Higher |
| Use Cases | Data transformation | Real-time data |
Pure Pipe Example:
@Pipe({
name: 'currency',
pure: true
})
export class CurrencyPipe implements PipeTransform {
transform(value: number, currency: string = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(value);
}
}
Impure Pipe Example:
@Pipe({
name: 'asyncData',
pure: false
})
export class AsyncDataPipe implements PipeTransform {
transform(value: Observable<any>): any {
// This pipe needs to run on every change detection
// to handle real-time data updates
return value;
}
}
77. Explain Angular's lazy loading and code splitting
Answer: Lazy loading loads modules only when needed, reducing initial bundle size:
// app-routing.module.ts
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canActivate: [AuthGuard]
}
];
// products-routing.module.ts
const routes: Routes = [
{
path: '',
component: ProductListComponent
},
{
path: ':id',
component: ProductDetailComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductsRoutingModule {}
Preloading Strategy:
// app-routing.module.ts
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})
],
exports: [RouterModule]
})
export class AppRoutingModule {}
78. What is the difference between lazy loading and preloading?
Answer:
Lazy Loading loads modules on-demand:
// Lazy loading - loads only when route is accessed
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
}
Preloading loads modules in the background:
// Custom preloading strategy
@Injectable()
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) {
return load();
}
return of(null);
}
}
// Usage
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule),
data: { preload: true }
}
79. Explain Angular's tree shaking and its benefits
Answer: Tree shaking removes unused code during the build process:
// utils.ts - Tree shakeable
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
}
export function formatDate(date: Date): string {
return date.toLocaleDateString();
}
// component.ts - Only imports what it needs
import { formatCurrency } from './utils';
@Component({
selector: 'app-product',
template: `<div>{{ formattedPrice }}</div>`
})
export class ProductComponent {
formattedPrice = formatCurrency(100);
// formatDate is not imported, so it will be tree shaken
}
Benefits: - Smaller bundle size - Unused code is removed - Faster loading - Less code to download - Better performance - Reduced parsing and execution time
80. What are the differences between development and production builds?
Answer:
| Aspect | Development | Production |
|---|---|---|
| Bundle Size | Larger | Smaller |
| Performance | Slower | Faster |
| Debugging | Source maps | Minified |
| Error Messages | Detailed | Generic |
| Optimizations | Disabled | Enabled |
Development Build:
ng serve
# or
ng build --configuration=development
Production Build:
ng build --configuration=production
# or
ng build --prod
Configuration Differences:
// angular.json
{
"configurations": {
"development": {
"optimization": false,
"sourceMap": true,
"namedChunks": true,
"extractLicenses": false,
"vendorChunk": true,
"buildOptimizer": false
},
"production": {
"optimization": true,
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
}
81. Explain Angular testing frameworks and tools
Test framework and runner choices depend on project generation and configuration. Current Angular CLI documentation uses Vitest for new projects; existing applications may use Jasmine/Karma or another configured runner. TestBed remains Angular’s environment for testing injected services and components. Reference: Angular testing.
82. What are the differences between Jasmine and Jest?
Answer: - Jasmine is the default Angular testing framework, while Jest is a popular alternative. - Jasmine uses its own assertion and mocking library; Jest has built-in mocking, spies, and snapshot testing. - Jest is generally faster due to parallel test execution and better performance optimizations. - Jest provides snapshot testing, which Jasmine lacks. - Jest has a simpler configuration and better watch mode.
Example Jasmine Test:
describe('MyComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
});
Example Jest Test:
test('should create', () => {
expect(component).toBeTruthy();
});
83. Explain component testing and TestBed configuration
Answer: Component testing verifies the behavior and rendering of Angular components in isolation. TestBed is Angular's primary API for configuring and initializing environment for unit tests.
Example:
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { MyComponent } from './my.component';
describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
let component: MyComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MyComponent],
// imports: [OtherModules],
// providers: [Services]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should render title', () => {
const compiled = fixture.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('MyComponent');
});
});
84. What is the difference between TestBed and ComponentFixture?
Answer: - TestBed: Angular's primary API for configuring and initializing the test environment. It sets up modules, components, services, etc. - ComponentFixture: A test harness for interacting with a created component instance. It provides access to the component, its DOM, and methods to trigger change detection.
Example:
const fixture = TestBed.createComponent(MyComponent); // ComponentFixture
const component = fixture.componentInstance; // MyComponent instance
fixture.detectChanges(); // Triggers Angular change detection
85. Explain service testing and mocking strategies
Answer: Service testing involves verifying the logic of Angular services, often in isolation. Mocking strategies include:
- Manual Mocks: Creating fake classes/objects.
- Jasmine Spies: Using
spyOnto mock methods. - TestBed Providers: Overriding providers with mocks.
Example:
class MockDataService {
getData() { return of(['mock', 'data']); }
}
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: DataService, useClass: MockDataService }
]
});
service = TestBed.inject(DataService);
});
86. What are the differences between spyOn and jasmine.createSpy?
Answer: - spyOn: Attaches a spy to an existing object method. - jasmine.createSpy: Creates a standalone spy function, not attached to any object.
Example:
// spyOn
spyOn(service, 'getData').and.returnValue(of([]));
// jasmine.createSpy
const fakeMethod = jasmine.createSpy('fakeMethod').and.returnValue('value');
87. Explain end-to-end testing with Protractor/Cypress
Protractor is a legacy, discontinued Angular end-to-end testing tool. Use a maintained browser testing tool such as Playwright or Cypress for new tests, and test user-visible behavior, navigation, and API integration. Tests need deterministic data and explicit waiting for observable UI conditions.
88. What is the difference between unit testing and e2e testing?
Answer: - Unit Testing: Tests individual units (components, services) in isolation, fast, run in memory. - E2E Testing: Tests the entire application as a user would, slower, runs in a real browser, covers integration between components.
89. Explain Angular testing utilities and their usage
Answer: Angular provides utilities to facilitate testing:
- TestBed: Configures and initializes the test environment.
- ComponentFixture: Handles component instance and DOM.
- async/fakeAsync/tick: Handles asynchronous code in tests.
- inject: Injects dependencies into tests.
Example:
it('should wait for async', fakeAsync(() => {
let value = false;
setTimeout(() => value = true, 1000);
tick(1000);
expect(value).toBeTrue();
}));
90. What are the differences between fakeAsync and async?
The historical Angular testing helper async was renamed waitForAsync. waitForAsync coordinates asynchronous work in a test zone; fakeAsync and tick simulate time in the Zone.js testing environment. They are not interchangeable with JavaScript async/await, and fakeAsync is not available with every runner configuration. In a Vitest-based setup, use the supported native async and fake-timer facilities for that project. Reference: Angular testing.
91. Explain Angular's AOT compilation and its benefits
Answer: AOT (Ahead-of-Time) compilation compiles Angular HTML and TypeScript code into efficient JavaScript during build time, before the browser downloads and runs the code.
Benefits: - Faster rendering (no runtime compilation) - Smaller bundle size - Early template error detection - Improved security (no template injection)
92. What are the differences between JIT and AOT compilation?
Answer: - JIT (Just-in-Time): Compiles templates in the browser at runtime. Useful for development. - AOT (Ahead-of-Time): Compiles templates at build time. Used for production.
| Feature | JIT | AOT |
|---|---|---|
| Compilation | Runtime (browser) | Build time |
| Speed | Slower | Faster |
| Error Detection | At runtime | At build time |
| Bundle Size | Larger | Smaller |
93. Explain Angular's internationalization (i18n) features
Answer: Angular provides built-in i18n support for translating applications:
- i18n Attributes: Mark text for translation in templates.
- Angular CLI: Extracts translatable strings.
- Translation Files: XLIFF, XMB, JSON formats.
- Runtime/Build-time Localization: Replace text based on locale.
Example:
<h1 i18n="site header|An introduction header">Welcome</h1>
94. What is the difference between i18n and l10n?
Answer: - i18n (Internationalization): Process of designing an app to support multiple languages/locales. - l10n (Localization): Adapting the app for a specific language/region (translating text, formatting dates, etc.).
95. Explain Angular's PWA features and service workers
Answer:
Angular supports Progressive Web Apps (PWA) via the @angular/pwa package:
- Service Workers: Enable offline support, caching, background sync.
- Manifest File: Defines app icon, theme, etc.
- Push Notifications: Via service workers.
Example:
ng add @angular/pwa
This adds service worker configuration and manifest to your project.
96. What are the differences between service workers and web workers?
Answer: - Service Workers: Proxy between web app and network, handle caching, push notifications, run in background, enable offline. - Web Workers: Run JavaScript in background threads for heavy computation, do not have access to DOM or network.
97. Explain Angular's server-side rendering (SSR) with Angular Universal
Angular Universal is the historical name commonly used for Angular server rendering. Current Angular SSR uses the @angular/ssr integration. Avoid browser-only globals during server rendering; hydrate with consistent server/client output. Match setup commands to the actual Angular version.
Answer: Angular Universal enables SSR, rendering Angular apps on the server:
- Improves initial load performance and SEO
- Renders HTML on server, sends to client, then bootstraps Angular app
Example:
ng add @nguniversal/express-engine
This sets up SSR with Express.
98. What is the difference between SSR and CSR?
Answer: - SSR (Server-Side Rendering): HTML is rendered on the server, sent to client. Faster first paint, better SEO. - CSR (Client-Side Rendering): HTML is rendered in the browser using JavaScript. Slower initial load, but faster navigation after load.
99. Explain Angular's micro-frontend architecture
Answer: Micro-frontends split a large frontend app into smaller, independently deployable apps (micro-apps), each owned by different teams.
- Each micro-frontend can be developed, tested, and deployed independently.
- Integration via module federation, custom elements, or iframes.
100. What are the differences between micro-frontends and monolithic applications?
Answer: - Micro-frontends: Multiple independent apps, separate deployments, technology agnostic, better scalability, team autonomy. - Monolithic: Single codebase, tightly coupled, harder to scale and maintain as teams grow.