Q11.

What are pipes in Angular? Can you create custom pipes?

Pipes are a powerful feature in Angular used to transform data before it is displayed in the view. They enhance data presentation by allowing data manipulation directly within templates.

Pipes are simple functions used in Angular templates to transform data. They take data as input and return transformed data, making it easy to format, filter, or sort information directly within your HTML without altering the component's underlying data.

Angular comes with several built-in pipes like DatePipe, UpperCasePipe, LowerCasePipe, CurrencyPipe, and PercentPipe. For example, the DatePipe can format a JavaScript Date object into various human-readable formats.

html
<p>Original date: {{ myDate }}</p>
<p>Formatted date: {{ myDate | date:'shortDate' }}</p>
<p>Currency: {{ price | currency:'USD':'symbol':'1.2-2' }}</p>

Creating Custom Pipes

Yes, Angular allows you to create custom pipes to encapsulate any data transformation logic that isn't covered by the built-in pipes or to reuse complex transformations across your application. This promotes reusability and keeps your component logic clean.

  • Generate a pipe using the Angular CLI: ng generate pipe <pipe-name>.
  • Implement the PipeTransform interface and its transform method. The transform method takes the input value and optional arguments, returning the transformed value.
  • Decorate the class with @Pipe decorator, providing a unique name property for the pipe.
  • Declare the custom pipe in an Angular module (typically app.module.ts) or a standalone component/pipe.

Example: Custom Reverse String Pipe

typescript
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'reverseString',
  standalone: true // Or declare in a module
})
export class ReverseStringPipe implements PipeTransform {
  transform(value: string): string {
    if (!value) return '';
    return value.split('').reverse().join('');
  }
}

To use this pipe in a template, you would simply apply it like any other pipe: <p>{{ 'Hello Angular' | reverseString }}</p> which would output 'ralugnA olleH'.

Q12.

What is dependency injection in Angular?

Dependency Injection (DI) is a core design pattern used in Angular to manage how components and services obtain the dependencies they need. It's a way of decoupling the creation of objects from their usage, making applications more modular, testable, and maintainable.

What is Dependency Injection?

In its essence, Dependency Injection is a software design pattern that deals with how components get hold of their dependencies. Instead of a component creating its dependencies, an 'injector' provides them. This inversion of control separates the responsibility of creating an object from the responsibility of using it.

How Angular Implements DI

Angular has its own sophisticated DI system. It relies on three main concepts: Injectors, Providers, and the @Injectable() decorator. When Angular creates a component, it first checks the constructor for any dependencies. If found, it consults its injector to find a 'provider' that can supply an instance of that dependency.

Providers

A provider is a recipe that tells an injector how to create or obtain a dependency. It maps a token (usually a class type) to a factory function that produces the dependency's value. Providers are configured at different levels: component, module, or application-wide (using providedIn: 'root' for tree-shakable services).

Injectors

An injector is an object that can create dependency instances and inject them into other objects. Angular maintains a hierarchical tree of injectors. Each component has its own injector, and these injectors inherit from their parent's injector. This hierarchy allows for different instances of services to be provided at different levels of the application.

Key Benefits

  • Testability: Easier to mock dependencies when testing components in isolation.
  • Maintainability: Components become less coupled, making code easier to change and understand.
  • Reusability: Services can be reused across multiple components without tight coupling.
  • Scalability: Helps manage complex applications by breaking them into manageable, interconnected parts.

Example Usage

Here's a simple example showing a LoggerService being injected into a MyComponent. The @Injectable() decorator marks LoggerService as a class that can be injected, and providedIn: 'root' makes it a singleton available throughout the application.

typescript
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  log(message: string) {
    console.log(`[Logger]: ${message}`);
  }
}
typescript
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({
  selector: 'app-my',
  template: '<button (click)="logMessage()">Log Something</button>'
})
export class MyComponent {
  constructor(private logger: LoggerService) {}

  logMessage() {
    this.logger.log('Hello from MyComponent!');
  }
}
Q13.

What is the Angular CLI?

Q14.

What is interpolation in Angular?

Q15.

What is event binding?

Q16.

Explain the Angular component lifecycle hooks.

Angular components and directives have a well-defined lifecycle, managed by Angular itself. Lifecycle hooks allow you to tap into key moments during a component's or directive's existence, from its creation to its destruction, enabling you to perform actions at specific times.

Understanding Lifecycle Hooks

Every Angular component has a lifecycle. Angular provides lifecycle hook interfaces that allow developers to hook into these lifecycle events, executing custom logic at precise moments. These hooks are methods prefixed with ng that are called by Angular automatically.

Primary Lifecycle Hooks

ngOnChanges

Responds when Angular sets or resets data-bound input properties. The method receives a SimpleChanges object of current and previous property values. This hook is called before ngOnInit and whenever one or more data-bound input properties change.

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

@Component({ selector: 'app-child', template: '<p>Value: {{ data }}</p>' })
export class ChildComponent implements OnChanges {
  @Input() data: string;

  ngOnChanges(changes: SimpleChanges): void {
    for (let propName in changes) {
      let change = changes[propName];
      let current = JSON.stringify(change.currentValue);
      let previous = JSON.stringify(change.previousValue);
      console.log(`${propName}: currentValue = ${current}, previousValue = ${previous}`);
    }
  }
}

ngOnInit

Initializes the component or directive after Angular first displays the data-bound properties and sets the component's input properties. This is a good place to fetch data from a server or perform other initialization tasks that don't depend on input changes.

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

@Component({ selector: 'app-hello', template: '<p>{{ message }}</p>' })
export class HelloComponent implements OnInit {
  message: string;

  ngOnInit(): void {
    this.message = 'Component initialized!';
    console.log('ngOnInit called');
  }
}

ngDoCheck

Detects and acts upon changes that Angular can't or won't detect on its own. It's called immediately after ngOnChanges and ngOnInit, and on every subsequent change detection run. Use with caution as it can impact performance.

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

@Component({ selector: 'app-docheck', template: '<p>DoCheck component</p>' })
export class DoCheckComponent implements DoCheck {
  ngDoCheck(): void {
    console.log('ngDoCheck called');
    // Implement custom change detection logic here
  }
}

ngAfterContentInit

Responds after Angular projects external content into the component's view. This hook is called once after the first ngDoCheck.

typescript
import { Component, AfterContentInit, ContentChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-parent-content',
  template: `
    <p>Parent Content</p>
    <ng-content></ng-content>
  `
})
export class ParentContentComponent implements AfterContentInit {
  @ContentChild('projectedContent') projectedContent: ElementRef;

  ngAfterContentInit(): void {
    console.log('ngAfterContentInit called. Projected content:', this.projectedContent?.nativeElement.textContent);
  }
}

ngAfterContentChecked

Responds after Angular checks the content projected into the component. Called after ngAfterContentInit and every subsequent ngDoCheck.

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

@Component({ selector: 'app-checked-content', template: '<ng-content></ng-content>' })
export class CheckedContentComponent implements AfterContentChecked {
  ngAfterContentChecked(): void {
    console.log('ngAfterContentChecked called');
  }
}

ngAfterViewInit

Responds after Angular initializes the component's views and child views. This hook is called once after the first ngAfterContentChecked.

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

@Component({
  selector: 'app-view-init',
  template: `
    <p #viewElement>This is part of the view.</p>
  `
})
export class ViewInitComponent implements AfterViewInit {
  @ViewChild('viewElement') viewElement: ElementRef;

  ngAfterViewInit(): void {
    console.log('ngAfterViewInit called. View element:', this.viewElement.nativeElement.textContent);
  }
}

ngAfterViewChecked

Responds after Angular checks the component's views and child views. Called after ngAfterViewInit and every subsequent ngAfterContentChecked.

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

@Component({ selector: 'app-view-checked', template: '<p>View Checked Component</p>' })
export class ViewCheckedComponent implements AfterViewChecked {
  ngAfterViewChecked(): void {
    console.log('ngAfterViewChecked called');
  }
}

ngOnDestroy

Cleans up just before Angular destroys the component or directive. This is the place to unsubscribe from observables, detach event handlers, and prevent memory leaks. It's called just before Angular destroys the directive/component.

typescript
import { Component, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';

@Component({ selector: 'app-destroy', template: '<p>Destroy Component</p>' })
export class DestroyComponent implements OnDestroy {
  private timerSubscription: Subscription;

  constructor() {
    this.timerSubscription = interval(1000).subscribe(num => console.log('Timer:', num));
  }

  ngOnDestroy(): void {
    console.log('ngOnDestroy called: Cleaning up resources');
    if (this.timerSubscription) {
      this.timerSubscription.unsubscribe();
    }
  }
}

Order of Execution

The lifecycle hooks execute in a predictable sequence. For components, content hooks are called before view hooks, as content is projected before the component's own view is fully initialized. Changes to data-bound input properties trigger ngOnChanges.

  • ngOnChanges (when input properties change)
  • ngOnInit
  • ngDoCheck
  • ngAfterContentInit
  • ngAfterContentChecked
  • ngAfterViewInit
  • ngAfterViewChecked
  • ngOnDestroy (when component is destroyed)

Lifecycle Hooks Summary

HookPurposeWhen Called
ngOnChangesRespond when Angular sets/resets data-bound input properties.Before ngOnInit and on input changes.
ngOnInitInitialize the component/directive after Angular first displays data-bound properties.Once after ngOnChanges and component initialization.
ngDoCheckDetect and act upon changes not detected by Angular's default change detection.Immediately after ngOnChanges/ngOnInit and on every subsequent change detection run.
ngAfterContentInitRespond after Angular projects external content into the component's view.Once after the first ngDoCheck.
ngAfterContentCheckedRespond after Angular checks the content projected into the component.After ngAfterContentInit and every subsequent ngDoCheck.
ngAfterViewInitRespond after Angular initializes the component's views and child views.Once after the first ngAfterContentChecked.
ngAfterViewCheckedRespond after Angular checks the component's views and child views.After ngAfterViewInit and every subsequent ngAfterContentChecked.
ngOnDestroyClean up just before Angular destroys the component/directive.Just before Angular destroys the component/directive.
Q17.

What is a service in Angular and why is it used?

Q18.

What is lazy loading in Angular?

Lazy loading is a design pattern in Angular that allows you to load parts of your application only when they are needed. Instead of loading all modules at once when the application starts, lazy loading fetches modules on demand, typically when a user navigates to a specific route.

What is Lazy Loading?

In the context of large Angular applications, bundling all modules into a single file can lead to long initial load times. Lazy loading addresses this by splitting the application into smaller bundles, which are then loaded asynchronously. This strategy significantly improves the application's startup performance by reducing the initial payload, as the browser only downloads the necessary code for the current view.

  • Reduces initial bundle size
  • Faster application startup time
  • Improved user experience, especially on slower networks
  • Optimizes resource utilization

How Lazy Loading Works

Angular implements lazy loading primarily through its router. When you configure routes, instead of importing the module directly, you use the loadChildren property. This property tells the Angular router to load the module asynchronously using the browser's dynamic import capabilities when a particular route is activated. Angular then creates a separate JavaScript bundle for each lazy-loaded module.

typescript
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
  },
  {
    path: 'products',
    loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Implementing Lazy Loading

  • Create a feature module (e.g., AdminModule, ProductsModule) with its own routing module (AdminRoutingModule, ProductsRoutingModule).
  • In the feature module's routing module, define the routes for that specific feature.
  • In your main AppRoutingModule, configure a route using loadChildren to point to the feature module.
  • Ensure the feature module is not directly imported into any eagerly loaded module (like AppModule), as this would defeat lazy loading.

Advantages of Lazy Loading

  • Performance Enhancement: Significantly reduces the initial load time of the application.
  • Resource Efficiency: Only loads the code that is immediately required, saving bandwidth and memory.
  • Scalability: Makes it easier to manage and scale larger applications by breaking them into manageable chunks.
  • Improved User Experience: Users perceive the application as faster and more responsive.
Q19.

What are route guards in Angular?

Angular route guards are interfaces that can be implemented to control navigation to or away from routes. They provide a powerful way to manage access to different parts of your application based on various conditions, such as user authentication status, authorization roles, or whether data needs to be pre-fetched.

What are Route Guards?

Route guards are a feature in Angular's router that allows you to control navigation based on specific conditions. They prevent unauthorized access to routes, block users from leaving a route with unsaved changes, or even pre-fetch data before a component is activated. They essentially act as 'gatekeepers' for your application's routes.

Types of Route Guards

Angular provides several interfaces that you can implement to create different types of route guards, each serving a specific purpose in the navigation lifecycle.

  • CanActivate: Determines if a route can be activated (i.e., if the user can navigate to it). Useful for authentication and authorization.
  • CanActivateChild: Determines if the children of a route can be activated. Applies to child routes.
  • CanDeactivate: Determines if a user can leave a route. Useful for preventing users from navigating away with unsaved changes.
  • Resolve: Pre-fetches data before a route is activated, ensuring that the component has all necessary data upon initialization.
  • CanLoad: Determines if a lazy-loaded module can be loaded. Prevents loading of modules that the user doesn't have access to.

Implementing a Route Guard

To create a route guard, you define a class that implements one or more of the guard interfaces. The guard's method (e.g., canActivate for CanActivate) must return a boolean, UrlTree, Observable<boolean | UrlTree>, or Promise<boolean | UrlTree>.

typescript
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service'; // Assume this service exists

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      if (this.authService.isLoggedIn()) {
        return true; // User is logged in, allow access
      } else {
        // User is not logged in, redirect to login page
        return this.router.createUrlTree(['/login']);
      }
  }
}

Once defined, you apply the guard to a route configuration in your app-routing.module.ts or feature module routing files using the canActivate (or canActivateChild, canDeactivate, resolve, canLoad) property.

typescript
import { Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { AuthGuard } from './auth.guard';

const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [AuthGuard] // Apply the AuthGuard here
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    canLoad: [AuthGuard] // Apply to lazy-loaded module
  },
  // ... other routes
];

Common Use Cases

  • Authentication: Ensuring only logged-in users can access certain routes (e.g., a dashboard or profile page).
  • Authorization: Checking if a user has the necessary roles or permissions to view a specific resource (e.g., an admin panel).
  • Preventing Unsaved Changes: Prompting users before they navigate away from a form with unsaved data.
  • Data Pre-fetching: Loading necessary data from a backend service before a component is even rendered, ensuring a smooth user experience.
  • Lazy Loading Control: Preventing the loading of entire modules if the user isn't authorized to access any of its routes.
Q20.

What is the difference between template-driven forms and reactive forms?

Angular provides two distinct approaches for building forms: Template-Driven Forms and Reactive Forms. While both facilitate data input and validation, they differ fundamentally in how they are constructed and managed, catering to different complexities and development preferences.

Template-Driven Forms

Template-driven forms are built almost entirely within the component's template, relying heavily on directives like ngModel and ngForm. They are easier to get started with for simple forms, as much of the form logic is implicitly handled by Angular's directives through two-way data binding.

  • Minimal component code; logic mostly in the template.
  • Rely on two-way data binding ([(ngModel)]).
  • Form controls are created implicitly by Angular directives.
  • Easier for simple forms and quick prototyping.
  • Less testable due to reliance on DOM interaction for validation and state.

Reactive Forms

Reactive forms (also known as model-driven forms) are built programmatically in the component class, providing a more explicit and predictable way to manage form state. They use a reactive programming style with observables and are preferred for complex scenarios due to their flexibility and testability.

  • Form model is explicitly defined in the component class (FormGroup, FormControl, FormArray).
  • Rely on immutable data structures and one-way data flow.
  • More control over validation, state changes, and testing.
  • Better for complex forms, dynamic forms, and scaling.
  • Easier to test and debug as the form model is separate from the template.

Key Differences

FeatureTemplate-Driven FormsReactive Forms
SetupMinimal component code, directives in templateExplicitly build form model in component class
Form ModelImplicitly created by directivesExplicitly created (FormGroup, FormControl, FormArray)
Data FlowTwo-way data binding (`[(ngModel)]`)One-way data flow, immutable data
ValidationDirectives in template, less programmatic controlProgrammatic validation functions
ScalabilityLess scalable for complex formsHighly scalable and maintainable for complex forms
TestabilityChallenging to test (relies on DOM)Easily testable (model separate from view)
Asynchronous OperationsLimited support for async validation/data flowStrong support for async validation/data flow (Observables)