Youcef Madadi
Web and game development teacher
an IT enthusiast who is intrested in the web dev industry and its technology
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.
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.
Feature | Angular | React | Vue |
---|---|---|---|
Language | TypeScript | JavaScript | JavaScript |
Learning Curve | Moderate | Steep | Easy |
Core Strength | Full framework | UI library | Versatile |
JavaScript Basics:
TypeScript Basics:
HTML & CSS:
Node.js and npm:
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.
RxJS
Observables for state updates.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.
Angular 18:
Advanced preloading strategies for CSR optimization.
Support for Edge Rendering.
Cleaner syntax than ngIf
/ngFor
.
@for
and @if
)<!-- Old Way -->
<div *ngIf="condition"></div>
<!-- New Way -->
@if (condition) {
<div></div>
}
Improved performance with fine-grained control over module preloading.
const routes: Routes = [
{
path: 'customers',
loadChildren: () =>
import('./customers/customers.module')
.then((m) => m.CustomersModule),
},
];
@Component({
selector: 'app-example',
template: '<p>Hello, world!</p>'
})
export class ExampleComponent {}
@Component
, @Directive
, @Injectable
, @Pipe
.@Input
, @Output
.@HostListener
.@Inject
.@Component
Key Metadata Properties
selector
:
<app-example></app-example>
.template
/ templateUrl
:
styles
/ styleUrls
:
standalone
:
@NgModule
.imports
(if standalone):
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: './example.component.css',
standalone: true,
imports: [ComponentA, FormsModule]
})
export class ExampleComponent {}
export class CounterComponent {
count: number = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
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);
}
}
@Input
and input<T>()
<app-child [prop]="valueFromParent"></app-child>
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `<p>Received: {{ prop }}</p>`
})
export class ChildComponent {
@Input() prop!: string;
}
@Input
and input<T>()
<app-child [prop]="valueFromParent"></app-child>
import { Component, Input, input } from '@angular/core';
@Component({
selector: 'app-child',
template: `<p>Received: {{ prop() }}</p>`
})
export class ChildComponent {
prop = input<string>();
}
Feature | Use Case |
---|---|
@Input |
Simple, non-reactive data binding. |
input<T>() |
For reactive, signal-based workflows with real-time updates. |
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
}
}
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}`);
}
}
}
import { provideRouter } from '@angular/router';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
import { Routes } from '@angular/router';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: '**', component: NotFoundComponent }, // Wildcard route
];
@NgModule
for routing.<nav>
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>
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
];
@Injectable
@Injectable
marks a class as a service or provider that can be injected into other classes.@Injectable
Works:@Injectable
is a decorator used in Angular to mark a class as available for dependency injection (DI).@Injectable({
// Makes the service available app-wide
providedIn: 'root'
})
export class DataService {
constructor() {
console.log('DataService created');
}
getData() {
return 'Data from DataService';
}
}
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);
}
}
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());
}
}
export class DataService {
private data: string[] = [];
http= inject(HttpClient)
getFromApi( ){
this.http.get('https://url')
}
addData(item: string) {
this.data.push(item);
}
}
providers:[
provideHttpClient, // it has to be added to the configuration
...
]
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);
})
}
}
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;
}
}
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
*ngIf
, *ngFor
).ngClass
, ngStyle
).@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: './example.component.css',
standalone: true,
imports: [ComponentA, FormsModule]
})
export class ExampleComponent {}
@Component
decorated classes.<div *ngIf="isVisible">Content is visible</div>
*
.<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
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';
}
});
}
*
.@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.*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>
@for
directive iterates over a signal that returns an iterable collection (like an array or list).@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:
output
Function in Angular 17+output
is a new function in Angular 17+ that replaces the traditional @Output
decorator.@Output
, the output
function allows you to directly define signal-based outputs for event handling.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:
@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:
output
function offers a more declarative and streamlined approach for emitting events.@Output
decorator.EventEmitter
.output
FunctionAngular provides two types of form handling mechanisms:
FormsModule
.ReactiveFormsModule
.import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/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);
}
}
}
<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>
ng g pipe name
command:
{{ value | pipeName:parameter }}
syntax:
{{ 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.
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 }}
@Pipe({
name: 'reverseString',
pure: true
})
@Pipe({
name: 'reverseString',
pure: false
})
async
pipe is a built-in pipe that subscribes to an observable or promise and returns the latest value.<div>{{ observableData | async }}</div>
observableData
.computed()
function is a part of Angular's new Signals API introduced in Angular 16.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
By Youcef Madadi