Q1. What are the key differences between Angular and AngularJS?
Angular and AngularJS are fundamentally different frameworks. AngularJS (1.x) is a JavaScript framework for building dynamic web applications with two-way data binding, while Angular (2+) is a complete rewrite using TypeScript as the primary language. Angular provides superior performance through AOT compilation, better modularity with NgModules, improved change detection mechanism, dependency injection improvements, and reactive programming support through RxJS. AngularJS uses controller-based architecture, whereas Angular uses component-based architecture where components are the fundamental building blocks. Angular also offers better tooling, more structured routing, reactive forms, and built-in HTTP client with interceptor support. The migration from AngularJS to Angular requires significant refactoring as they are incompatible frameworks. Angular receives regular updates and long-term support, while AngularJS is no longer actively maintained.
Q2. Explain Angular architecture with its main building blocks.
Angular architecture follows a component-based, modular design pattern built around several core concepts. The main building blocks are Modules (NgModule), Components, Services, Directives, Pipes, and Dependency Injection. Modules bundle related components, services, directives, and pipes together, with the root module (AppModule) being the entry point. Components are the basic UI building blocks, each with its own template, styles, and logic. Services encapsulate business logic and are shared across components through Dependency Injection. Directives extend HTML with custom attributes and element behaviors, while Pipes transform data for display. Angular uses a hierarchical injector for dependency injection, managing component and service dependencies. The framework leverages TypeScript for type safety, RxJS for reactive programming, and a powerful router for navigation. This architecture promotes code reusability, testability, maintainability, and scalability.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Q3. What is a Component in Angular? How is it different from a Module?
A Component in Angular is a TypeScript class decorated with @Component that controls a view or section of the user interface. Each component encapsulates its own template (HTML), styles (CSS), and logic (TypeScript), making it a self-contained, reusable unit. Components are the fundamental building blocks of Angular applications and can be nested to create complex UIs. A Module (NgModule), on the other hand, is a container for organizing components, services, directives, and pipes into cohesive units. Modules provide the compilation context for components and manage dependencies through the Dependency Injection system. While a component represents a specific UI element with behavior, a module groups related components and other features together for better code organization. Every Angular application must have at least one module (the root AppModule), which bootstraps the root component (AppComponent). Components are typically declared within a module using the declarations array, and the module controls what is exposed to other modules via the exports array.
Q4. What is the @NgModule decorator and what are its important properties?
The @NgModule decorator is used to mark a class as an Angular module and provides metadata that determines how the module's components, directives, and pipes are compiled and used at runtime. The key properties of @NgModule include declarations (array of components, directives, and pipes that belong to this module), imports (array of modules whose components, directives, and pipes are needed), providers (array of services available within this module and its child components), exports (array of declarations that should be visible to other modules that import this module), and bootstrap (array of root components to load when the module is bootstrapped, typically only used in the root AppModule). Additional properties include entryComponents (in older Angular versions) for components not referenced in templates, and schemas for allowing non-Angular elements. The declarations array is crucial as only declared components can be used within the module's templates. The imports array is used to include dependencies from other modules, allowing access to their exported components and services. Understanding these properties is essential for proper module organization and dependency management in Angular applications.
Q5. What are the different types of data binding in Angular?
Angular supports four types of data binding, each serving different purposes in establishing communication between the component class and the view. Interpolation ({{}}) is one-way binding from component to template, allowing you to display component property values in the HTML. Property binding ([property]) enables one-way binding where you can bind component properties to DOM element properties or directive properties. Event binding ((event)) is one-way binding from the template to the component, capturing user events like clicks and button submissions. Two-way binding ([(ngModel)]) combines property binding and event binding, automatically synchronizing data between the component and the template - when the component property changes, the template updates, and when the user modifies the template input, the component property updates. Two-way binding requires importing FormsModule and is commonly used with form controls. These binding mechanisms are fundamental to Angular's reactivity and enable dynamic, responsive user interfaces. Understanding when to use each type is crucial for building efficient Angular applications and managing data flow effectively.
// Component TypeScript
export class BindingComponent {
name: string = 'Angular';
isActive: boolean = true;
onButtonClick() {
alert('Button clicked!');
}
}
// Component Template
<div>
<!-- Interpolation: Component to Template -->
<p>{{ name }}</p>
<!-- Property Binding: Component to Template -->
<button [disabled]="!isActive">Click Me</button>
<!-- Event Binding: Template to Component -->
<button (click)="onButtonClick()">Submit</button>
<!-- Two-way Binding: Bidirectional -->
<input [(ngModel)]="name" />
</div>
Q6. Explain Angular template syntax. What are *ngIf, *ngFor, and *ngSwitch?
Angular template syntax provides powerful directives for manipulating the DOM dynamically based on component logic. The *ngIf structural directive conditionally removes or recreates an element from the DOM based on a boolean expression, with optional else clause for alternative content. The *ngFor structural directive repeats an element for each item in an iterable collection, maintaining a local loop variable accessible within the template. The *ngSwitch directive provides conditional branching similar to JavaScript switch statements, used in conjunction with *ngSwitchCase and *ngSwitchDefault. The asterisk (*) prefix on these directives indicates they are structural directives that modify the DOM structure, as opposed to attribute directives that only modify element attributes. These directives are essential for building dynamic, responsive user interfaces that react to component state changes. The trackBy function with *ngFor improves performance by helping Angular identify which items have changed, preventing unnecessary DOM manipulation. Angular also provides [ngIf], [ngFor], and [ngSwitch] microsyntax alternatives, though the structural directive format is preferred for clarity.
// Component TypeScript
export class DirectiveComponent {
users = [
{ id: 1, name: 'John', role: 'Admin' },
{ id: 2, name: 'Jane', role: 'User' },
{ id: 3, name: 'Bob', role: 'Guest' }
];
showDetails = false;
selectedRole = 'Admin';
}
// Component Template
<!-- ngIf example -->
<div *ngIf="showDetails; else noDetails">
<p>User details are visible</p>
</div>
<ng-template #noDetails>
<p>No details</p>
</ng-template>
<!-- ngFor example -->
<ul>
<li *ngFor="let user of users; trackBy: trackByUserId">
{{ user.name }} - {{ user.role }}
</li>
</ul>
<!-- ngSwitch example -->
<div [ngSwitch]="selectedRole">
<p *ngSwitchCase="'Admin'">Administrator privileges</p>
<p *ngSwitchCase="'User'">Regular user privileges</p>
<p *ngSwitchDefault>Guest privileges</p>
</div>
Q7. What are Angular Component Lifecycle Hooks? List them in execution order.
Angular component lifecycle hooks are interfaces that allow you to tap into specific moments in a component's lifetime and execute custom code. The lifecycle begins when Angular instantiates the component class, progresses through change detection and rendering, and ends when Angular destroys the component. The hooks execute in a specific order: ngOnChanges (called before ngOnInit and whenever input properties change), ngOnInit (called once after Angular initializes the component and its inputs), ngDoCheck (called during every change detection run), ngAfterContentInit (called after Angular projects external content into the component), ngAfterContentChecked (called after ngAfterContentInit and during every change detection), ngAfterViewInit (called after Angular initializes the component's view), ngAfterViewChecked (called after ngAfterViewInit and during every change detection), and ngOnDestroy (called just before Angular destroys the component, used for cleanup). Each hook is implemented by writing a method in the component class that matches the hook interface name. Common hooks are ngOnInit for initialization, ngOnChanges for reacting to input property changes, and ngOnDestroy for cleanup like unsubscribing from observables and canceling timers. Understanding the execution order is critical for proper component initialization and state management.
Q8. Explain the ngOnInit and ngOnDestroy lifecycle hooks with use cases.
The ngOnInit hook is called once after Angular has initialized all data-bound properties of the component and before the component view is first displayed. This is the ideal place for initializing component state, fetching data from APIs, setting up subscriptions to observables, and performing other setup operations that require the component to be ready. ngOnInit is typically where you call service methods to load data and configure the component for display. The ngOnDestroy hook is called just before Angular destroys the component and removes it from the DOM. This is essential for cleanup operations to prevent memory leaks and resource wastage. Common cleanup tasks include unsubscribing from observables using takeUntil or subscription.unsubscribe(), canceling timers using clearTimeout or clearInterval, removing event listeners, closing WebSocket connections, and releasing other external resources. Implementing OnDestroy interface ensures proper resource management. Proper use of these hooks is crucial for building stable, performant Angular applications that don't leak memory or hold onto unnecessary resources after components are destroyed.
Q9. What is ngOnChanges lifecycle hook? When is it called?
The ngOnChanges hook is called when Angular detects changes to input properties (@Input decorated properties) of a component. It's called before ngOnInit on the initial initialization, and then again whenever one or more input properties change. The hook receives a SimpleChanges object that contains information about which inputs have changed, including their previous and current values, and whether it's the first time they're being set. ngOnChanges is particularly useful when a component needs to react to changes in its input data, perform validation, compute derived properties, or trigger updates based on new input values. This hook is essential in parent-child component communication where the parent component passes data to the child component through @Input properties. The SimpleChanges object provides previousValue and currentValue for each changed property, allowing you to compare old and new values. However, ngOnChanges only detects reference changes for objects and arrays, not deep property mutations, so if you need to detect changes to nested object properties, you may need additional logic. Understanding ngOnChanges is critical for building reactive components that properly handle input property updates.
Q10. What are ngAfterViewInit and ngAfterViewChecked hooks?
The ngAfterViewInit hook is called after Angular initializes the component's view and the views of its child components. This is the first opportunity to access the component's view elements through ViewChild and ViewChildren decorators. ngAfterViewInit is useful when you need to interact with DOM elements after they're rendered, access child components, initialize third-party libraries that depend on DOM elements, or set focus on form inputs. The ngAfterViewChecked hook is called after Angular checks the component's view for changes, which happens after ngAfterViewInit and during every subsequent change detection cycle. This hook is useful for performing operations that need to run after the view has been fully updated with the latest data. Both hooks are called after the content and view initialization/checking phases, making them appropriate for DOM-related operations. A critical rule when using these hooks is to avoid changing component properties in these methods, as this can cause ExpressionChangedAfterItHasBeenCheckedError. If you need to make changes, you should do so in ngOnInit or use ChangeDetectorRef.detectChanges() carefully. These hooks are less frequently used than ngOnInit but are essential when working with template references and view queries.
Q11. What are ngAfterContentInit and ngAfterContentChecked hooks?
The ngAfterContentInit hook is called after Angular has projected external content into the component using ng-content. This is the first opportunity to access projected content through ContentChild and ContentChildren decorators. ngAfterContentInit is useful when you need to interact with projected content, initialize components that depend on external content, or perform operations based on the projected elements. The ngAfterContentChecked hook is called after Angular checks the projected content for changes, occurring after ngAfterContentInit and during every subsequent change detection cycle. This hook allows you to respond to changes in the projected content and update component behavior accordingly. The key difference between these hooks and ngAfterViewInit/ngAfterViewChecked is that content hooks deal with projected content (ng-content), while view hooks deal with the component's own template. Content projection is a powerful pattern for creating flexible, reusable components that accept arbitrary HTML content from parent components. Understanding when content is projected and how to interact with it is crucial for building sophisticated component compositions. Like the view hooks, you should avoid changing component properties in these hooks to prevent change detection errors.
Q12. What is a Service in Angular and how does Dependency Injection work?
A Service in Angular is a class with a specific purpose and responsibility, typically encapsulating reusable business logic that can be shared across multiple components. Services are used to fetch data from APIs, perform calculations, manage state, handle authentication, logging, and other cross-cutting concerns. Services promote code reusability, maintainability, and testability by separating business logic from presentation logic. Dependency Injection (DI) is an architectural pattern where a class receives its dependencies from external sources rather than creating them internally. Angular has a built-in DI container (Injector) that creates instances of services and injects them into components that need them. Components declare their service dependencies through constructor parameters, and Angular automatically provides the required instances. This loose coupling between components and services makes the application more flexible and testable - you can easily swap service implementations for testing using mock services. Services are typically decorated with @Injectable() decorator, which marks them as a service that can be injected. The providedIn property in @Injectable() decorator allows you to specify at which level the service should be provided (root level, specific module, or component level), determining the service's scope and lifetime.
// Service
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient) { }
getUsers(): Observable<any[]> {
return this.http.get<any[]>('/api/users');
}
}
// Component with Dependency Injection
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-user-list',
template: '<div>Users: {{ users | async | json }}</div>'
})
export class UserListComponent implements OnInit {
users: Observable<any[]>;
constructor(private userService: UserService) { }
ngOnInit() {
this.users = this.userService.getUsers();
}
}
Q13. What is @Injectable decorator and what does providedIn property mean?
The @Injectable() decorator marks a class as a service that can be injected as a dependency in other classes. It signals to Angular that this class is part of the dependency injection system. The providedIn property is a part of the @Injectable() configuration that specifies at which level the service should be provided and determines the service's scope and singleton lifetime. When providedIn is set to 'root', the service is provided at the root level of the application and shares a single instance across the entire application, making it a true singleton. This is the recommended approach for most services and is more efficient than providing services in modules. Alternatively, you can set providedIn to a specific module name to restrict the service to that module, or omit providedIn and provide the service in a module's providers array instead. Setting providedIn: 'root' in the @Injectable() decorator has the advantage of enabling tree-shaking - if a service is not used, it will be removed from the final bundle during production builds. You can also provide services at the component level by including them in the component's providers array, which creates a new instance of the service for each component instance. Understanding the proper scope and lifecycle of services is crucial for managing state and ensuring efficient memory usage in Angular applications.
Q14. Explain hierarchical injector in Angular. What is the order of service resolution?
The hierarchical injector in Angular creates a hierarchy of injectors that corresponds to the component tree structure. Each component has its own injector instance, and these injectors are organized in a parent-child hierarchy mirroring the component hierarchy. When a component requests a service, Angular searches for it in the component's own injector first, then moves up the hierarchy to parent component injectors, module injectors, and finally the root application injector. This hierarchical approach allows for different scopes and instances of services at different levels. Services provided at the root level are singletons shared across the entire application, while services provided at the module level are shared within that module and its children, and services provided at the component level create a new instance for each component instance. The resolution order is: component injector, parent component injector, parent module injector, platform injector, and null injector. Understanding the hierarchical nature of injectors is essential for managing service lifecycle and avoiding unexpected behavior with shared state. You can leverage this hierarchy to have different service instances at different levels - for example, having a shared AuthService singleton at the root level while having component-specific state services at the component level. This flexibility makes Angular's DI system powerful for building scalable, maintainable applications.
Q15. How do you prevent multiple instances of a service from being created?
To ensure that only a single instance of a service is created and shared throughout the application, you should use providedIn: 'root' in the @Injectable() decorator. This makes the service a true singleton at the application root level, regardless of how many components request it. This is the most straightforward and recommended approach for creating singleton services in Angular. Providing the service in the root module (AppModule) providers array also creates a singleton, but providedIn: 'root' is preferred because it enables tree-shaking optimization - if the service is never actually used, it will be removed from the production bundle. You should avoid providing services at the component level in the component's providers array, as this creates a new instance for each component instance. Similarly, providing services at the module level in the module's providers array limits the service to that module's scope. When multiple providers of the same service exist at different levels, the most specific injector wins - component level takes precedence over module level, which takes precedence over root level. Common singleton services include AuthService, HttpService, StateManagementService, and other application-level services that should maintain a single state. Using providedIn: 'root' ensures predictable behavior and helps prevent hard-to-debug issues caused by multiple service instances.
Q16. What is the Angular Router? How do you configure it?
The Angular Router is a service that enables navigation between different views or components based on browser history and URL changes. It allows you to build single-page applications where the URL changes and different components are displayed without full page reloads. The router maintains browser history, allowing users to use the back and forward buttons naturally. Configuration of routing involves creating a routing module using RouterModule.forRoot() in the root module to define routes, and RouterModule.forChild() in feature modules for nested routes. Routes are defined as an array of route objects with properties like path (URL path), component (component to display), and children (nested routes). The router uses RouterOutlet directive in templates to designate where routed components should be displayed. RouterLink directive enables navigation in templates by binding to routes, while ActivatedRoute service provides access to route parameters and query strings. The router also supports lazy loading to improve performance by loading feature modules only when needed, wildcards for catch-all routes, and guards for protecting routes with authentication and authorization logic. Understanding routing is essential for building multi-page-like experiences in single-page applications and managing complex navigation flows effectively.
Q17. Explain RouterModule, RouterOutlet, RouterLink, and ActivatedRoute.
RouterModule is an Angular module that provides routing capabilities and is imported in application modules to enable routing. It provides directives and services needed for navigation and route handling. RouterOutlet is a directive that serves as a placeholder in templates where routed components are rendered. When a route matches, the corresponding component is instantiated and displayed in the RouterOutlet placeholder. Only one routed component can be displayed in a single RouterOutlet, though you can have multiple RouterOutlets in a template for complex layouts with named outlets. RouterLink is a directive that enables declarative navigation in templates by binding to route paths. When clicked, RouterLink navigates to the specified route without triggering a full page reload. RouterLink accepts the route path as input and automatically handles the navigation. It's also useful for styling the active link using routerLinkActive directive, allowing you to highlight the currently active route. ActivatedRoute is a service that provides information about the route associated with a component being displayed. It grants access to route parameters (from the URL path), query parameters (from the URL query string), and other route-specific data. You inject ActivatedRoute into components to retrieve route information needed for component initialization or to respond to route changes. These four concepts work together to enable client-side routing in Angular applications, providing a seamless navigation experience.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { UserComponent } from './user/user.component';
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'user/:id', component: UserComponent },
{ path: '**', redirectTo: '/home' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// app.component.html - Template with RouterOutlet and RouterLink
<nav>
<a routerLink="/home" routerLinkActive="active">Home</a>
<a routerLink="/user/123" routerLinkActive="active">User</a>
</nav>
<router-outlet></router-outlet>
// user.component.ts - Using ActivatedRoute
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user',
template: '<div>User ID: {{ userId }}</div>'
})
export class UserComponent implements OnInit {
userId: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.params.subscribe(params => {
this.userId = params['id'];
});
}
}
Q18. How do you handle route parameters and query parameters in Angular?
Route parameters are dynamic parts of the URL path defined in the route configuration using a colon prefix (e.g., /user/:id). When a route matches, the parameter values are extracted from the URL and can be accessed through ActivatedRoute.params observable. Query parameters are optional parameters in the URL query string (e.g., /users?sort=name&order=asc) and are accessed through ActivatedRoute.queryParams. Both are accessed as observables, allowing components to react to parameter changes. In Angular, you subscribe to these observables in ngOnInit or use the async pipe in templates to automatically handle subscriptions and unsubscriptions. The params and queryParams are case-sensitive, and you must unsubscribe from the subscription in ngOnDestroy to prevent memory leaks, or better yet, use takeUntil pattern with async pipe. Route parameters are typically required for identifying specific resources (like a user or product ID), while query parameters are optional and used for filtering, sorting, or pagination. For better performance and cleaner code, you can use the snapshot property (ActivatedRoute.snapshot.params) if you only need the initial parameter values and don't need to respond to subsequent parameter changes. Understanding parameter handling is essential for building interactive applications that navigate to different views based on user selections and maintain navigation state.
// Route configuration
const routes: Routes = [
{ path: 'products/:id', component: ProductDetailComponent },
{ path: 'users', component: UserListComponent }
];
// Component with route parameters
export class ProductDetailComponent implements OnInit, OnDestroy {
productId: string;
private destroy$ = new Subject<void>();
constructor(private route: ActivatedRoute) { }
ngOnInit() {
// Approach 1: Subscribe to params observable
this.route.params
.pipe(takeUntil(this.destroy$))
.subscribe(params => {
this.productId = params['id'];
});
// Approach 2: Use snapshot for initial value
// this.productId = this.route.snapshot.params['id'];
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
// Component with query parameters
export class UserListComponent implements OnInit {
sortBy: string = 'name';
order: string = 'asc';
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.queryParams
.subscribe(queryParams => {
this.sortBy = queryParams['sort'] || 'name';
this.order = queryParams['order'] || 'asc';
});
}
}
Q19. What are child routes and lazy loading in Angular? How do you implement them?
Child routes, also known as nested routes, allow you to define a hierarchy of routes where some routes are nested under parent routes. Child routes are configured in the children property of a parent route, and they extend the parent route's path. For example, if the parent route is /admin and the child route is /dashboard, the full path becomes /admin/dashboard. Child routes share the parent component's template and use a secondary RouterOutlet for displaying the child component. This is useful for building complex layouts with multiple levels of navigation. Lazy loading is a performance optimization technique where feature modules are loaded asynchronously only when needed, rather than loading everything upfront. Instead of importing a feature module directly in the main app module, you configure a route to load the module dynamically using loadChildren property. This reduces the initial bundle size and improves application startup time. When a user navigates to a lazy-loaded route for the first time, the feature module is downloaded and compiled. Implementing lazy loading involves configuring routes with loadChildren pointing to the feature module's routing module, using path-based syntax (e.g., 'feature#FeatureModule'). Lazy-loaded modules can have their own feature modules and child routes, creating flexible, scalable application architectures. Combining child routes with lazy loading enables efficient code organization and optimal performance for large, complex applications.
Q20. What are route guards? Explain CanActivate, CanDeactivate, and Resolve guards.
Route guards are services that implement specific guard interfaces and control whether a route can be activated or deactivated, providing authentication and authorization mechanisms. CanActivate guard determines if a user can access a route, commonly used to prevent unauthorized access to protected routes. It's useful for checking if a user is authenticated and has required permissions before allowing navigation to a route. CanDeactivate guard determines if a user can leave a route, useful for preventing accidental navigation away from forms with unsaved changes. It can prompt users to confirm they want to leave or save changes before navigating away. Resolve guard pre-fetches data before the route is activated, ensuring that all required data is available when the component is initialized, improving user experience by preventing empty views. Other guards include CanLoad for preventing lazy-loaded module access, and CanMatch for matching routes based on custom logic. Guards implement the corresponding interface and return a boolean, UrlTree, or Observable/Promise that resolves to a boolean. Returning true allows navigation, false prevents it, and a UrlTree allows redirecting to a different route. Guards can be applied to routes in the route configuration using canActivate, canDeactivate, and resolve properties. Guards are essential for building secure applications with proper authentication and authorization, and for managing navigation flows effectively.
Q21. What are Reactive Forms and Template-driven Forms in Angular?
Angular offers two approaches for handling forms: Reactive Forms (also called Model-driven Forms) and Template-driven Forms. Reactive Forms are built around explicit form control objects created programmatically in the component class using FormBuilder or FormControl/FormGroup classes. In Reactive Forms, the form model is the source of truth, and form controls are synchronized to the template using directives like formControl and formGroup. Reactive Forms offer more control, flexibility, and are well-suited for complex forms with dynamic controls, custom validators, and advanced features. They're easier to test since the form model is in the component class and doesn't depend on the template. Reactive Forms require ReactiveFormsModule import. Template-driven Forms rely on directives like ngModel in the template to build the form model implicitly. The template is the source of truth, and Angular automatically creates FormControl and FormGroup objects behind the scenes. Template-driven Forms are simpler for basic forms but less flexible for complex scenarios. They're good for simple forms where you don't need extensive validation or dynamic control creation. Template-driven Forms require FormsModule import. Choosing between them depends on form complexity, testing requirements, and developer preference. Most production applications favor Reactive Forms for their superior control and testability, though Template-driven Forms are suitable for simple forms. Understanding both approaches and when to use each is essential for effective form handling in Angular.
Q22. What is FormBuilder, FormGroup, and FormControl?
FormControl represents a single form field and tracks its value and validation state. Each FormControl has properties like value (current value), status (valid/invalid), errors (validation errors), and touched (whether the user has interacted with it). FormGroup is a collection of FormControls grouped together, representing the entire form or a section of it. FormGroups track the overall form status based on their child controls, allowing you to validate the form as a whole. FormBuilder is a service that simplifies creating FormGroup and FormControl instances with less boilerplate code. Instead of manually instantiating FormGroup and FormControls, you use FormBuilder.group() to define form structure in a more declarative way. FormBuilder accepts an object where keys are control names and values are default values or validation configurations. These are fundamental building blocks for creating Reactive Forms in Angular. FormControl and FormGroup can be created directly, but FormBuilder is preferred for cleaner, more maintainable code. FormGroup provides methods for form-level operations like get() to retrieve specific controls, setErrors() for setting errors, patchValue() for partial updates, and reset() for clearing form values. Understanding these classes and how they work together is essential for building robust, validated forms in Angular applications.
Q23. What are Validators in Angular forms? How do you use built-in and custom validators?
Validators are functions that check whether form control values meet specific criteria and return null if valid or an object of validation errors if invalid. Angular provides built-in validators like Validators.required, Validators.minLength, Validators.maxLength, Validators.pattern, Validators.email, Validators.min, Validators.max, and Validators.requiredTrue. You apply validators when creating form controls by passing them as the second argument to FormControl or in the validation configuration object within FormBuilder.group(). You can apply single or multiple validators by passing them in an array. Custom validators are functions that implement custom validation logic beyond built-in validators. A custom validator function takes a FormControl as input and returns an object with validation errors (if validation fails) or null (if validation passes). Custom validators are particularly useful for business logic validation like checking if a username is already taken, comparing two field values, or validating complex conditions. Async validators perform validation asynchronously (e.g., checking server availability or validating username uniqueness through API calls). They return an Observable or Promise that resolves to validation errors or null. Async validators are applied as the third argument to FormControl or in the asyncValidators property in FormBuilder.group(). Validation errors are accessed through FormControl.errors property, which returns an object with validation error keys. This information is useful for displaying appropriate error messages to users. Proper validation ensures data integrity and improves user experience by providing immediate feedback about form errors.
Q24. How do you create custom validators in Angular Reactive Forms?
Custom validators are functions that implement custom validation logic and return validation errors or null. Creating a custom validator involves writing a function that takes a FormControl as parameter and returns a ValidationErrors object or null. For single control validation, the validator function directly accesses the control's value and performs the validation logic. For cross-field validation (comparing multiple fields), you create a validator function that takes a FormGroup as parameter instead. Common patterns include password confirmation validators that ensure two password fields match, validators that check API availability, and business logic validators. Async validators are created similarly but return an Observable or Promise that eventually resolves to ValidationErrors or null, useful for server-side validation. You can organize custom validators in a separate file or class for reusability across the application. Applying custom validators is the same as built-in validators - pass them as the second argument to FormControl or include them in FormBuilder.group(). The validator function should be pure and not have side effects. For better code organization and readability, you can create a custom validator class with static methods that return validator functions. This pattern is useful for creating validators that require configuration parameters. Testing custom validators is straightforward since they're pure functions - you can test them independently of the component. Proper custom validators are essential for implementing business logic validation and creating forms that enforce application-specific rules.
// Custom single-control validator
export function passwordStrengthValidator(
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 hasNumber = /[0-9]/.test(value);
const isLengthValid = value.length >= 8;
const passwordValid = hasUpperCase && hasLowerCase &&
hasNumber && isLengthValid;
return !passwordValid ?
{ weakPassword: true } : null;
}
// Custom cross-field validator
export function passwordMatchValidator(
group: FormGroup
): ValidationErrors | null {
const password = group.get('password')?.value;
const confirmPassword = group.get('confirmPassword')?.value;
return password === confirmPassword ?
null : { passwordMismatch: true };
}
// Async validator for checking username availability
export function usernameAvailabilityValidator(
userService: UserService
): AsyncValidatorFn {
return (control: AbstractControl):
Observable<ValidationErrors | null> => {
if (!control.value) {
return of(null);
}
return userService.checkUsernameAvailability(control.value)
.pipe(
map(isAvailable =>
isAvailable ? null : { usernameTaken: true }
),
catchError(() => of(null))
);
};
}
// Using custom validators in FormBuilder
const form = this.fb.group({
username: ['', [Validators.required],
[usernameAvailabilityValidator(this.userService)]],
password: ['', [Validators.required,
passwordStrengthValidator]],
confirmPassword: ['', Validators.required]
}, { validators: passwordMatchValidator });
Q25. What is the difference between patchValue and setValue in Angular forms?
Both patchValue and setValue are methods used to update FormControl or FormGroup values, but they differ in their behavior. setValue updates the entire form or control value and requires all form controls to be specified - if you omit any control, it throws an error. This strict behavior ensures that you provide values for all controls in the form. patchValue, on the other hand, only updates the specified controls and doesn't require values for all controls - any unspecified controls retain their current values. This makes patchValue more flexible and convenient for partial updates. In most real-world scenarios, patchValue is preferred because you typically update only certain form fields rather than the entire form. For FormGroup, setValue requires all child controls to be specified, while patchValue allows partial updates, making it ideal for scenarios like loading data from the server and updating only specific fields. Both methods trigger validation and update the form's status appropriately. When using either method, you typically want to pass an object where keys correspond to control names and values are the new values. It's important to use these methods appropriately to avoid runtime errors and to handle partial updates cleanly. Understanding the distinction between these methods is crucial for effectively managing form state and handling user interactions and data updates in Angular applications.
Q26. What are Pipes in Angular? How do you use built-in pipes?
Pipes are Angular features that transform data for display purposes in templates using the pipe operator (|). They're particularly useful for formatting dates, numbers, currencies, and text without modifying the underlying component data. Built-in pipes in Angular include DatePipe for formatting dates, CurrencyPipe for displaying currency values, UpperCasePipe and LowerCasePipe for text case transformation, NumberPipe for number formatting, DecimalPipe for decimal number formatting, PercentPipe for percentage display, SlicePipe for extracting portions of arrays or strings, JsonPipe for displaying object JSON representation, KeyValuePipe for iterating over object properties, and AsyncPipe for handling asynchronous operations like observables and promises. Pipes can be chained together to apply multiple transformations in sequence, for example: {{ date | date:'short' | uppercase }}. Most pipes accept parameters using a colon syntax to customize their behavior, such as {{ price | currency:'USD' }}. Pipes operate on the template level and don't modify the underlying data in the component, keeping the component logic clean. Using built-in pipes is often more efficient than manually formatting data in the component class. The AsyncPipe is particularly powerful as it automatically handles subscription and unsubscription from observables, preventing memory leaks. Understanding pipes and their parameters is essential for creating well-formatted, user-friendly Angular templates.
Q27. What are custom pipes? How do you create and use them?
Custom pipes are user-defined pipes that implement custom transformation logic for specific application needs. Creating a custom pipe involves creating a class that implements the PipeTransform interface and decorated with @Pipe decorator. The class must implement the transform method, which receives the input value and any optional parameters, and returns the transformed value. The @Pipe decorator specifies the pipe name used in templates. Custom pipes are declared in a module's declarations array to make them available to components. For example, you might create a custom pipe to format phone numbers, truncate text, filter arrays, or implement domain-specific transformations. Custom pipes should be pure (deterministic) by default, meaning the same input always produces the same output and the transformation doesn't have side effects. However, you can create impure pipes by setting pure: false in the @Pipe decorator - impure pipes are called for every change detection cycle, which can impact performance. Common scenarios for custom pipes include formatting data specific to your business domain, translating values, filtering arrays, and converting between units. Custom pipes can accept parameters just like built-in pipes using the pipe operator syntax. Creating reusable custom pipes improves code organization and template readability. Pipes should be focused on simple data transformations rather than complex business logic, keeping the separation of concerns between templates and component logic.
Q28. What is the difference between pure and impure pipes?
Pure pipes are stateless transformations that always return the same output for the same input, regardless of how many times they're called. Angular optimizes pure pipes by only re-executing them when the input reference changes or when primitive input values change. Pure pipes are the default and are preferred for performance because they're only re-evaluated when necessary. Most pipes should be pure to maintain good application performance. Impure pipes, created by setting pure: false in the @Pipe decorator, are re-executed during every change detection cycle, even if the inputs haven't changed. This makes them less performant but necessary for pipes that need to respond to changes beyond input value modifications, such as pipes that depend on external state or current time. Examples of impure pipes include AsyncPipe (observes observable emissions), pipes that need to reflect real-time data, and pipes that depend on application state. Using impure pipes extensively can significantly impact application performance because they're evaluated on every change detection cycle. It's important to understand when a pipe needs to be impure and to use pure pipes whenever possible. If you find yourself needing an impure pipe, consider whether the logic could be moved to the component instead. The distinction between pure and impure pipes is critical for writing performant Angular applications, particularly in large applications with complex change detection scenarios.
Q29. How do you build a custom pipe with parameters?
Custom pipes with parameters extend the transform method to accept additional arguments beyond the input value. The transform method signature includes the input value as the first parameter, followed by additional parameters with their types. When using the pipe in a template, you pass parameters using colon-separated syntax after the pipe name, e.g., {{ value | customPipe:param1:param2 }}. Each parameter after the first colon is passed as a separate argument to the transform method in order. This allows creating flexible pipes that can adapt behavior based on runtime parameters. For example, you might create a truncate pipe that accepts a length parameter, a currency converter that accepts a currency code, or a format pipe that accepts a format string. Parameters can be of any type - strings, numbers, booleans, or even variable references from the component. When using variable references as parameters, the pipe is automatically re-evaluated when those variables change (in impure mode). It's important to handle optional parameters and provide sensible defaults if parameters aren't provided. Custom pipes with parameters are more flexible and reusable than those without parameters, allowing a single pipe implementation to handle multiple use cases. This pattern is particularly useful for creating domain-specific pipes that require configuration for different scenarios. Understanding how to create parameterized custom pipes enhances your ability to build clean, reusable Angular templates.
Q30. What are async pipes and how do you use them?
The AsyncPipe is a built-in Angular pipe that automatically handles subscriptions and unsubscriptions from observables and promises in templates. Instead of manually subscribing to observables in component classes and storing the result in a property, you can bind observables directly to the AsyncPipe in templates. When the observable emits a value, the pipe automatically updates the template. When the component is destroyed, the pipe automatically unsubscribes, preventing memory leaks. This simplifies code and reduces boilerplate while improving performance. The AsyncPipe is perfect for displaying data from HTTP requests, real-time data streams, or any asynchronous source. It works with both observables (using subscribe) and promises (using then). Using AsyncPipe eliminates the need to manually manage subscriptions in component classes, reducing the chance of memory leaks. For complex scenarios with multiple async operations, you can combine AsyncPipe with RxJS operators like map, switchMap, and combineLatest. The AsyncPipe can be combined with other pipes using pipe chaining, for example {{ data$ | async | json }}. When using AsyncPipe with *ngIf for null checking, you need to be careful as the async pipe will re-evaluate and potentially re-subscribe. Modern Angular often combines AsyncPipe with the as keyword for template variables, improving readability. Using AsyncPipe is considered a best practice for handling asynchronous operations in templates and is essential for writing clean, maintainable Angular code.
Q31. What is RxJS? Explain Observables vs Promises.
RxJS (Reactive Extensions for JavaScript) is a library for composing asynchronous and event-based programs using observable sequences. It provides a powerful, functional approach to handling asynchronous operations and data streams. Observables are lazy, reusable streams that can emit multiple values over time, while promises represent a single value that will be available in the future. Observables don't execute until someone subscribes to them, whereas promises execute immediately upon creation. Observables can emit zero, one, or many values over their lifetime, while promises always emit exactly one value (or error). Observables are cancelable - you can unsubscribe to stop receiving updates and potentially free resources, while promises cannot be canceled once created. Observables integrate seamlessly with Angular's change detection and other Angular features, making them the preferred choice in Angular applications. RxJS provides powerful operators like map, filter, switchMap, debounceTime, and throttleTime for transforming and composing observables. Promises are simpler for one-off operations but lack the flexibility and power of observables for complex scenarios. Angular's HTTP client returns observables, routing provides observables for route changes, and reactive forms work naturally with observables. Understanding RxJS and observables is essential for modern Angular development as they're fundamental to the framework's reactive architecture.
Q32. What are Subjects in RxJS? Explain BehaviorSubject and ReplaySubject.
Subjects are special observables that are also observers - they can emit values and multiple subscribers can listen to those values. A Subject is a multicast observable that shares a single subscription among multiple subscribers, whereas regular observables create separate subscriptions for each observer. Subjects are useful for sharing state across components and implementing event-driven architectures. BehaviorSubject is a type of subject that requires an initial value and emits the current value to new subscribers immediately upon subscription. This is useful for state management - BehaviorSubject always has a value available, making it perfect for maintaining and sharing component state. ReplaySubject buffers a specified number of previous values and emits them to new subscribers upon subscription, useful for replaying recent events or state history. You specify the buffer size in the ReplaySubject constructor. AsyncSubject only emits the last value when the subject completes, useful for operations that produce a final result. ReplaySubject with no size parameter replays all previous values, while ReplaySubject with a size parameter acts like a circular buffer. Subjects should be used carefully as improper implementation can lead to memory leaks if subscriptions aren't properly cleaned up. Using takeUntil pattern or unsubscribing in ngOnDestroy is essential when working with subjects. Subjects are fundamental to building reactive, event-driven Angular applications and are commonly used in services for cross-component communication and state management.
// Subject example
export class EventService {
private clickSubject = new Subject<string>();
click$ = this.clickSubject.asObservable();
emitClick(event: string) {
this.clickSubject.next(event);
}
}
// BehaviorSubject for state management
export class StateService {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();
getUser(): User | null {
return this.userSubject.value;
}
setUser(user: User) {
this.userSubject.next(user);
}
}
// ReplaySubject example
export class LogService {
private logSubject = new ReplaySubject<string>(5);
logs$ = this.logSubject.asObservable();
log(message: string) {
this.logSubject.next(message);
}
}
// Component using subjects
export class MyComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
constructor(private stateService: StateService) { }
ngOnInit() {
this.stateService.user$
.pipe(takeUntil(this.destroy$))
.subscribe(user => {
console.log('Current user:', user);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Q33. What are RxJS operators? Explain map, filter, switchMap, and mergeMap.
RxJS operators are functions that take an observable as input and return a new observable, allowing you to transform, filter, and combine observable streams. The map operator transforms each value emitted by an observable using a provided function, creating a new observable with transformed values. This is useful for converting one data format to another or extracting specific properties from objects. The filter operator creates a new observable that emits only values that satisfy a predicate function, allowing you to selectively pass through values. The switchMap operator is useful for handling sequential asynchronous operations - when a new value is emitted by the source observable, it unsubscribes from the previous inner observable and subscribes to a new one, canceling any pending operations. This is perfect for scenarios like search-as-you-type where you want to cancel previous requests when a new query is entered. The mergeMap (also called flatMap) operator subscribes to all inner observables and merges their emissions, useful when you want to process multiple concurrent operations without canceling previous ones. Other important operators include concatMap (processes sequentially), debounceTime (waits before emitting), throttleTime (limits emission frequency), combineLatest (combines multiple observables), and takeUntil (unsubscribes when another observable emits). Operators can be chained together to create complex data transformation pipelines. Understanding operators is crucial for effectively using RxJS and building reactive Angular applications.
Q34. What are takeUntil and combineLatest operators?
The takeUntil operator unsubscribes from an observable when another observable (called the notifier) emits a value. This is the primary pattern for managing subscription cleanup in Angular components, preventing memory leaks. You typically create a destroy$ subject in the component, subscribe to observables with takeUntil(this.destroy$), and emit and complete the destroy$ subject in ngOnDestroy. This pattern ensures all subscriptions are cleaned up when the component is destroyed. combineLatest is an operator that combines multiple observables and emits the latest value from each whenever any of them emits. It waits for all source observables to emit at least once before emitting, then emits whenever any source emits. This is useful for combining multiple data streams, like combining user selections, filters, and search queries into a single result stream. combineLatest returns an array of the latest values from all source observables. The order of values in the array corresponds to the order of source observables provided. combineLatest is often used with switchMap to handle complex scenarios where multiple dependencies need to be combined and processed together. Both operators are essential patterns in modern Angular development - takeUntil for memory leak prevention and combineLatest for multi-stream coordination. Understanding when and how to use these operators is critical for building robust, leak-free Angular applications.
Q35. What are debounceTime and throttleTime operators?
The debounceTime operator delays the emission of values by a specified time and resets the timer each time a new value arrives. It only emits the last value in a sequence after the specified duration has passed without any new values. debounceTime is perfect for search-as-you-type scenarios where you want to wait for the user to stop typing before making an API request, reducing unnecessary network traffic. It's also useful for form validation where you want to validate after the user stops typing rather than on every keystroke. The throttleTime operator emits at most one value in a specified time window, suppressing subsequent values within the window. After the time window passes, the next value is emitted and a new time window begins. throttleTime is useful for events that fire frequently (like scroll or resize events) where you want to limit the rate of processing to improve performance. While debounceTime waits for a pause in the stream, throttleTime enforces a minimum time between emissions. Both operators help optimize performance by reducing the number of times expensive operations (like API calls or DOM updates) are executed. Understanding when to use each operator is crucial - use debounceTime for waiting for user input completion and throttleTime for limiting the frequency of high-frequency events. These operators are fundamental for building responsive, performant user interfaces in Angular applications.
Q36. What is HttpClient in Angular? How do you make HTTP requests?
HttpClient is an Angular service for making HTTP requests to backend services and APIs. It's provided by HttpClientModule and offers methods for all HTTP verbs: get(), post(), put(), patch(), delete(), and head(). HttpClient returns observables, allowing you to use RxJS operators for transforming responses and handling errors. HttpClient automatically serializes JavaScript objects to JSON and deserializes JSON responses to JavaScript objects. It handles request headers automatically and provides methods to set custom headers, parameters, and configure request options. The generic type parameter allows you to specify the response type, providing type safety when working with API responses. HttpClient methods accept a URL and optional configuration object with options like headers, params, responseType, and withCredentials. Responses are automatically deserialized based on the type annotation, though you should be cautious with untyped responses from untrusted sources. Error handling is done using the catchError operator to transform errors into appropriate responses or rethrow them for component handling. HttpClient is vastly superior to the deprecated Http service, offering better performance, simpler syntax, and better integration with Angular's dependency injection system. Interceptors can be created to add global request/response handling like authentication headers or error logging. HttpClient is fundamental to building Angular applications that communicate with backend services and APIs.
Q37. What is HttpInterceptor? How do you implement JWT token authentication with interceptors?
HttpInterceptor is an interface for intercepting HTTP requests and responses globally. Interceptors allow you to add common functionality to all HTTP requests without having to modify each request individually. Common uses include adding authentication headers, logging requests/responses, handling errors, adding request timestamps, and modifying request/response bodies. An interceptor implements HttpInterceptor interface and the intercept method, which receives HttpRequest and HttpHandler and returns an Observable. The intercept method is where you can modify the request, call the handler to send the request, and then process the response. Interceptors are chained in order, allowing you to stack multiple interceptors. JWT (JSON Web Token) authentication with interceptors involves intercepting outgoing requests, adding the JWT token to the Authorization header, and handling token refresh when tokens expire. When implementing JWT authentication, you typically retrieve the token from local storage or a service, add it to the Authorization header of requests, and handle 401 Unauthorized responses by refreshing the token and retrying the request. Interceptors can also handle response errors globally, catching authentication errors and redirecting to login, or handling network errors with user-friendly messages. Using interceptors for JWT authentication provides a clean, centralized approach that applies authentication to all HTTP requests automatically. Interceptors must be registered in the module's providers array with the HTTP_INTERCEPTORS token. Proper implementation of interceptors is essential for building secure Angular applications with centralized request/response handling.
Q38. How do you handle HTTP errors in Angular?
Error handling in Angular HTTP requests is typically done using the catchError operator from RxJS, which intercepts errors and allows you to transform them into appropriate responses or re-throw them. The catchError operator receives an error object and should return an observable - either transforming the error or throwing it further down the chain. HTTP errors include network errors, timeout errors, and server errors (4xx and 5xx status codes). You should handle errors at the appropriate level - globally in interceptors for common errors, or in component services for specific handling. Common patterns include displaying user-friendly error messages, retrying failed requests, logging errors for debugging, and navigating to error pages. The error object contains status, statusText, and error properties that provide details about what went wrong. You can check the error status to handle specific cases differently - for example, redirecting to login on 401 Unauthorized or showing a retry option on network errors. Using the throwError function allows you to propagate errors to component subscribers who can handle them at the UI level. Implementing centralized error handling through interceptors reduces code duplication and ensures consistent error handling across the application. RxJS operators like retry() and retryWhen() can automatically retry failed requests based on conditions. Proper error handling significantly improves user experience by providing clear feedback about what went wrong and guiding users toward resolution.
// Service with error handling
export class UserService {
constructor(private http: HttpClient) { }
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users')
.pipe(
retry(1),
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
let errorMessage = 'An error occurred';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side error
errorMessage = `Error Code: ${error.status}\n` +
`Message: ${error.message}`;
}
console.error(errorMessage);
return throwError(() => new Error(errorMessage));
}
}
// HTTP Interceptor with error handling
@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) {
this.router.navigate(['/login']);
}
return throwError(() => error);
})
);
}
}
Q39. What are ViewChild and ContentChild decorators?
ViewChild and ContentChild are decorators that allow you to access child components, directives, or DOM elements from parent component classes. ViewChild queries the component's own template (declared in the component's view) and provides access to the first matching element. ContentChild queries the content projected into the component using ng-content and provides access to the first matching element. Both decorators return a reference to the element, which is useful for accessing properties, calling methods, or manipulating the DOM directly. You use ViewChild for elements within the component's template, while ContentChild for elements that are projected as content from parent components. The generic type parameter specifies the type of element to query - it can be a component class, directive class, or a template reference variable name. By default, queries execute in ngAfterViewInit (ViewChild) or ngAfterContentInit (ContentChild) when the elements are guaranteed to be initialized. ViewChildren and ContentChildren are plural variants that return a QueryList of all matching elements, useful for iterating over multiple elements. QueryList is observable and triggers change detection when its contents change, allowing you to react to dynamic element changes. You can use the @ViewChild('templateRefName') syntax to query elements by their template reference variable names. These decorators are powerful for accessing child components and DOM elements but should be used sparingly - they create tight coupling and can make testing more difficult. Understanding when and how to use ViewChild and ContentChild is important for building complex component hierarchies and implementing sophisticated component interactions.
Q40. What is @Input and @Output decorators? How do they enable component communication?
@Input and @Output are decorators that enable parent-child component communication. @Input allows a parent component to pass data to a child component by binding to a property decorated with @Input. The child component receives this data and can use it in its template and logic. @Output allows a child component to send events to a parent component by emitting events through an EventEmitter property decorated with @Output. The parent component can listen to these events using event binding. @Input creates a one-way data binding from parent to child - the parent binds a value to the child's input property, and when the parent's value changes, the child component receives the updated value. The ngOnChanges lifecycle hook is called when input properties change, allowing the child to react to updates. @Output creates event communication from child to parent - the child emits events through the decorated property, and the parent can listen using parentheses syntax like (outputName)="handleEvent()". This pattern enables clean, unidirectional data flow and loose coupling between components. Components can have multiple @Input and @Output decorators to accept multiple data inputs and emit multiple types of events. Using @Input and @Output is the standard approach for component communication and is essential for building reusable, composable component hierarchies. This pattern is preferred over accessing parent/child components directly through ViewChild/ContentChild as it maintains better encapsulation and component independence.
Q41. How does EventEmitter work in Angular?
EventEmitter is a class that extends Subject and is used to emit custom events from child components to parent components. EventEmitter implements the RxJS Subject pattern, allowing you to emit values and have multiple subscribers listen to those events. When you decorate a property with @Output and make it an EventEmitter instance, child components can emit events and parent components can listen to them. The emit method sends a value to all subscribers, and parent components listen using the subscribe method or event binding syntax with parentheses. EventEmitter is generic, allowing you to specify the type of data being emitted, providing type safety for events. Parent components can bind to EventEmitter properties using event binding syntax like <child-component (eventName)="handleEvent($event)">, where $event contains the emitted value. EventEmitter simplifies component communication by providing a familiar, observable-based pattern that integrates seamlessly with Angular's reactive architecture. Most custom events in Angular components use EventEmitter, making it a fundamental pattern for building component interactions. EventEmitter is essentially a Subject optimized for event communication in component hierarchies. Understanding EventEmitter and how it integrates with @Output is essential for building interactive, composable Angular components that communicate effectively with their parents.
Q42. What is Change Detection in Angular? Explain Default and OnPush strategies.
Change detection is the mechanism Angular uses to detect when component properties have changed and synchronize the view with the component's state. Angular's default change detection strategy checks the entire component tree on every browser event, timer, or async event. While this is simple and reliable, it can be inefficient in large applications with many components. The Default change detection strategy (ChangeDetectionStrategy.Default) checks all components in the tree whenever any event occurs, ensuring the view is always in sync with the component state. This is convenient but can cause performance issues in applications with deep component hierarchies or components that render frequently. The OnPush change detection strategy (ChangeDetectionStrategy.OnPush) only checks a component when its input properties change, when events originating from the component or its children occur, or when explicitly triggered. OnPush is more performant because it reduces unnecessary change detection cycles, but requires careful management of inputs and state to work correctly. Components using OnPush strategy should treat inputs as immutable and use immutable patterns for state updates. OnPush strategy requires that components emit new references when data changes (using spread operators or immutable libraries) rather than modifying properties directly. Angular also provides ChangeDetectorRef service for manually triggering change detection when needed. Understanding change detection strategies is crucial for optimizing Angular application performance, especially in large applications with many components. Using OnPush strategically in leaf components can significantly improve rendering performance.
// Default change detection strategy
@Component({
selector: 'app-default-component',
template: '<div>{{ count }}</div><button (click)="increment()">+</button>'
})
export class DefaultComponent {
count = 0;
increment() {
this.count++;
}
}
// OnPush change detection strategy
@Component({
selector: 'app-onpush-component',
template: '<div>{{ user.name }}</div>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnPushComponent {
@Input() user: User;
updateUser(name: string) {
// Create new reference for OnPush to detect change
this.user = { ...this.user, name };
}
}
// Manual change detection
@Component({
selector: 'app-manual-detection',
template: '<div>{{ data }}</div>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ManualDetectionComponent {
data: string;
constructor(private cdr: ChangeDetectorRef) { }
updateData(newData: string) {
this.data = newData;
// Manually trigger change detection
this.cdr.markForCheck();
}
}
Q43. What are directives in Angular? Explain attribute and structural directives.
Directives are Angular classes with decorators that extend HTML with new functionality and behaviors. There are three types of directives: component directives (components are directives with templates), structural directives (modify the DOM structure), and attribute directives (modify the behavior and appearance of elements). Structural directives use an asterisk (*) prefix and modify the DOM by adding, removing, or replacing elements. Built-in structural directives include *ngIf (conditional rendering), *ngFor (iteration), and *ngSwitch (conditional branching). Structural directives are essentially syntactic sugar for ng-template, where *directive is expanded to <ng-template [directive]>. Attribute directives modify the appearance or behavior of an existing element without changing the DOM structure. Built-in attribute directives include [ngClass] for dynamic CSS classes, [ngStyle] for dynamic inline styles, and [ngModel] for two-way binding. Attribute directives don't have the asterisk prefix and are applied like normal HTML attributes. Custom directives can be created for both structural and attribute purposes, allowing you to encapsulate complex logic and reuse it across the application. Directives are essential for building flexible, reusable components and for extending HTML with application-specific functionality. Understanding the distinction between structural and attribute directives is crucial for building effective Angular templates. Directives promote code reusability and allow you to keep templates clean and focused on presentation.
Q44. How do you create custom attribute directives?
Custom attribute directives are created by defining a class and decorating it with @Directive decorator, specifying a selector for the directive name. The directive class can use @Input to accept configuration, @HostListener to listen to host element events, and ElementRef/Renderer2 to manipulate the host element. ElementRef provides access to the native DOM element, while Renderer2 is the preferred way to safely modify the DOM as it works across different rendering platforms. The directive receives the host element through dependency injection of ElementRef, allowing you to access and modify it. @HostListener decorator allows you to listen to events on the host element, triggering methods when those events occur. @HostBinding decorator binds host element properties or attributes to values in the directive class, allowing you to dynamically set CSS classes, styles, or HTML attributes. Custom directives should be declared in a module's declarations array to be available for use. Common use cases for attribute directives include highlighting elements on hover, validating input fields, implementing custom event handling, and applying conditional styling. Creating reusable attribute directives promotes code reusability and keeps template code clean and maintainable. Attribute directives can be simple (single decorator, basic functionality) or complex (multiple inputs, event listeners, advanced DOM manipulation). Understanding how to create and use custom directives is important for building sophisticated, reusable Angular components.
Q45. How do you create custom structural directives?
Custom structural directives are created by defining a class with @Directive decorator and using TemplateRef and ViewContainerRef to manipulate the DOM. The directive accepts a template using TemplateRef, which represents the template associated with the directive in the template. ViewContainerRef is the container where the directive can create or destroy embedded views. The directive uses ViewContainerRef.createEmbeddedView() to instantiate the template and add it to the DOM, and clear() to remove it. A custom structural directive typically accepts an input property using the directive name itself - this convention allows clean template syntax like *appMyDirective="value". The directive evaluates conditions and manages the DOM based on those conditions. Common patterns include conditional rendering (like ngIf), repetition with iteration (like ngFor), and pagination or lazy loading. The microsyntax of structural directives (the syntax after the asterisk) can be customized to provide clean, intuitive APIs. For example, you can create directives with inputs and outputs that accept configuration and emit events. Structural directives are powerful but can be complex to implement correctly - the template syntax must be properly understood and implemented. Proper implementation requires understanding the distinction between ng-template and view instantiation. Creating custom structural directives allows you to encapsulate complex DOM manipulation logic and reuse it across the application. This is an advanced Angular feature that allows building sophisticated, domain-specific templating logic.
Q46. What are Angular standalone components? How are they different from traditional components?
Angular standalone components (introduced in Angular 14+) are components, directives, and pipes that can be used independently without being declared in a module. Traditional components must be declared in an NgModule, while standalone components eliminate this requirement by marking them with standalone: true in the component decorator. Standalone components can import other standalone components, directives, pipes, and modules directly in their imports array, making them truly self-contained and reusable. This approach simplifies Angular development by reducing boilerplate and allowing components to express their dependencies directly rather than relying on module configuration. Standalone components are particularly useful for library development and building micro-frontends, as they eliminate the need to export components from barrel exports or manage module hierarchies. The bootstrap function in main.ts can directly bootstrap standalone components without creating an AppModule, simplifying application initialization. Standalone components work well with lazy loading and can be used to create entire feature modules as standalone components with their own routing. The migration path from NgModule-based to standalone components is gradual - you can mix standalone and traditional components in the same application. Standalone components are the future direction of Angular, and new projects are encouraged to use them. However, they require a modern version of Angular (14+) and may not be suitable for legacy applications. Understanding standalone components is becoming increasingly important as the Angular framework evolves toward a more component-centric architecture.
Q47. What are Angular Signals? How do they work?
Angular Signals (introduced in Angular 16+) are a new reactive primitive for managing component state with fine-grained reactivity and improved performance. Signals are functions that hold a value and notify interested consumers when that value changes. Signals are created using the signal() function, which returns a writable signal that can be updated using the set() method or by providing an updater function to mutate the current value. Computed signals are created using computed() function and automatically recalculate when their dependencies change, providing automatic dependency tracking and memoization. Signals are more performant than traditional property-based reactivity because they enable fine-grained change detection - only components that use a changed signal are re-evaluated, rather than checking the entire component tree. Signals can be read in templates directly without using async pipes, simplifying template syntax. The effect() function allows you to run side effects whenever signal values change, providing a replacement for lifecycle hooks in many cases. Signals integrate with Angular's zoneless change detection roadmap, improving performance by eliminating zone.js overhead. Signals are particularly useful for implementing complex state management patterns with minimal boilerplate. Unlike observables, signals are synchronous and represent current state, while observables are asynchronous and represent streams of values. Signals are becoming the recommended approach for state management in modern Angular applications. Understanding signals is important for building high-performance Angular applications and for staying current with the framework's evolution.
Q48. What is NgRx? Explain the Redux pattern in Angular.
NgRx is a library that brings Redux state management to Angular applications. Redux is a predictable state management pattern based on the principles of single source of truth, actions, pure reducers, and unidirectional data flow. In NgRx, the Store is the single source of truth containing the entire application state as a single, immutable object. Actions are plain objects describing user interactions or events that occur in the application. Reducers are pure functions that take the current state and an action, and return a new state. Selectors are functions that extract specific slices of state, enabling components to subscribe to relevant state portions. NgRx follows a strict unidirectional data flow: components dispatch actions, reducers handle actions and update state, selectors retrieve state, and components subscribe to state changes through observables. This pattern makes state management predictable and easier to debug - you can replay actions to reproduce any state. Effects middleware handles side effects like API calls, triggered by actions and dispatching new actions based on results. NgRx requires more boilerplate than simpler state management solutions but provides powerful devtools for time-travel debugging. NgRx is particularly beneficial for large, complex applications with sophisticated state management requirements. Understanding NgRx and Redux patterns is valuable for managing complex state in Angular applications. NgRx alternatives include Akita, RxJS-based solutions, and newer approaches using signals for state management.
Q49. What are the best practices for Angular performance optimization?
Angular performance optimization involves multiple strategies targeting different aspects of application rendering and execution. Using OnPush change detection strategy reduces unnecessary change detection cycles, particularly in leaf components. Code splitting and lazy loading reduces initial bundle size by loading feature modules only when needed. Using TrackBy functions with *ngFor improves performance by helping Angular identify list items that haven't changed, preventing unnecessary DOM manipulation. Implementing proper unsubscription patterns using takeUntil or async pipe prevents memory leaks and improves application stability. Using the async pipe in templates eliminates manual subscription management and automatically handles cleanup. Memoizing expensive computations using useMemo pattern or Angular Signals with computed() prevents recalculation on every change detection cycle. Lazy loading images and deferring non-critical content improves initial load time and perceived performance. Preloading strategies can preload likely-to-be-accessed routes to improve perceived performance. Bundle size analysis and optimization removes unused code and dependencies. Using production builds enables tree-shaking and minification. Implementing virtual scrolling for large lists improves rendering performance by only rendering visible items. Using components with smaller dependency trees reduces change detection overhead. Profiling with Chrome DevTools and Angular DevTools identifies actual performance bottlenecks. Modern Angular features like standalone components and signals provide better performance characteristics. Performance optimization should be data-driven - measure first, then optimize based on actual bottlenecks. Regular performance monitoring and optimization maintains application responsiveness as the codebase grows.
Q50. What is the TrackBy function in Angular and how does it improve performance?
The TrackBy function is a performance optimization for *ngFor directive that helps Angular identify which list items have changed, improved, or been removed. By default, *ngFor destroys and recreates DOM elements for all list items on every change, even if only one item changed. The TrackBy function provides a way to identify items uniquely so Angular can reuse DOM elements for unchanged items. TrackBy function takes the index and item as parameters and returns a unique identifier for that item, typically the item's ID. When Angular detects list changes, it uses the TrackBy function to determine which items are new, which are unchanged, and which have been removed. For unchanged items, Angular reuses the existing DOM elements and only updates the item values, significantly improving performance. For large lists with hundreds or thousands of items, TrackBy can dramatically improve rendering performance by reducing DOM manipulation. Implementing TrackBy is simple - define a method in the component that returns a unique identifier, and pass it to *ngFor using the trackBy property. The TrackBy function should be pure and return the same value for the same input. Using TrackBy is considered a best practice and should be implemented for all *ngFor loops, even small ones, as it has minimal cost and can prevent performance issues as lists grow. Many Angular linters and style guides recommend or enforce using TrackBy for all *ngFor loops. Understanding and implementing TrackBy functions is essential for building performant Angular applications, particularly those with frequently updating lists.