Q41.

How do you pass data between components?

Angular applications often consist of multiple components that need to share information. Understanding how to effectively pass data between these components is fundamental for building robust and interactive applications. This guide covers the most common techniques for inter-component communication in Angular.

Parent to Child Communication (@Input())

To send data from a parent component to a child component, the @Input() decorator is used. The child component declares an input property, which the parent can then bind to using property binding in its template. This is a common and straightforward method for one-way data flow.

typescript
/* child.component.ts */
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<h2>Child Component</h2><p>Received from parent: {{ message }}</p>`
})
export class ChildComponent {
  @Input() message: string = '';
}

/* parent.component.html */
<app-child [message]="'Hello from Parent!'"></app-child>

Child to Parent Communication (@Output() and EventEmitter)

When a child component needs to send data or notify its parent component about an event, the @Output() decorator combined with EventEmitter is used. The child component emits events, and the parent component listens for these events using event binding.

typescript
/* child.component.ts */
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <h2>Child Component</h2>
    <button (click)="sendMessage()">Send to Parent</button>
  `
})
export class ChildComponent {
  @Output() messageEvent = new EventEmitter<string>();

  sendMessage() {
    this.messageEvent.emit('Hello from Child!');
  }
}

/* parent.component.html */
<app-child (messageEvent)="receiveMessage($event)"></app-child>
<p>Received from child: {{ receivedChildMessage }}</p>

/* parent.component.ts */
import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html'
})
export class ParentComponent {
  receivedChildMessage: string = '';

  receiveMessage(message: string) {
    this.receivedChildMessage = message;
  }
}

Communication via Services (for Unrelated Components)

For communication between components that do not have a direct parent-child relationship (e.g., sibling components, or components deeply nested in different branches of the component tree), a shared service is the recommended approach. Services can hold shared data and provide methods for components to interact with that data, often utilizing RxJS Observables (like Subject or BehaviorSubject) to broadcast changes.

typescript
/* data.service.ts */
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private messageSource = new BehaviorSubject<string>('Default Message');
  currentMessage = this.messageSource.asObservable();

  constructor() { }

  changeMessage(message: string) {
    this.messageSource.next(message);
  }
}
typescript
/* component-a.component.ts (Sender) */
import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-component-a',
  template: `
    <h3>Component A</h3>
    <button (click)="newMessage()">Send Message</button>
  `
})
export class ComponentAComponent {
  constructor(private dataService: DataService) { }

  newMessage() {
    this.dataService.changeMessage('Message from Component A');
  }
}

/* component-b.component.ts (Receiver) */
import { Component, OnInit, OnDestroy } from '@angular/core';
import { DataService } from './data.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-component-b',
  template: `
    <h3>Component B</h3>
    <p>Received: {{ message }}</p>
  `
})
export class ComponentBComponent implements OnInit, OnDestroy {
  message: string = '';
  subscription!: Subscription;

  constructor(private dataService: DataService) { }

  ngOnInit() {
    this.subscription = this.dataService.currentMessage.subscribe(message => this.message = message);
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Summary Table of Communication Methods

MethodRelationshipDescriptionProsCons
@Input()Parent to ChildChild component declares an input property; parent binds data to it.Simple, direct, one-way data flow.Only works for direct parent-child.
@Output() and EventEmitterChild to ParentChild emits events; parent listens and reacts.Event-driven, clear separation of concerns.Only works for direct parent-child.
Shared Service (with Observables)Any (Unrelated, Sibling)Service holds shared data (e.g., BehaviorSubject) and components subscribe to it.Flexible, scalable for complex apps, supports multiple subscribers.Can add complexity, requires careful subscription management.
Q42.

Explain @Input and @Output decorators.

In Angular, `@Input` and `@Output` are fundamental decorators used for communication between components. They enable hierarchical data flow, allowing components to share data and events in a clear and organized manner, facilitating reusable and maintainable component architectures.

@Input Decorator

The @Input decorator is used to pass data from a parent component to a child component. It allows a child component to receive data from its parent, making the child component reusable and configurable.

To use @Input, you decorate a property in the child component with @Input(). The parent component can then bind to this property using property binding syntax ([]).

typescript
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <p>Child Component</p>
    <p>Received message: {{ message }}</p>
  `
})
export class ChildComponent {
  @Input() message: string = '';
}
html
<!-- parent.component.html -->
<app-child [message]="'Hello from Parent!'"></app-child>

@Output Decorator

The @Output decorator is used to send data or events from a child component to a parent component. It allows the child component to notify its parent about changes or actions that have occurred within itself.

To use @Output, you decorate a property in the child component with @Output(). This property must be an instance of EventEmitter. The child component uses the .emit() method of this EventEmitter to send data, and the parent component listens for these events using event binding syntax (()).

typescript
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <button (click)="sendMessageToParent()">Send Message</button>
  `
})
export class ChildComponent {
  @Output() messageEvent = new EventEmitter<string>();

  sendMessageToParent() {
    this.messageEvent.emit('Hello from Child!');
  }
}
typescript
import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    <app-child (messageEvent)="receiveMessageFromChild($event)"></app-child>
    <p>Received from child: {{ childMessage }}</p>
  `
})
export class ParentComponent {
  childMessage: string = '';

  receiveMessageFromChild(message: string) {
    this.childMessage = message;
  }
}

Key Differences and Use Cases

  • @Input: For parent-to-child communication. Use when the parent needs to provide data to the child.
  • @Output: For child-to-parent communication. Use when the child needs to notify the parent about an event or pass data back.
  • Data Flow: @Input represents data flowing *down* the component tree, while @Output represents events flowing *up* the component tree.
  • Mechanism: @Input uses property binding to pass values. @Output uses event binding and EventEmitter to emit custom events.
Q43.

What is ViewChild and ContentChild?

In Angular, ViewChild and ContentChild are powerful decorators that allow a component to query and gain programmatic access to elements, directives, or other components within its view or projected content. They are crucial for inter-component communication, direct DOM manipulation, or integrating with third-party libraries.

What are ViewChild and ContentChild?

Both @ViewChild and @ContentChild serve the purpose of querying the DOM within an Angular application. They enable a component to obtain a reference to an element, a component instance, or a directive. The fundamental difference lies in *where* they look for these items: ViewChild looks within the component's *own* template, while ContentChild looks within *projected content*.

ViewChild

The @ViewChild decorator is used to query and get a reference to the first element or the first instance of a directive/component matching the selector from the component's *own view*. This means it looks within the template that is directly associated with the component itself (the content defined in the component's template or templateUrl).

  • Queries elements defined within the component's own template.
  • The queried element is available after the ngAfterViewInit lifecycle hook.
  • Typically used to interact with child components, directives, or native DOM elements that are a direct part of the component's visual structure.
  • Can select by: a template reference variable (e.g., #myElement), a component type, or a directive type.
typescript
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<p>I am a child component.</p>`
})
export class ChildComponent {
  public message: string = 'Hello from child!';
}

@Component({
  selector: 'app-parent-component',
  template: `
    <h3 #myHeading>Hello ViewChild Example!</h3>
    <app-child-component></app-child-component>
  `
})
export class ParentComponent implements AfterViewInit {
  @ViewChild('myHeading') myHeadingRef!: ElementRef;
  @ViewChild(ChildComponent) childComponentRef!: ChildComponent;

  ngAfterViewInit() {
    // Accessing a native DOM element
    console.log('Heading Text:', this.myHeadingRef.nativeElement.textContent);
    this.myHeadingRef.nativeElement.style.color = 'blue';

    // Accessing a child component instance
    console.log('Child Component Message:', this.childComponentRef.message);
    this.childComponentRef.message = 'Message updated by parent!';
  }
}

ContentChild

The @ContentChild decorator is used to query and get a reference to the first element or the first instance of a directive/component matching the selector from the *content projected* into the component. Content projection (using <ng-content>) allows a parent component to pass content into a child component, and @ContentChild enables the child component to interact with that passed-in content.

  • Queries elements that are passed into the component via content projection (<ng-content>).
  • The queried element is available after the ngAfterContentInit lifecycle hook.
  • Used when a component needs to interact with or manipulate the content it receives from its parent.
  • Can select by: a template reference variable, a component type, or a directive type, similar to ViewChild.
typescript
import { Component, ContentChild, ElementRef, AfterContentInit } from '@angular/core';

@Component({
  selector: 'app-projected-component',
  template: `<p>I am a component projected as content.</p>`
})
export class ProjectedComponent {
  public greet(): string { return 'Hello from projected content!'; }
}

@Component({
  selector: 'app-wrapper-component',
  template: `
    <h2>Wrapper Component</h2>
    <ng-content></ng-content> <!-- This is where content is projected -->
  `
})
export class WrapperComponent implements AfterContentInit {
  @ContentChild('projectedText') projectedTextRef!: ElementRef;
  @ContentChild(ProjectedComponent) projectedComponentRef!: ProjectedComponent;

  ngAfterContentInit() {
    // Accessing a native DOM element from projected content
    if (this.projectedTextRef) {
      console.log('Projected Text:', this.projectedTextRef.nativeElement.textContent);
      this.projectedTextRef.nativeElement.style.fontWeight = 'bold';
    }

    // Accessing a projected component instance
    if (this.projectedComponentRef) {
      console.log('Projected Component Greet:', this.projectedComponentRef.greet());
    }
  }
}

@Component({
  selector: 'app-parent-of-wrapper',
  template: `
    <app-wrapper-component>
      <p #projectedText>This paragraph is content passed to the wrapper.</p>
      <app-projected-component></app-projected-component>
    </app-wrapper-component>
  `
})
export class ParentOfWrapperComponent {}

Key Differences

FeatureViewChildContentChild
PurposeQueries elements from the component's *own* view template.Queries elements from *content projected* into the component (<ng-content>).
Location of SearchWithin the `<template>` or `templateUrl` of the current component.Within the content provided by a parent component and inserted via `<ng-content>`.
Timing of AvailabilityAvailable after the `ngAfterViewInit` lifecycle hook.Available after the `ngAfterContentInit` lifecycle hook.
Use CaseInteracting with internal child components, directives, or DOM elements that the component directly owns and defines.Interacting with external content (components, elements) that a parent provides to the component.

When to use which?

Use @ViewChild when you need to access and manipulate elements, directives, or child components that are defined directly within your component's own template. This is common for internal component interactions or direct DOM manipulation of elements that your component is responsible for. Use @ContentChild when your component is designed to receive content from its parent via content projection (e.g., a modal or tab component), and you need to query or interact with that projected content. This allows for flexible and reusable wrapper components that can customize the behavior of the content they host.

Q44.

Difference between ViewChild and ViewChildren?

In Angular, `ViewChild` and `ViewChildren` are property decorators used to query elements from the view DOM. They allow a component class to get references to elements, components, or directives that are part of its own view. The primary difference lies in the number of elements they can query and their return types.

ViewChild

ViewChild is used to query and get the first matching element, component, or directive from the DOM in the component's template. It returns a single instance of the matched element. The query result is available in the ngAfterViewInit lifecycle hook, which runs once after the component's view has been fully initialized.

typescript
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-view-child-example',
  template: `
    <h3 #myHeading>Hello ViewChild!</h3>
    <input #myInput type="text" placeholder="Enter text">
  `
})
export class ViewChildExampleComponent implements AfterViewInit {
  @ViewChild('myHeading') myHeadingElement: ElementRef;
  @ViewChild('myInput') myInputElement: ElementRef;

  ngAfterViewInit() {
    console.log('Heading text:', this.myHeadingElement.nativeElement.textContent);
    this.myInputElement.nativeElement.focus();
  }
}

ViewChildren

ViewChildren is used to query and get all matching elements, components, or directives from the DOM in the component's template. It returns a QueryList of instances. A QueryList is a collection that stores a list of items. When the state of the application changes and a child element is added or removed, the QueryList automatically updates its entries. It also provides a changes observable that you can subscribe to for reactive updates.

typescript
import { Component, ViewChildren, ElementRef, AfterViewInit, QueryList } from '@angular/core';

@Component({
  selector: 'app-view-children-example',
  template: `
    <div *ngFor="let item of items">
      <p #myParagraph>{{ item }}</p>
    </div>
    <button (click)="addItem()">Add Item</button>
  `
})
export class ViewChildrenExampleComponent implements AfterViewInit {
  items = ['Item 1', 'Item 2', 'Item 3'];
  @ViewChildren('myParagraph') myParagraphs: QueryList<ElementRef>;

  ngAfterViewInit() {
    this.myParagraphs.forEach((paragraph, index) => {
      console.log(`Paragraph ${index + 1} text:`, paragraph.nativeElement.textContent);
    });

    // Subscribe to changes if elements are added/removed dynamically
    this.myParagraphs.changes.subscribe(list => {
      console.log('Paragraphs updated:', list.toArray().map(el => el.nativeElement.textContent));
    });
  }

  addItem() {
    this.items.push(`Item ${this.items.length + 1}`);
  }
}

Key Differences

FeatureViewChildViewChildren
Return TypeSingle instance (ElementRef, component, or directive)QueryList<T> (a collection of instances)
Query ResultThe first matching elementAll matching elements
AvailabilityAvailable in `ngAfterViewInit` (synchronously)The `QueryList` is available in `ngAfterViewInit`. The `changes` observable provides updates if elements are added/removed.
ReactivityNot inherently reactive; refers to a static element at query time.Reactive; the `QueryList` automatically updates, and its `changes` observable can be subscribed to.
Use CaseAccessing a single known element (e.g., a specific input field, a single child component).Accessing a collection of dynamic or repeated elements (e.g., items within an `ngFor` loop, multiple child components of the same type).

When to Use Which

  • Use ViewChild when:
  • You need to interact with a single, specific DOM element, component, or directive.
  • To focus an input field on initialization.
  • To call a method on a single child component.
  • To get a reference to a static element in your template that is not expected to change.
  • Use ViewChildren when:
  • You need to interact with multiple instances of the same element, component, or directive.
  • When dealing with dynamically added or removed elements (e.g., within an ngFor loop).
  • To perform an action on all child components of a certain type.
  • To subscribe to changes in a collection of view elements to react to dynamic updates.
Q45.

Explain reactive forms architecture.

Angular Reactive Forms provide a model-driven approach to handling form inputs whose values change over time. They are built on a reactive programming paradigm, making forms more predictable, testable, and robust. This architecture emphasizes immutability, synchronous data flow, and explicit form controls.

Core Building Blocks

Reactive Forms are composed of several fundamental classes that define the structure and behavior of the form. These classes inherit from the AbstractControl base class, providing a common interface for interaction.

FormControl

A FormControl is the most basic building block, representing an individual input field (e.g., text input, checkbox, select). It tracks the value, validation status, and user interaction (e.g., touched, dirty) for a single form control. Each FormControl instance is a self-contained unit managing its own state.

typescript
import { FormControl } from '@angular/forms';

const nameControl = new FormControl(''); // Initial value is an empty string

FormGroup

A FormGroup aggregates a collection of FormControls (or other FormGroups/FormArrays) into a single entity. It represents a form as a whole or a subsection of a form. When you interact with a FormGroup, its value and validation status are derived from the aggregate of its child controls. It provides a way to manage related form fields as a unit.

typescript
import { FormGroup, FormControl } from '@angular/forms';

const userForm = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl(''),
  email: new FormControl('')
});

FormArray

A FormArray manages an array of AbstractControl instances (FormControls, FormGroups, or even other FormArrays). It's useful for dynamic forms where you need to add or remove identical sets of controls, such as a list of phone numbers or skills. Like FormGroup, its value and validation status are aggregated from its children.

typescript
import { FormArray, FormControl } from '@angular/forms';

const skillsForm = new FormGroup({
  skills: new FormArray([
    new FormControl('Angular'),
    new FormControl('TypeScript')
  ])
});

AbstractControl

FormControl, FormGroup, and FormArray all extend the AbstractControl class. This base class provides common properties and methods shared across all control types, such as: - value: The current value of the control. - status: The validation status ('VALID', 'INVALID', 'PENDING', 'DISABLED'). - valid/invalid: Boolean flags indicating validation status. - touched/untouched: Boolean flags indicating if the user has interacted with the control. - dirty/pristine: Boolean flags indicating if the value has changed from its initial state. - enable()/disable(): Methods to change the control's enabled state. - setValue()/patchValue(): Methods to update the control's value.

FormBuilder Service

The FormBuilder is an injectable helper service that provides a more convenient and concise syntax for creating instances of FormControls, FormGroups, and FormArrays. It reduces boilerplate code, especially for complex forms.

typescript
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

// In a component or service constructor:
constructor(private fb: FormBuilder) {}

// Creating a form group with FormBuilder:
const userProfileForm = this.fb.group({
  name: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]],
  address: this.fb.group({
    street: [''],
    city: ['']
  })
});

Validation

Validators are functions that are used to check the validity of a form control's value. Angular provides a set of built-in validators (e.g., Validators.required, Validators.minLength) and also allows for custom synchronous or asynchronous validators. Validators are typically passed as the second argument when instantiating a FormControl or FormArray/FormGroup using the FormBuilder.

typescript
import { FormControl, Validators } from '@angular/forms';

const passwordControl = new FormControl('', [
  Validators.required,
  Validators.minLength(8),
  Validators.pattern(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).*$/)
]);

// Checking validation status
console.log(passwordControl.valid); // boolean

Data Flow and View Integration

In Reactive Forms, the form model (FormGroup, FormControl instances) is defined explicitly in the component's TypeScript code, separate from the template. The template then binds to this model using the formControlName or formGroupName directives. Data flows primarily from the component's model to the view, and user input updates the model directly and synchronously. This clear separation makes the data flow unidirectional and easier to reason about, test, and debug.

html
<form [formGroup]="userProfileForm">
  <input type="text" formControlName="name">
  <input type="email" formControlName="email">
  <div formGroupName="address">
    <input type="text" formControlName="street">
    <input type="text" formControlName="city">
  </div>
  <button type="submit" [disabled]="!userProfileForm.valid">Submit</button>
</form>
Q46.

How does Angular form validation work?

Angular provides a robust and flexible system for validating user input in forms, supporting both template-driven forms and reactive forms. It allows you to ensure data integrity and provide a good user experience by giving immediate feedback on input validity.

Two Approaches: Template-Driven vs. Reactive Forms

Angular offers two distinct approaches for building forms: template-driven forms and reactive forms. While both support the same validation capabilities, their implementation differs significantly.

Template-Driven Forms Validation

In template-driven forms, validation logic is primarily defined directly within the HTML template using directives. Angular automatically creates form control instances behind the scenes based on these directives.

  • ngModel directive: Binds form controls to data properties.
  • HTML5 validation attributes: required, minlength, maxlength, pattern, email.
  • Angular-specific directives: Though often HTML5 attributes suffice, Angular provides equivalents like ng-required.
  • #fieldName="ngModel": Exports the NgModel directive into a local template variable, allowing access to its properties (e.g., valid, invalid, dirty, touched).
html
<form #heroForm="ngForm">
  <div class="form-group">
    <label for="name">Name</label>
    <input type="text" id="name" name="name"
           class="form-control"
           required minlength="4" #name="ngModel"
           [(ngModel)]="model.name">
    <div *ngIf="name.invalid && (name.dirty || name.touched)"
         class="alert alert-danger">
      <div *ngIf="name.errors?.required">
        Name is required.
      </div>
      <div *ngIf="name.errors?.minlength">
        Name must be at least 4 characters long.
      </div>
    </div>
  </div>
  <button type="submit" [disabled]="!heroForm.valid">Submit</button>
</form>

Reactive Forms Validation

Reactive forms provide a more explicit and programmatic way to manage form validation. Validation logic is defined directly in the component class using FormControl, FormGroup, and FormArray instances.

  • FormControl: Represents a single input field.
  • FormGroup: Represents a collection of FormControl instances.
  • Validators class: Provides a set of built-in validation functions (e.g., Validators.required, Validators.minLength, Validators.pattern).
  • Custom validators: Functions that return a validation error object or null.
  • Asynchronous validators: For server-side validation or operations with a delay.
typescript
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-hero-form-reactive',
  templateUrl: './hero-form-reactive.component.html',
  styleUrls: ['./hero-form-reactive.component.css']
})
export class HeroFormReactiveComponent implements OnInit {
  heroForm!: FormGroup;

  ngOnInit(): void {
    this.heroForm = new FormGroup({
      'name': new FormControl(null, [
        Validators.required,
        Validators.minLength(4)
      ]),
      'email': new FormControl(null, [
        Validators.required,
        Validators.email
      ])
    });
  }

  onSubmit() {
    console.log(this.heroForm.value);
  }
}
html
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label for="name">Name</label>
    <input type="text" id="name" formControlName="name" class="form-control">
    <div *ngIf="heroForm.get('name')?.invalid && (heroForm.get('name')?.dirty || heroForm.get('name')?.touched)"
         class="alert alert-danger">
      <div *ngIf="heroForm.get('name')?.errors?.required">
        Name is required.
      </div>
      <div *ngIf="heroForm.get('name')?.errors?.minlength">
        Name must be at least 4 characters long.
      </div>
    </div>
  </div>
  <div class="form-group">
    <label for="email">Email</label>
    <input type="email" id="email" formControlName="email" class="form-control">
    <div *ngIf="heroForm.get('email')?.invalid && (heroForm.get('email')?.dirty || heroForm.get('email')?.touched)"
         class="alert alert-danger">
      <div *ngIf="heroForm.get('email')?.errors?.required">
        Email is required.
      </div>
      <div *ngIf="heroForm.get('email')?.errors?.email">
        Please enter a valid email.
      </div>
    </div>
  </div>
  <button type="submit" [disabled]="!heroForm.valid">Submit</button>
</form>

Validator States and CSS Classes

Angular automatically applies CSS classes to form controls based on their validation state. This allows for easy styling to provide visual feedback to users.

Class NameDescription
`ng-valid`Control's value is valid
`ng-invalid`Control's value is invalid
`ng-pristine`Control's value has not changed
`ng-dirty`Control's value has changed
`ng-untouched`Control has not been visited
`ng-touched`Control has been visited

These classes can be used in your CSS to style valid/invalid inputs, for example:

css
.ng-valid[required], .ng-valid.required {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

Custom Validators

When built-in validators are not sufficient, you can create custom validator functions. These functions take a FormControl or AbstractControl as an argument and return a validation error object if invalid, or null if valid.

typescript
import { AbstractControl, ValidatorFn, ValidationErrors } from '@angular/forms';

export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {forbiddenName: {value: control.value}} : null;
  };
}

// Usage in reactive forms:
// new FormControl(null, [forbiddenNameValidator(/bob/i)])

Asynchronous Validators

For validation that requires a server request or a delay, asynchronous validators are used. These validators return a Promise<ValidationErrors | null> or an Observable<ValidationErrors | null>.

typescript
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, timer } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';

export function uniqueUsernameValidator(userService: any): AsyncValidatorFn {
  return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
    return timer(500).pipe( // Simulate network delay
      switchMap(() => userService.checkUsernameNotTaken(control.value)),
      map(isTaken => (isTaken ? { uniqueUsername: true } : null))
    );
  };
}

// Usage in reactive forms:
// new FormControl(null, [], [uniqueUsernameValidator(this.userService)])
Q47.

What are custom validators in Angular?

In Angular, custom validators are functions that allow developers to define specific validation logic beyond the set of built-in validators (such as `required`, `minLength`, `pattern`). They are crucial for implementing unique business rules or complex validation scenarios that are not covered by Angular's standard validation API, ensuring data integrity and a better user experience.

What are Custom Validators?

A custom validator in Angular is a function that receives an AbstractControl instance (representing a FormControl, FormGroup, or FormArray) as an argument. It returns either a ValidationErrors object (if validation fails) or null (if validation passes). For asynchronous validation, it returns a Promise<ValidationErrors | null> or an Observable<ValidationErrors | null>.

Why Use Custom Validators?

  • Implementing unique business rules (e.g., password must contain a special character).
  • Cross-field validation (e.g., confirm password must match password).
  • Asynchronous validation (e.g., checking if a username is already taken on the server).
  • Reusing complex validation logic across multiple forms or controls.

Implementing a Custom Validator

Custom validators are typically implemented as functions that return a ValidatorFn or AsyncValidatorFn. These functions can take configuration arguments if the validator needs to be parameterized.

Synchronous Validator Example

This example shows a validator that checks if a control's value contains at least one digit.

typescript
import { AbstractControl, ValidatorFn, ValidationErrors } from '@angular/forms';

export function containsDigitValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const value = control.value;
    if (!value) {
      return null; // Don't validate empty values, use 'required' instead
    }
    const hasDigit = /[0-9]/.test(value);
    return hasDigit ? null : { containsDigit: { value: control.value } };
  };
}

Asynchronous Validator Example

This example simulates an asynchronous validator that checks if a username is available after a delay, typically used for server-side validation.

typescript
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, delay } from 'rxjs/operators';

// Simulate a service call
function checkUsernameAvailability(username: string): Observable<boolean> {
  const existingUsernames = ['admin', 'guest', 'user123'];
  const isAvailable = !existingUsernames.includes(username);
  return of(isAvailable).pipe(delay(500)); // Simulate network delay
}

export function uniqueUsernameValidator(): AsyncValidatorFn {
  return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
    const username = control.value;
    if (!username) {
      return of(null); // Don't validate empty values
    }
    return checkUsernameAvailability(username).pipe(
      map(isAvailable => (isAvailable ? null : { uniqueUsername: true })),
      catchError(() => of({ uniqueUsername: true })) // Handle errors, e.g., network down
    );
  };
}

Applying Custom Validators

Custom validators are applied to FormControl instances in the same way as built-in validators, either during instantiation or using setValidators() and setAsyncValidators() methods.

typescript
import { FormControl, Validators } from '@angular/forms';
import { containsDigitValidator, uniqueUsernameValidator } from './custom-validators';

const usernameControl = new FormControl('', {
  validators: [Validators.required, Validators.minLength(4), containsDigitValidator()],
  asyncValidators: [uniqueUsernameValidator()],
  updateOn: 'blur' // Optional: validate on blur for async validators
});
Q48.

Difference between setValue and patchValue?

Q49.

What is HttpInterceptor?

Angular's `HttpInterceptor` provides a way to intercept HTTP requests and responses to modify them or handle them programmatically. It's a powerful mechanism for managing cross-cutting concerns in your application's HTTP communication.

What is an HttpInterceptor?

An HttpInterceptor is a service that implements the HttpInterceptor interface. It sits between your application and the backend, allowing you to examine and modify outgoing HttpRequests and incoming HttpResponses. This enables a clean separation of concerns for tasks like authentication, logging, or error handling.

Why use HttpInterceptors?

Interceptors are ideal for applying global transformations or handling common tasks across all HTTP requests and responses without modifying individual service calls. They help keep your components and services focused on their primary responsibilities.

  • Authentication: Automatically adding authorization tokens to outgoing requests.
  • Logging: Centralizing request and response logging for debugging or analytics.
  • Error Handling: Implementing global error handling strategies, such as showing notifications or retrying failed requests.
  • Caching: Developing custom caching mechanisms for HTTP responses.
  • Modifying Headers: Adding or modifying common headers (e.g., Content-Type, Accept-Language).
  • Loading Indicators: Managing global loading spinners or progress bars for active HTTP requests.

How HttpInterceptors Work

When you make an HTTP request using Angular's HttpClient, the request passes through a chain of registered interceptors. Each interceptor's intercept method gets called, allowing it to inspect, modify, or even stop the request. The request then proceeds to the next interceptor in the chain or ultimately to the backend. The response follows the reverse path back through the interceptors, allowing for similar manipulation.

Creating an HttpInterceptor

1. Implement the HttpInterceptor Interface

First, create a service that implements the HttpInterceptor interface and its intercept method. The intercept method receives the HttpRequest and HttpHandler.

typescript
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Clone the request and add new header
    const authReq = request.clone({
      headers: request.headers.set('Authorization', 'Bearer my-token')
    });
    // Pass the cloned request to the next handler
    return next.handle(authReq);
  }
}

2. Register the Interceptor

Interceptors must be provided as part of the HTTP_INTERCEPTORS multi-provider token in your Angular module (typically AppModule). The multi: true option is crucial as it allows you to register multiple interceptors.

typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth.interceptor'; // Import your interceptor

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule // Essential for HTTP services
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true // Essential for multiple interceptors
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Interceptor Flow and Order of Execution

Interceptors are executed in the order they are provided in the providers array. For outgoing requests, the first registered interceptor processes the request first. For incoming responses, the last registered interceptor processes the response first, creating a stack-like behavior. This order is important when multiple interceptors modify the same request or response properties.

Common Interceptor Scenarios

ScenarioDescriptionExample Action
Add Auth TokenAttaching authentication tokens (e.g., JWT) to outgoing requests.Clone request, add `Authorization` header before `next.handle()`.
Error HandlingCentralizing error management and displaying user-friendly messages for HTTP failures.Use `catchError` operator on the `next.handle()` observable, perform action, then rethrow.
Loading SpinnerManaging global loading indicators to show network activity.Increment a counter before `next.handle()`, decrement in `finally` block of the observable.
API VersioningDynamically adding an API version to all request URLs.Clone request, modify `url` property to include version string.
Request/Response LoggingLogging details of HTTP requests and responses for debugging purposes.Log request before `next.handle()`, log response details in `tap` operator on the observable.
Q50.

How do you implement global error handling?

Implementing global error handling in Angular applications is crucial for improving user experience, debugging, and maintaining application stability. It allows you to centralize the management of errors that occur anywhere in your application, preventing crashes and providing a consistent feedback mechanism to users.

Understanding Error Handling in Angular

By default, Angular provides a basic ErrorHandler that logs errors to the browser's console. While this is helpful during development, it doesn't offer a robust solution for production environments, as it doesn't notify users, send errors to a logging service, or gracefully handle application failures. To address this, Angular allows you to provide a custom implementation of the ErrorHandler.

Creating a Custom Error Handler Service

To implement global error handling, you typically create a service that extends Angular's built-in ErrorHandler class. This custom service will override the handleError method, allowing you to define your own logic for processing errors. You can differentiate between client-side (e.g., JavaScript runtime errors) and server-side (e.g., HTTP responses) errors and react accordingly.

typescript
import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';
// Assuming you have a NotificationService for user feedback
// import { NotificationService } from './notification.service';

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {

  constructor(private injector: Injector) { }

  handleError(error: any) {
    // Use Injector to get services to avoid circular dependency with ErrorHandler
    const router = this.injector.get(Router);
    // const notificationService = this.injector.get(NotificationService);

    let message: string;
    let stackTrace: string | undefined;

    if (error instanceof HttpErrorResponse) {
      // Server Error
      message = `Server error: ${error.status} - ${error.message}`;
      stackTrace = error.error?.message || error.statusText;
      // notificationService.showError(message);
    } else if (error instanceof Error) {
      // Client Error
      message = `Client error: ${error.message}`;
      stackTrace = error.stack;
      // notificationService.showError(message);
    } else {
      // Unknown Error
      message = 'An unexpected error occurred';
      stackTrace = JSON.stringify(error);
      // notificationService.showError(message);
    }

    console.error('Error caught by GlobalErrorHandler:', { message, stackTrace, originalError: error });

    // Optionally, navigate to an error page or display a global notification
    // router.navigate(['/error'], { queryParams: { error: message } });
  }
}

Providing the Custom Error Handler

Once you have created your custom error handler service, you need to tell Angular to use it instead of the default one. This is done by providing it in the providers array of your root module (e.g., AppModule).

typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { GlobalErrorHandler } from './global-error-handler';
// import { NotificationService } from './notification.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    { provide: ErrorHandler, useClass: GlobalErrorHandler },
    // NotificationService // Provide your notification service if used
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Error Logging and Reporting

Inside your GlobalErrorHandler, you can integrate with various logging and reporting solutions. This ensures that errors are not only caught but also documented and sent to appropriate monitoring systems for analysis and resolution.

  • Remote Logging Services: Send error details to services like Sentry, LogRocket, Bugsnag, or a custom backend logging API.
  • User Feedback: Display user-friendly error messages or notifications (e.g., via a toast service) instead of crashing or showing raw error details.
  • Contextual Information: Include additional data such as the current user, application state, component tree, or browser information to aid in debugging.
  • Navigation: Optionally redirect the user to a generic error page or prompt them to reload the application.

Handling Specific HTTP Errors with Interceptors

While GlobalErrorHandler catches all errors, including HTTP errors, you might want more granular control over HTTP error responses (e.g., specific status codes like 401 Unauthorized, 404 Not Found, 500 Internal Server Error). For this, Angular's HttpInterceptor is the ideal solution. Interceptors can transform HTTP requests and responses, allowing you to catch HTTP errors before they propagate to the global error handler.

typescript
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
// import { NotificationService } from './notification.service';
// import { AuthService } from './auth.service'; // For handling 401

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {

  constructor(
    // private notificationService: NotificationService,
    // private authService: AuthService
  ) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        let errorMessage = '';
        if (error.error instanceof ErrorEvent) {
          // Client-side network error or JavaScript error
          errorMessage = `Client-side error: ${error.error.message}`;
        } else {
          // Server-side error
          switch (error.status) {
            case 401:
              errorMessage = 'Unauthorized: Please log in again.';
              // this.authService.logout(); // Redirect to login
              break;
            case 404:
              errorMessage = 'Resource not found.';
              break;
            case 500:
              errorMessage = 'Internal server error. Please try again later.';
              break;
            default:
              errorMessage = `Server Error: ${error.status} - ${error.message}`;
              break;
          }
        }

        // this.notificationService.showError(errorMessage);
        console.error('HTTP Error caught by Interceptor:', errorMessage, error);
        return throwError(() => new Error(errorMessage));
      })
    );
  }
}

To activate the HTTP interceptor, you must provide it in your AppModule using the HTTP_INTERCEPTORS token and set multi: true.

typescript
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpErrorInterceptor } from './http-error.interceptor';

// ... inside @NgModule({ providers: [] })
providers: [
  { provide: ErrorHandler, useClass: GlobalErrorHandler },
  { provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true },
  // NotificationService, // Provide your notification service if used
  // AuthService // Provide your auth service if used
]

Conclusion

By combining a custom ErrorHandler for application-wide errors and HttpInterceptor for specific HTTP response handling, you can create a robust global error management system in your Angular application. This approach ensures that all errors are caught, processed gracefully, and reported effectively, significantly improving the stability and user experience of your application.