Migration

— by Youcef MADADI, FREELANCER & PROJECT MANAGER

Who am I?

an IT enthusiast who is intrested in the web dev industry and its technology 

Index for 1st session

Introduction to Angular

What is Angular?

  • A TypeScript-based open-source framework.

  • Developed and maintained by Google.

  • Ideal for building scalable and dynamic single-page applications (SPAs).

  • Component-Based Architecture:

    • Reusable UI components.

  • Two-Way Data Binding:

    • Syncs data between the model and view.

  • Dependency Injection:

    • Simplifies managing app dependencies.

  • Built-In Tools:

    • Routing, HTTP services, and forms.

Why Angular?

  • Modular Structure:

    • Organizes code into modules.

  • Directives:

    • Customizes HTML behavior.

  • Pipes:

    • Transforms data in templates.

  • Reactive Programming:

    • State management with RxJS (Older versions) and Signals (New versions).

  • Optimized Rendering:

    • Improved with Angular 17/18.

Key Features

Comparison with Other Frameworks

Feature Angular React Vue
Language TypeScript JavaScript JavaScript
Learning Curve Moderate Steep Easy
Core Strength Full framework UI library Versatile
  • Handles complex forms (e.g., claim submissions).
  • Supports dynamic dashboards for real-time policy tracking.
  • Built for scalable and secure enterprise applications.

Why AXA needs

Angular?

Prequisit

JavaScript Basics:

  • Variables, functions, and event handling.
  • ES6+ concepts like arrow functions, destructuring, and modules.

1.

TypeScript Basics:

  • Strong typing: interfaces, classes, and enums.
  • Familiarity with decorators.

2.

HTML & CSS:

  • Understanding of HTML structure.
  • Basic CSS for styling components.

3.

Node.js and npm:

  • Installing and using Node.js.
  • Knowledge of npm for managing dependencies.

4.

Angular Updates from Versions <16 to 17 & 18

General Philosophy of Angular Updates focused mainly on:

  • Performance Enhancements: Faster and lighter applications.

  • Simplification: Reducing boilerplate and improving developer experience.

  • Modern Features: Embracing cutting-edge web technologies.

Signals for State Management

  • Reactive primitives to manage state in a simpler, more declarative way.
  • Alternative to RxJS Observables for state updates.
  • Minimal boilerplate compared to Observables.
  • Intuitive API for tracking and reacting to state changes.
import { signal } from '@angular/core';
const count = signal(0);
count.set(1); // Updates the state
  • Angular 17:

    • Faster runtime with Partial Hydration:

      • Only necessary components are hydrated, improving performance.

    • Improved debugging for hydration mismatches.

Enhanced Client-Side Rendering (CSR)

  • Angular 18:

    • Advanced preloading strategies for CSR optimization.

    • Support for Edge Rendering.

  • Enhanced tree-shakable providers to reduce application size.
  • Improved debugging for DI errors.

Optimized Dependency Injection (DI)

  • Cleaner syntax than ngIf/ngFor.

Simplified Structural Directives (@for and @if)

<!-- Old Way -->
<div *ngIf="condition"></div>

<!-- New Way -->
@if (condition) {
  <div></div>
}

Lazy Loading

  • Improved performance with fine-grained control over module preloading.

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

Let's setup a new project

What Are Angular Decorators?

@Component({
  selector: 'app-example',
  template: '<p>Hello, world!</p>'
})
export class ExampleComponent {}
  • Special TypeScript feature for attaching metadata to classes, methods, properties, or parameters.
  • Widely used in Angular to define building blocks like components, directives, and services.
  • Help Angular understand how to process and instantiate elements in your app.
  • Types of Decorators
    • Class: @Component, @Directive, @Injectable, @Pipe.
    • Property: @Input, @Output.
    • Method: @HostListener.
    • Parameter: @Inject.
  • Key Benefits

    • Reduces boilerplate.

    • Enables declarative programming.

    • Improves code modularity.

Understanding  @Component

Key Metadata Properties

  • selector:

    • Defines how the component is called in HTML.
    • Example: <app-example></app-example>.
  • template / templateUrl:

    • Inline HTML or external HTML file defining the UI.
  • styles / styleUrls:

    • Inline styles or external CSS/SCSS files for the component.
  • standalone:

    • Declares if the component is independent of @NgModule.
  • imports (if standalone):

    • Specifies modules, components, directives, or pipes used in the component.
  • A class decorator in Angular that marks a class as a component.
  • Provides metadata about how the component should be processed, instantiated, and used.
@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: './example.component.css',
  standalone: true,
  imports: [ComponentA, FormsModule]
})
export class ExampleComponent {}

State Management - Normal State vs. Signals

export class CounterComponent {
  count: number = 0;

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }
}
  • Automatically track dependencies.
  • Simplify state updates and reduce boilerplate.
  • Traditionally managed using class variables in components.

  • State changes trigger Angular's change detection to update the DOM.

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

@Component({
  selector: 'app-counter',
  template: `
    <h1>{{ count() }}</h1>
    <button (click)="increment()">Increment</button>
    <button (click)="decrement()">Decrement</button>
  `,
})
export class CounterComponent {
  count = signal(0);

  increment() {
    this.count.set(this.count() + 1);
  }

  decrement() {
    this.count.set(this.count() - 1);
  }
}

Passing Props with @Input and input<T>()

<app-child [prop]="valueFromParent"></app-child>

using @Input:

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

@Component({
  selector: 'app-child',
  template: `<p>Received: {{ prop }}</p>`
})
export class ChildComponent {
  @Input() prop!: string;
}

Passing Props with @Input and input<T>()

<app-child [prop]="valueFromParent"></app-child>

using @Input:

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

@Component({
  selector: 'app-child',
  template: `<p>Received: {{ prop() }}</p>`
})
export class ChildComponent {
  prop = input<string>();
}

When to Use Which?

Feature Use Case
@Input Simple, non-reactive data binding.
input<T>() For reactive, signal-based workflows with real-time updates.

Angular Lifecycle Hooks

  • Lifecycle Hooks are special methods in Angular that allow you to tap into key phases of a component or directive's lifecycle.
  • Helps manage initialization, change detection, cleanup, and more.
import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `<p>Lifecycle Example</p>`,
})
export class ExampleComponent implements OnInit, OnDestroy {
  timer: any;

  ngOnInit() {
    console.log('Component initialized!');
    this.timer = setInterval(() => console.log('Timer running...'), 1000);
  }

  ngOnDestroy() {
    console.log('Component destroyed!');
    clearInterval(this.timer); // Cleanup resources
  }
}

Angular Lifecycle Hooks

  • Lifecycle Hooks are special methods in Angular that allow you to tap into key phases of a component or directive's lifecycle.
  • Helps manage initialization, change detection, cleanup, and more.
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

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

  ngOnChanges(changes: SimpleChanges): void {
    // Detecting changes in input properties
    if (changes['value']) {
      const prevValue = changes['value'].previousValue;
      const currentValue = changes['value'].currentValue;
      console.log(`Input 'value' changed from ${prevValue} to ${currentValue}`);
    }
  }
}

Routing for Angular 17+

import { provideRouter } from '@angular/router';

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)],
});

Standalone Component Routing:

import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: '**', component: NotFoundComponent }, // Wildcard route
];
  • No need to define @NgModule for routing.

Routing for Angular 17+

<nav>
  <a routerLink="/">Home</a>
  <a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>

To navigate

import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', loadComponent:()=>{return import('./components/about.component')
  					.then(c=>c.AboutComponent)} }, // lazy
  { path: '**', component: NotFoundComponent }, // Wildcard route
];

Lazy loading

Understanding  @Injectable

What is Dependency Injection (DI)?

  • DI is a design pattern used to implement inversion of control, where the framework (Angular) manages how objects are created and passed to components, services, etc.
  • @Injectable marks a class as a service or provider that can be injected into other classes.

How @Injectable Works:

  • Angular creates an instance of the class and injects its dependencies when required.
  • The class can have services or other providers injected into its constructor.
  • @Injectable is a decorator used in Angular to mark a class as available for dependency injection (DI).
  • It tells Angular that this class can have dependencies injected into it, making it part of the Angular Dependency Injection system.
  • Commonly used for services, but can be applied to any class that Angular needs to manage.
@Injectable({
  // Makes the service available app-wide
  providedIn: 'root'  
})
export class DataService {
  constructor() {
    console.log('DataService created');
  }

  getData() {
    return 'Data from DataService';
  }
}

Angular Services

ng generate service data
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root', // Singleton service at the root level
})
export class DataService {
  private data: string[] = [];
  
  getData() {
    return this.data;
  }
  
  addData(item: string) {
    this.data.push(item);
  }
}
  • A reusable class in Angular that encapsulates business logic, data fetching, or shared functionality.
  • Promotes separation of concerns by keeping components focused on the UI.

Angular Services

import { Component,signal } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-example',
  template: `<p>{{ items }}</p>`,
})
export class ExampleComponent implements OnInit {
  items=signal<string[]>([]);
  dataService= inject(DataService)
  
  ngOnInit( ) :void{
    this.items.set(this.dataService.getData());
  }
}
  • how to inject it in lifecycle

Angular Services

export class DataService {
  private data: string[] = [];
  http= inject(HttpClient)

  getFromApi( ){
   	this.http.get('https://url')
  }
  
  addData(item: string) {
    this.data.push(item);
  }
}
  • Using API
providers:[
	provideHttpClient, // it has to be added to the configuration
    ...
]

Angular Services

import { Component,signal } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-example',
  template: `<p>{{ items }}</p>`,
})
export class ExampleComponent implements OnInit {
  items = signal<string[]>([]);
  dataService= inject(DataService)
  
  ngOnInit( ) :void{
  	this.dataService.getFromApi()
    .pipe(catchError(console.error))
    .subscribe(data=>{    
    	this.items.set(data);
    })
  }
}
  • Using API

Routing guard (using Injectable)

ng generate guard auth
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

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

  canActivate(): boolean {
    const isLoggedIn = !!localStorage.getItem('token');
    if (!isLoggedIn) {
      this.router.navigate(['/login']);
    }
    return isLoggedIn;
  }
}
  • Route Guards control access to routes based on specific conditions.
 {  path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }

Directives

  • Directives are classes in Angular that allow you to add behavior to elements in the DOM.
  • They are a powerful way to extend HTML capabilities without changing the structure of the HTML elements.
  • Three types of directives:
    1. Component Directives: Directives with templates (essentially components).
    2. Structural Directives: Modify the structure of the DOM (e.g., *ngIf, *ngFor).
    3. Attribute Directives: Change the appearance or behavior of elements (e.g., ngClass, ngStyle).

Component Directives

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: './example.component.css',
  standalone: true,
  imports: [ComponentA, FormsModule]
})
export class ExampleComponent {}
  • These directives are a special kind of directive with their own templates and logic.
  • Components are essentially directives with templates.
  • Example: @Component decorated classes.

Structural Directives

<div *ngIf="isVisible">Content is visible</div>
  • Structural directives alter the layout or structure of the DOM by adding or removing elements.
  • They typically start with *.
<ul>
  <li *ngFor="let item of items">{{ item }}</li>
</ul>

Attribute Directives

import { Directive, input, effect, inject, ElementRef } from '@angular/core';

@Directive({
  selector: '[appHighlighted]',
  standalone: true,
})
export class HighlightedDirective {
  isHighlighted = input(false);
  el = inject(ElementRef);
  stylesEffect = effect(() => {
    if (this.isHighlighted()) {
      this.el.nativeElement.style.backgroundColor = '#d3f9d8';
      this.el.nativeElement.style.color = '#6c757d';
    } else {
      this.el.nativeElement.style.backgroundColor = '#fff';
      this.el.nativeElement.style.color = '#000';
    }
  });
}
  • Structural directives alter the layout or structure of the DOM by adding or removing elements.
  • They typically start with *.

New Control Flow Directives with Signals in Angular 17+

  • Angular 17 introduces signal-based structural directives:
    • @if: Conditionally renders content based on the signal’s value.
    • @for: Iterates over a collection of signals.
  • These directives enable more powerful and reactive template management using signals.

@if with Signals

@if(signal)
  <p>Content to display when signal is true</p>
  • @if is a signal-aware directive that conditionally renders content when a signal’s value changes.
  • This is similar to *ngIf but works specifically with Angular's signal system.
@if (isLoading()) {
	<p>Loading...</p>
}

syntax:

html:

@for with Signals

@for(item of signal; track item.index)
  <p>{{ item }}</p>
  • The @for directive iterates over a signal that returns an iterable collection (like an array or list).
  • It allows you to directly bind to a signal that updates reactively.
@for(item of todoItems(); track item.id) {
  <li>{{ item }}</li>
}

syntax:

html:

@if @else if, @else

@if(condition)
  <p>Content to display when condition is true</p>
@else if(condition2)
  <p>Content to display when condition2 is true</p>
@else 
  <p>Content to display when no condition is true</p>
@if (isLoading()) {
  <p>Loading...</p>
} else {
  <ul>
    @for (let item of todoItems()) {
      <li>{{ item }}</li>
    }
  </ul>
}

syntax:

html:

the output Function in Angular 17+

  • output is a new function in Angular 17+ that replaces the traditional @Output decorator.
  • It is used for emitting events or signals from a component.
  • Unlike @Output, the output function allows you to directly define signal-based outputs for event handling.

Creating an Event Emitter with output

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

@Component({
  selector: 'app-child',
  template: `<button (click)="sendData()">Send Data</button>`,
})
export class ChildComponent {
  // Creating an output signal using the output function
  sendDataSignal = output<string>();

  sendData() {
    // Emitting the data signal when the button is clicked
    this.sendDataSignal.emit('Hello from Child!');
  }
}

child component:

Listening for Events in the Parent Component

@Component({
  selector: 'app-parent',
  template: `<app-child (sendDataSignal)="handleData($event)"></app-child>`,
})
export class ParentComponent {
  handleData(data: string) {
    console.log('Received from child:', data);
  }
}

parent component:

  • Declarative and Simpler: The output function offers a more declarative and streamlined approach for emitting events.
  • No Need for Decorators: You no longer need to use the @Output decorator.
  • Signal-based Communication: Fully integrated with Angular’s new signal-based reactivity system.
  • Enhanced Performance: Provides a more lightweight and performant solution compared to traditional EventEmitter.

Advantages of Using output Function

.Forms

  • Forms in Angular are managed using Reactive Forms and Template-driven Forms, both of which require certain modules to function properly.

Angular provides two types of form handling mechanisms:

  • Template-driven Forms: Managed using FormsModule.
  • Reactive Forms: Managed using ReactiveFormsModule.
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';

Standalone Component with Reactive Forms

import { Component, OnInit } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-my-form',
  standalone: true,
  imports: [ReactiveFormsModule],  // Importing ReactiveFormsModule directly
  templateUrl: './my-form.component.html',
})
export class MyFormComponent implements OnInit {
  myForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
    });
  }

  onSubmit() {
    if (this.myForm.valid) {
      console.log(this.myForm.value);
    }
  }
}

Standalone Component with Reactive Forms

<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
  <label for="name">Name:</label>
  <input id="name" formControlName="name" type="text" />

  <label for="email">Email:</label>
  <input id="email" formControlName="email" type="email" />

  <button type="submit" [disabled]="!myForm.valid">Submit</button>
</form>

Pipes in Angular

  • Pipes in Angular are used for transforming data in templates.
  • A pipe takes in data and transforms it to another format for display.
  • They are typically used in the HTML template to format, filter, or manipulate the data being displayed.
ng g pipe name

command:

{{ value | pipeName:parameter }}

syntax:

Built-in Pipes in Angular

{{ today | date:'short' }}

Date Pipe: Formats date values.

{{ amount | currency:'USD' }}

Currency Pipe: Transforms numbers into currency format

{{ 'hello' | uppercase }}

Uppercase and Lowercase Pipes: Transforms text to uppercase or lowercase.

{{ user | json }}

JSON Pipe: Converts objects to JSON format.

Built-in Pipes in Angular

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

@Pipe({
  name: 'reverseString'
})
export class ReverseStringPipe implements PipeTransform {
  transform(value: string): string {
    return value.split('').reverse().join('');
  }
}
ng g pipe reverseString
{{ 'hello' | reverseString }}

Pure vs Impure Pipes

  • Pure Pipes

    • Pure Pipes are only re-evaluated when the input data changes.
    • They are optimized and efficient because Angular caches their result unless the input changes.
@Pipe({
  name: 'reverseString',
  pure: true
})
  • Impure Pipes

    • Impure Pipes are evaluated on every change detection cycle, regardless of whether the input changes.
    • They are useful when you need to check for changes in more complex data structures like arrays or objects.
@Pipe({
  name: 'reverseString',
  pure: false
})

Async Pipe

  • The async pipe is a built-in pipe that subscribes to an observable or promise and returns the latest value.
  • It automatically handles subscriptions and unsubscriptions.
<div>{{ observableData | async }}</div>
  • It displays the latest value emitted by the observable observableData.

State optimization with 'Computed'

  • The computed() function is a part of Angular's new Signals API introduced in Angular 16.
  • It allows you to define derived or computed state that automatically updates when its dependencies (signals) change.
  • Reactive Dependencies: Automatically reacts to changes in the state.
  • Derived State: Simplifies logic by creating a state that depends on other signals.
  • Efficient Updates: Only recomputes when its dependencies change.

Why and when?

  • Eliminates the need for manual state management.
  • Keeps the application logic concise and reactive.
  • Improves performance by recalculating only when needed.

State optimization with 'Computed'

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

@Component({
  selector: 'app-counter',
})
export class CounterComponent {
  // Define a signal for the counter
  count = signal(0);

  // Create a computed signal for derived state
  doubleCount = computed(() => this.count() * 2);

  // Another computed signal to check if the count is even
  isEven = computed(() => this.count() % 2 === 0 ? 'Yes' : 'No');

  // Increment the counter
  increment() {
    this.count.set(this.count() + 1);
  }
}

Let's practice

THANK YOU

Contact me via email or Discord if you have any questions.

Angular training - Migration

By Youcef Madadi

Angular training - Migration

  • 208