Tuesday, March 4, 2025

Angular advanced

Authentication and authorization in Angular are typically handled using JSON Web Tokens (JWT), Route Guards, and services. Here's an overview of how they work:

1. Authentication in Angular

Authentication verifies a user's identity, usually by logging in.

Steps for Authentication:

The user enters credentials (email, password) in a login form.

The Angular app sends these credentials to a backend API.

The backend validates the credentials and returns a JWT if successful.

The Angular app stores the token (in localStorage or sessionStorage).

The token is sent in the Authorization header for future API requests.


Example of Login Service:

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private apiUrl = 'https://your-api.com/auth';

  constructor(private http: HttpClient) {}

  login(credentials: { email: string; password: string }) {
    return this.http.post<{ token: string }>(`${this.apiUrl}/login`, credentials)
      .pipe(
        tap(response => localStorage.setItem('token', response.token))
      );
  }

  logout() {
    localStorage.removeItem('token');
  }

  getToken() {
    return localStorage.getItem('token');
  }
}


---

2. Authorization in Angular

Authorization determines what users can access.

Using Route Guards for Role-Based Access:

Angular provides route guards (CanActivate, CanLoad, etc.) to restrict access based on authentication status or user roles.

Example of an Auth Guard:

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): boolean {
    const token = this.authService.getToken();
    if (token) {
      return true;
    }
    this.router.navigate(['/login']);
    return false;
  }
}

Applying Route Guards in Routing Module:

const routes: Routes = [
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
  { path: 'login', component: LoginComponent },
  { path: '**', redirectTo: 'login' }
];


---

3. Adding JWT to HTTP Requests

To ensure secure communication, attach the token to API requests using an HTTP interceptor.

Example of an HTTP Interceptor:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    if (token) {
      const cloned = req.clone({
        setHeaders: { Authorization: `Bearer ${token}` }
      });
      return next.handle(cloned);
    }
    return next.handle(req);
  }
}

Registering the Interceptor in app.module.ts:

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]


---

4. Role-Based Access Control (RBAC)

If the backend includes roles (e.g., admin, user), decode the JWT to check permissions.

Decoding the Token for Roles:

getUserRole(): string {
  const token = this.getToken();
  if (token) {
    const payload = JSON.parse(atob(token.split('.')[1]));
    return payload.role; // Assuming the token contains a "role" field
  }
  return '';
}


---

Conclusion

Authentication: Users log in, and a JWT is stored.

Authorization: Route guards restrict access based on authentication and roles.

Interceptors: Automatically attach JWTs to requests.

RBAC: Decode JWT to manage user permissions.

Webpack is a module bundler used in Angular to efficiently manage and optimize application assets like JavaScript, CSS, HTML, and images. It processes and bundles files, making them ready for deployment.

How Webpack Works in Angular

1. Module Bundling – It combines multiple files into a few optimized bundles.


2. Code Splitting – It splits code into smaller chunks for better performance (e.g., lazy loading).


3. Tree Shaking – It removes unused code to reduce bundle size.


4. Loaders and Plugins – It processes different file types (e.g., TypeScript, SCSS) and enhances functionality.


5. Hot Module Replacement (HMR) – Speeds up development by updating modules without a full reload.



Webpack in Angular CLI

Angular CLI abstracts Webpack configuration, but internally, it uses Webpack to build and serve applications.

It sounds like you’re referring to BehaviorSubject in RxJS!

A BehaviorSubject is a special type of RxJS Subject that:

Always holds the latest value and emits it to new subscribers immediately.

Requires an initial value when created.


Example Usage of BehaviorSubject in Angular

import { BehaviorSubject } from 'rxjs';

class ExampleService {
  private messageSource = new BehaviorSubject<string>('Initial Value');
  currentMessage = this.messageSource.asObservable();

  updateMessage(newMessage: string) {
    this.messageSource.next(newMessage);
  }
}

const service = new ExampleService();
service.currentMessage.subscribe(value => console.log('Subscriber 1:', value));

service.updateMessage('New Value'); // Notifies all subscribers

Key Differences from Subject:

A Subject does not store values; new subscribers won’t receive past values.

A BehaviorSubject emits the last stored value immediately to new subscribers.


Both ReplaySubject and AsyncSubject are special types of RxJS Subjects, but they behave differently in how they emit values to subscribers.

1. ReplaySubject

Stores a history of emitted values and replays them to new subscribers.

You can specify how many past values it should remember.


Example:

import { ReplaySubject } from 'rxjs';

const replaySubject = new ReplaySubject<number>(2); // Stores last 2 values

replaySubject.next(1);
replaySubject.next(2);
replaySubject.next(3);

replaySubject.subscribe(value => console.log('Subscriber:', value));
// Output: 2, 3 (last 2 values are replayed)

2. AsyncSubject

Only emits the last value when the subject completes.

If it doesn’t complete, subscribers get nothing.


Example:

import { AsyncSubject } from 'rxjs';

const asyncSubject = new AsyncSubject<number>();

asyncSubject.subscribe(value => console.log('Subscriber:', value));

asyncSubject.next(1);
asyncSubject.next(2);
asyncSubject.next(3);

asyncSubject.complete(); // Only now does it emit "3"
// Output: 3
In Angular (not AngularJS), forRoot() and forChild() are used in routing to manage the configuration of routes across different modules.

1. forRoot() – Used in the Root Module (AppRoutingModule)

It sets up the main application routes and provides the RouterModule service globally.

Should be used only once in the application, typically in AppRoutingModule.


Example:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';

const routes: Routes = [{ path: '', component: HomeComponent }];

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


---

2. forChild() – Used in Feature Modules

Used in lazy-loaded or feature modules to define additional routes.

It does not reinitialize the RouterModule but extends the existing configuration.


Example (Feature Module Routing):

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard.component';

const routes: Routes = [{ path: 'dashboard', component: DashboardComponent }];

@NgModule({
  imports: [RouterModule.forChild(routes)], // Configures child routes
  exports: [RouterModule]
})
export class DashboardRoutingModule {}

Optimizing performance in an Angular application is crucial for better speed and efficiency. Here are some key strategies:

1. Optimize Change Detection

Use OnPush change detection strategy in components to reduce unnecessary re-renders.


@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExampleComponent {}

Use trackBy in *ngFor to optimize list rendering.


<li *ngFor="let item of items; trackBy: trackByFn">{{ item.name }}</li>

trackByFn(index: number, item: any) {
  return item.id; // Track by unique ID
}

2. Lazy Load Modules

Load feature modules only when needed using Angular’s lazy loading.


const routes: Routes = [
  { path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule) }
];

3. Optimize Bundle Size

Enable Ahead-of-Time (AOT) compilation for faster rendering.


ng build --aot

Use Tree Shaking to remove unused code (enabled by default in production builds).

Use Lazy Loading for third-party libraries.


4. Optimize HTTP Requests

Use Caching and ReplaySubject to avoid redundant API calls.

Use Debouncing for user input to prevent frequent API calls.


searchTerm.pipe(debounceTime(300)).subscribe(value => this.fetchResults(value));

Use Pagination and Infinite Scrolling instead of loading large datasets.


5. Use Web Workers for Heavy Computations

Offload CPU-intensive tasks to web workers to keep the UI responsive.


6. Optimize Assets

Compress and minify images, CSS, and JavaScript files.

Use lazy loading for images with loading="lazy".


7. Use Efficient Event Handling

Use Unsubscribe from Observables to prevent memory leaks.


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

Use takeUntil to automatically unsubscribe.


private destroy$ = new Subject<void>();

this.dataService.getData()
  .pipe(takeUntil(this.destroy$))
  .subscribe(data => this.items = data);

ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

8. Enable Production Mode

Always enable production mode for better performance.


import { enableProdMode } from '@angular/core';
if (environment.production) {
  enableProdMode();
}

Build with production optimizations:


ng build --prod

What is Angular Universal?

Angular Universal is a server-side rendering (SSR) solution for Angular applications. It renders Angular apps on the server instead of the browser, improving performance, SEO, and initial load time.


---

Benefits of Angular Universal

1. Improved SEO – Search engines can fully index pages since the content is pre-rendered.


2. Faster First Load – The server sends a fully rendered page, reducing Time to First Paint (TTFP).


3. Better Performance on Slow Networks – The client receives a ready-to-view page before Angular bootstraps.


4. Social Media Sharing – Metadata (like Open Graph tags) is fully rendered for proper previews.




---

How to Add Angular Universal to an Existing Project

1. Install Angular Universal:



ng add @nguniversal/express-engine

This sets up server-side rendering with Express.js.

2. Update angular.json to include SSR support.


3. Modify server.ts (Entry Point for SSR):
This file handles server-side requests and renders the app.


4. Run the Application with SSR:



npm run dev:ssr

This starts the Angular Universal server.


---

How Angular Universal Works

On initial request, the server generates and sends a fully rendered HTML page.

Once the Angular app loads in the browser, it hydrates (merges the interactive features).



---

NgRx is a powerful library for managing state in Angular applications, inspired by Redux. It uses Reactive Extensions (RxJS) to manage state through streams, providing a predictable and scalable way to handle state management, side effects, and communication in complex applications.

Key NgRx Tools

1. Store

Centralized state management in Angular applications. The Store allows components to access and update the state in a consistent and observable manner.

Selectors are used to select pieces of state for components to consume.


// State definition
export interface AppState {
  counter: number;
}

// Store
@Injectable({
  providedIn: 'root'
})
export class CounterStore {
  constructor(private store: Store<AppState>) {}

  increment() {
    this.store.dispatch(increment()); // Dispatch an action to change state
  }
}


2. Actions

Actions are dispatched to initiate changes in the state. They describe what happened in the application.

Actions are typically used to trigger reducers to update the state.


// Action
import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter] Increment');


3. Reducers

Reducers are pure functions that specify how the state changes in response to an action.

They receive the current state and an action and return a new state.


import { createReducer, on } from '@ngrx/store';
import { increment } from './counter.actions';

export const initialState = 0;

const _counterReducer = createReducer(
  initialState,
  on(increment, (state) => state + 1)
);

export function counterReducer(state, action) {
  return _counterReducer(state, action);
}


4. Selectors

Selectors allow you to retrieve data from the store in a way that is decoupled from the components.

They are typically memoized to prevent unnecessary recomputations.


import { createSelector } from '@ngrx/store';

export const selectCounter = (state: AppState) => state.counter;

export const selectDoubleCounter = createSelector(
  selectCounter,
  (counter) => counter * 2
);


5. Effects

Effects handle side effects like HTTP requests, logging, or routing, which do not directly alter the state but may trigger actions based on external events.

They listen for actions dispatched to the store and trigger further actions as necessary.


@Injectable()
export class CounterEffects {
  loadCounter$ = createEffect(() => 
    this.actions$.pipe(
      ofType(loadCounter), // Listen for the action
      switchMap(() => 
        this.counterService.getCounter().pipe(
          map((counter) => loadCounterSuccess({ counter })), // Dispatch another action with the result
          catchError(() => of(loadCounterFailure())) // Handle errors
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private counterService: CounterService
  ) {}
}


6. Entity

NgRx Entity provides a set of helper functions to handle normalized collections of items (such as lists of objects). This helps avoid manual management of ids and collections.

It provides a simpler way to manage entities without worrying about mutations and updates.


import { createEntityAdapter, EntityState } from '@ngrx/entity';

export interface Product {
  id: string;
  name: string;
  price: number;
}

export interface State extends EntityState<Product> {}

const adapter = createEntityAdapter<Product>();
const initialState: State = adapter.getInitialState({});

const productReducer = createReducer(
  initialState,
  on(addProduct, (state, { product }) => adapter.addOne(product, state)),
  on(updateProduct, (state, { product }) => adapter.updateOne(product, state)),
  // other operations...
);




---

NgRx Tools Recap:

Store: Holds application state.

Actions: Describe events that modify state.

Reducers: Handle how state changes based on actions.

Selectors: Get slices of state.

Effects: Handle side effects like API calls.

Entity: Simplify handling of collections.


Advantages of NgRx:

Predictable state management with a clear flow of actions.

Easy to debug using Redux DevTools.

RxJS is integrated, making it highly scalable and reactive.
What is an NgModule in Angular?

An NgModule is a fundamental building block of an Angular application. It is used to group related components, directives, pipes, and services into cohesive blocks of functionality, helping with modularity, scalability, and maintainability.

Key Characteristics of NgModule:

Metadata container: It provides metadata about the components, directives, pipes, and services that belong to the module.

Organizes code: Allows you to divide your application into smaller, feature-specific modules.

Imports and exports: An NgModule can import functionality from other modules and export its own features for use in other modules.


Structure of an NgModule:

Here’s the basic structure of an NgModule:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MyComponent } from './my-component/my-component.component';

@NgModule({
  declarations: [MyComponent], // Components, directives, and pipes that belong to the module
  imports: [CommonModule], // Other modules that this module depends on
  exports: [MyComponent], // Components, directives, and pipes that can be used outside the module
  providers: [], // Services that can be used within the module
})
export class MyModule { }

Important Parts of an NgModule:

1. declarations: This is where you declare components, directives, and pipes that belong to the module.


2. imports: Imports other modules that you need, such as CommonModule or feature modules like FormsModule and HttpClientModule.


3. exports: Makes components, directives, and pipes available to other modules.


4. providers: Specifies services that should be available in the injector for this module.


5. bootstrap: This is used only in the root module (typically AppModule). It identifies the root component to bootstrap when the app is launched.




---

Importance of NgModule

1. Modularity:

Organizes your application into cohesive blocks of code, which can be independently developed, maintained, and tested.

Example: A feature module might handle a user profile, while the core module handles services like authentication.



2. Lazy Loading:

NgModule allows you to implement lazy loading, meaning you can load modules only when they are needed. This is especially useful in large applications for better performance.

Example:

const routes: Routes = [
  { path: 'profile', loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule) }
];



3. Separation of Concerns:

By grouping related components, services, and other parts of your app into distinct modules, you maintain separation of concerns, making the application easier to scale and maintain.



4. Reusability:

You can create reusable modules (like a shared module with common components) that can be imported into different parts of the application.

Example: A SharedModule can have commonly used components like buttons, headers, etc.



5. Dependency Injection:

NgModule allows you to define providers for services, and Angular will manage the dependency injection (DI) for you, making services available where needed.



6. Improves Performance:

With NgModule, you can manage code bundling and tree-shaking, ensuring that only the code that's actually used is included in the final bundle.

Lazy-loaded modules ensure that parts of your app are loaded only when required, reducing the initial loading time.





---

How Angular Uses NgModule:

1. Root Module (AppModule): The root module of an Angular application, AppModule, is where the application is bootstrapped. It’s the entry point that brings together other modules and bootstraps the root component.


2. Feature Modules: These are modules that group specific parts of an application. For example, a UserModule might handle user-related components and services.


3. Shared and Core Modules:

SharedModule: Contains common components, directives, and pipes that need to be used across multiple modules.

CoreModule: Typically contains singleton services, like authentication and logging, which are needed across the app.





---

Summary: Why is NgModule Important?

It helps organize the application into logical, manageable units.

Facilitates lazy loading and improves app performance.

Ensures dependency management via providers and the DI system.

Allows better scalability by keeping the app modular.

Supports code sharing and reusability.
AoT (Ahead-of-Time) Compilation is a process in Angular that compiles the application during the build phase, before it is executed in the browser, rather than compiling it in the browser at runtime (just-in-time or JIT compilation).

How AoT Works:

During the build process, Angular compiles the TypeScript and HTML templates into efficient JavaScript code.

This compiled code is then bundled and optimized, which reduces the size of the JavaScript and speeds up the initial page load.

AoT compilation also checks for errors in templates, which helps catch issues earlier during the build rather than at runtime.


Benefits of AoT:

1. Faster Rendering: Since templates and components are pre-compiled, the browser doesn’t need to compile them at runtime, resulting in faster rendering.


2. Smaller Bundle Size: AoT removes unnecessary code, making the final JavaScript bundle smaller and faster to load.


3. Better Error Detection: AoT catches template errors during the build process, which prevents runtime errors in the browser.


4. Improved Security: AoT helps to reduce the attack surface by eliminating certain runtime components, making the app more secure.



AoT vs. JIT (Just-in-Time) Compilation:

Enabling AoT in Angular:

By default, Angular CLI enables AoT in production builds. You don’t have to do anything extra to enable it for production. To build an AoT application, you use the following command:

ng build --prod

This command compiles your Angular application using AoT and optimizes it for production.

Summary:

AoT improves application performance by pre-compiling the code, reducing the amount of work needed in the browser, and making the app more efficient and secure. It's an essential feature for building production-ready Angular applications.

Change Detection in Angular

Change detection in Angular is the process by which the framework keeps track of changes to data in your application and updates the view accordingly. When something changes (like a variable value), Angular needs to check if the view needs to be updated based on that change.

How Change Detection Works:

1. Component State Update: When a component’s state (its properties or bindings) changes, Angular needs to check whether the view should be updated to reflect those changes.


2. Checking the View: Angular compares the current state of the component to the new state, and if something has changed, it triggers the update of the view.


3. Tree of Components: Angular uses a tree-like structure of components. It starts from the root component and checks the component's template to see if there are any changes to be made.


4. View Update: If changes are detected, Angular updates the DOM to reflect the new state.



Change Detection Strategies in Angular

Angular uses two types of change detection strategies:

1. Default Change Detection (ChangeDetectionStrategy.Default)

This is the default strategy for all components unless otherwise specified.

In this strategy, Angular checks all components in the component tree to see if there have been changes, even if the change occurred in a single component. This can sometimes lead to performance bottlenecks in large applications.

Angular performs change detection on all components in the component tree when any event happens, such as user interaction, HTTP request completion, or even setInterval.


2. OnPush Change Detection (ChangeDetectionStrategy.OnPush)

When you set the OnPush strategy, Angular only checks a component’s view if one of the following conditions is met:

1. Input Properties Change: If any of the @Input properties change.


2. Event Triggers: If an event occurs inside the component (e.g., button click).


3. Manual Trigger: If the component triggers change detection manually via ChangeDetectorRef.markForCheck() or ChangeDetectorRef.detectChanges().



This strategy improves performance because Angular will not check the component unless one of the above conditions happens, which avoids unnecessary checks on components that haven’t changed.


Example:

@Component({
  selector: 'app-item',
  templateUrl: './item.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush // OnPush strategy
})
export class ItemComponent {
  @Input() item: string; // Only checked when 'item' changes
}

How Does Angular Trigger Change Detection?

Change detection is triggered by various events in Angular:

1. User Input: When a user interacts with the app (clicks, types, etc.).


2. Async Events: Events like HTTP requests, timers, or observable subscriptions.


3. Component Lifecycle: Angular checks for changes during various lifecycle hooks like ngOnChanges, ngDoCheck, and ngAfterViewChecked.



Zones and NgZone

Angular uses Zone.js, a library that allows Angular to keep track of asynchronous tasks (e.g., setTimeout, HTTP requests). When any async operation completes, NgZone triggers Angular’s change detection to update the view.

This is why Angular can detect changes after an event (such as HTTP or user input) without the need to manually trigger change detection.


Manual Change Detection

While Angular does a lot of change detection automatically, you can also manually control it when necessary using ChangeDetectorRef:

markForCheck(): Marks a component for check on the next change detection cycle, especially useful with OnPush strategy.

detectChanges(): Manually checks the current component and its children for changes.


Example:

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

@Component({
  selector: 'app-example',
  template: '<p>{{ message }}</p>',
})
export class ExampleComponent {
  message = 'Hello';

  constructor(private cdRef: ChangeDetectorRef) {}

  updateMessage() {
    this.message = 'Hello, Angular!';
    this.cdRef.detectChanges(); // Manually trigger change detection
  }
}

How Does Change Detection Affect Performance?

Default strategy can be resource-heavy, especially for large applications, as it checks all components.

OnPush strategy improves performance because it only checks components that are affected by a change.

Manual triggering can be used to prevent unnecessary change detection cycles.


Summary:

Change detection ensures the view stays in sync with the model.

Angular supports two strategies: Default and OnPush.

OnPush improves performance by reducing the number of checks.

Change detection is triggered by user interactions, async events, and lifecycle hooks.

You can also manually control change detection using ChangeDetectorRef.

Angular provides robust form validation mechanisms to ensure data accuracy and completeness. There are two primary approaches to form validation in Angular: template-driven forms and reactive forms.

Template-Driven Forms

In template-driven forms, validation is added using HTML attributes and Angular directives. Angular uses directives to match these attributes with validator functions in the framework. For example, to validate a name field, you can use the required and minlength attributes along with a custom validator directive:

<form #actorForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" class="form-control" required minlength="4" appForbiddenName="bob" [(ngModel)]="actor.name" #name="ngModel">
<div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert">
<div *ngIf="name.hasError('required')">Name is required.</div>
<div *ngIf="name.hasError('minlength')">Name must be at least 4 characters long.</div>
<div *ngIf="name.hasError('forbiddenName')">Name cannot be Bob.</div>
</div>
</div>
</form>

Reactive Forms

In reactive forms, the form model is defined in the component class, and validators are added directly to the form control model. This approach provides more flexibility and control over the form validation logic. For example, to validate a name field in a reactive form, you can use the Validators class:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';

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

ngOnInit(): void {
this.actorForm = new FormGroup({
name: new FormControl('', [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i),
]),
role: new FormControl(''),
skill: new FormControl('', Validators.required),
});
}

get name() {
return this.actorForm.get('name');
}
}

Custom Validators

Sometimes, built-in validators may not suffice, and you may need to create custom validators. A custom validator is a function that takes a control instance and returns either a set of validation errors or null. For example, a custom validator to forbid a specific name can be defined as follows:

import { AbstractControl, ValidationErrors, ValidatorFn } 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;
};
}

Asynchronous Validators

Asynchronous validators are used when validation logic involves asynchronous operations, such as HTTP requests. These validators return a Promise or Observable that later emits a set of validation errors or null. For example, an async validator to check if a role is unique can be implemented as follows:

import { Directive, Injectable } from '@angular/core';
import { AsyncValidator, AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ActorsService } from './actors.service';

@Injectable({ providedIn: 'root' })
export class UniqueRoleValidator implements AsyncValidator {
constructor(private actorsService: ActorsService) {}

validate(control: AbstractControl): Observable<ValidationErrors | null> {
return this.actorsService.isRoleTaken(control.value).pipe(
map((isTaken) => (isTaken ? { uniqueRole: true } : null)),
catchError(() => of(null))
);
}
}

Cross-Field Validation

Cross-field validation involves comparing the values of different fields in a form and validating them in combination. For example, to ensure that the name and role fields do not have the same value, you can create a cross-field validator:

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

export const unambiguousRoleValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
const name = control.get('name');
const role = control.get('role');
return name && role && name.value === role.value ? { unambiguousRole: true } : null;
};

By leveraging these validation techniques, you can ensure that your Angular forms are robust, user-friendly, and maintain high data quality.


To implement a custom directive in Angular, follow these steps:

  1. Create a new directive using the CLI: ng generate directive myDirective.
  2. Implement the directive logic in the new file.
  3. Use the directive in your template12.

No comments:

Post a Comment