TECHNICAL PRESENTATION

Introduction to
Angular

The Full-Featured TypeScript Framework
TypeScript · components · signals · DI · RxJS · CLI
02

Agenda

Foundations

  • What Is Angular?
  • Architecture Overview
  • The Angular CLI
  • Components & Templates
  • Data Binding

Core Concepts

  • Directives
  • Signals & Reactive State
  • Services & Dependency Injection
  • RxJS & Observables
  • Routing & Navigation

Practical Patterns

  • Forms (Template & Reactive)
  • HTTP Client
  • Pipes
  • Component Communication
  • Standalone Components

Production

  • Testing Strategies
  • Performance & Build
  • Summary & Next Steps

20 slides · Real code examples throughout · Angular v17+ focus

03

What Is Angular?

Angular is a full-featured, opinionated TypeScript framework maintained by Google for building single-page applications and progressive web apps.

YearMilestone
2010AngularJS (v1.x) released by Google
2016Angular 2 — complete rewrite in TypeScript
2017-2022Angular 4-14 — Ivy renderer, strict mode
2023Angular 16-17 — signals, new control flow, esbuild
2024Angular 18-19 — zoneless, stable signals

Key Properties

  • TypeScript-first — types built in
  • Batteries included — router, forms, HTTP, testing
  • Dependency Injection — hierarchical IoC
  • Reactive — RxJS + Signals
  • CLI-driven — scaffolding & build in one tool

Angular is not AngularJS. The modern framework (v2+) is a complete rewrite with a fundamentally different architecture.

04

Angular Architecture

Modules Components Services Directives Pipes Dependency Injection · Change Detection · Renderer · Zone.js / Zoneless Angular Runtime Platform

Modules (NgModule)

Organise related code into cohesive blocks. Standalone components are replacing NgModules in modern Angular.

Components

Building blocks of the UI. Each has a TypeScript class, an HTML template, and optional CSS styles.

Services & DI

Encapsulate business logic and data access. Injected via Angular's hierarchical injector system.

05

The Angular CLI

The @angular/cli is the official tool for creating, developing, scaffolding, and building Angular projects.

# Install the CLI globally
npm install -g @angular/cli

# Create a new project
ng new my-app --style=scss --routing

# Serve with live reload
ng serve --open

# Generate artifacts
ng generate component features/dashboard
ng generate service core/auth
ng generate pipe shared/truncate
ng generate guard auth/role
CommandPurpose
ng newScaffold a full project
ng serveDev server with HMR
ng generateCreate components, services, etc.
ng buildProduction build (AOT + tree-shake)
ng testRun unit tests (Karma/Jest)
ng lintLint with ESLint
ng updateUpdate Angular + run migrations
ng addAdd libraries with schematics
06

Components & Templates

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

@Component({
  selector: 'app-hero-card',
  standalone: true,
  template: `
    <div class="hero-card">
      <h2>{{ hero.name }}</h2>
      <p>Power: {{ hero.power }}</p>
      <button (click)="onSelect()">
        Select
      </button>
    </div>
  `,
  styles: [`
    .hero-card {
      border: 1px solid #ccc;
      border-radius: 8px;
      padding: 1rem;
    }
  `]
})
export class HeroCardComponent {
  hero = { name: 'Windstorm', power: 'Weather' };

  onSelect() {
    console.log(`Selected: ${this.hero.name}`);
  }
}

@Component Decorator

  • selector — custom HTML tag name
  • template / templateUrl — inline or external HTML
  • styles / styleUrls — scoped CSS
  • standalone — no NgModule needed

Template Syntax

  • {{ expr }} — interpolation
  • [prop]="expr" — property binding
  • (event)="handler()" — event binding
  • [(ngModel)]="val" — two-way binding

Lifecycle Hooks

  • ngOnInit — after first data binding
  • ngOnChanges — when inputs change
  • ngOnDestroy — cleanup subscriptions
07

Data Binding

Angular provides four forms of data binding that connect the component class to the template.

Interpolation (one-way, class → view)

<h1>Welcome, {{ user.name }}!</h1>
<p>Total: {{ getTotal() | currency }}</p>

Property Binding (one-way, class → view)

<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isLoading">Submit</button>
<div [class.active]="isActive"></div>

Event Binding (one-way, view → class)

<button (click)="save()">Save</button>
<input (keyup.enter)="search(term)">
<div (mouseover)="highlight($event)"></div>

Two-Way Binding (class ↔ view)

<!-- Requires FormsModule -->
<input [(ngModel)]="username">

<!-- Desugared equivalent -->
<input [ngModel]="username"
       (ngModelChange)="username = $event">
SyntaxDirectionExample
{{ }}Component → DOMText interpolation
[ ]Component → DOMProperty / attribute / class / style
( )DOM → ComponentUser events (click, keyup, etc.)
[( )]BothForm inputs with ngModel
08

Directives

Legacy Structural Directives

<!-- *ngIf -->
<div *ngIf="user; else noUser">
  Hello, {{ user.name }}
</div>
<ng-template #noUser>
  <p>Please log in</p>
</ng-template>

<!-- *ngFor -->
<li *ngFor="let item of items;
     trackBy: trackById; let i = index">
  {{ i + 1 }}. {{ item.name }}
</li>

<!-- *ngSwitch -->
<div [ngSwitch]="role">
  <p *ngSwitchCase="'admin'">Admin</p>
  <p *ngSwitchDefault>User</p>
</div>

New Built-in Control Flow (v17+)

<!-- @if / @else -->
@if (user) {
  <p>Hello, {{ user.name }}</p>
} @else {
  <p>Please log in</p>
}

<!-- @for with required track -->
@for (item of items; track item.id) {
  <li>{{ item.name }}</li>
} @empty {
  <li>No items found</li>
}

<!-- @switch -->
@switch (role) {
  @case ('admin') { <p>Admin</p> }
  @default { <p>User</p> }
}

Attribute Directives

  • ngClass — conditional CSS classes
  • ngStyle — conditional inline styles
  • Custom: @Directive({ selector: '[appHighlight]' })
09

Signals & Reactive State

Angular Signals (stable in v17+) provide fine-grained, synchronous reactivity without Zone.js.

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

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <p>Count: {{ count() }}</p>
    <p>Double: {{ double() }}</p>
    <button (click)="increment()">+1</button>
  `
})
export class CounterComponent {
  // Writable signal
  count = signal(0);

  // Computed (read-only, auto-tracked)
  double = computed(() => this.count() * 2);

  constructor() {
    // Side-effect that re-runs on change
    effect(() => {
      console.log('Count changed:', this.count());
    });
  }

  increment() {
    this.count.update(c => c + 1);
  }
}

signal()

Creates a writable reactive value. Read by calling it as a function. Mutate with .set(), .update(), or .mutate().

computed()

Derives a read-only signal from other signals. Lazily evaluated and memoized — only recalculates when dependencies change.

effect()

Runs a side-effect whenever tracked signals change. Useful for logging, localStorage sync, or analytics.

Signals vs RxJS

  • Signals: synchronous, simple state
  • RxJS: async streams, complex transformations
  • toSignal() / toObservable() for interop
10

Services & Dependency Injection

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class HeroService {
  private http = inject(HttpClient);
  private apiUrl = '/api/heroes';

  getAll() {
    return this.http.get<Hero[]>(this.apiUrl);
  }

  getById(id: number) {
    return this.http.get<Hero>(
      `${this.apiUrl}/${id}`
    );
  }

  create(hero: Partial<Hero>) {
    return this.http.post<Hero>(
      this.apiUrl, hero
    );
  }
}

// Usage in a component
@Component({ /* ... */ })
export class HeroListComponent {
  private heroService = inject(HeroService);
  heroes = signal<Hero[]>([]);

  ngOnInit() {
    this.heroService.getAll().subscribe(
      data => this.heroes.set(data)
    );
  }
}

@Injectable

providedIn: 'root' registers a singleton at the root injector. Tree-shakeable — only included if actually injected.

inject() function

Modern alternative to constructor injection. Can be used in components, directives, pipes, and services.

Hierarchical Injectors

  • Root — app-wide singletons
  • Module — scoped to a lazy module
  • Component — new instance per component
  • Element — NodeInjector on the DOM tree

InjectionToken

export const API_URL =
  new InjectionToken<string>('API_URL');

// Provide it
{ provide: API_URL, useValue: '/api' }

// Inject it
url = inject(API_URL);
11

RxJS & Observables

import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { Subject, switchMap, debounceTime,
         distinctUntilChanged, catchError,
         of } from 'rxjs';
import { HeroService } from './hero.service';

@Component({
  selector: 'app-search',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    <input (input)="onSearch($event)">
    @for (hero of results$ | async;
          track hero.id) {
      <p>{{ hero.name }}</p>
    }
  `
})
export class SearchComponent {
  private search$ = new Subject<string>();
  private heroService = inject(HeroService);

  results$ = this.search$.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(term =>
      this.heroService.search(term).pipe(
        catchError(() => of([]))
      )
    )
  );

  onSearch(e: Event) {
    const val = (e.target as HTMLInputElement).value;
    this.search$.next(val);
  }
}

Core Types

  • Observable — lazy stream of values
  • Subject — multicast, push values manually
  • BehaviorSubject — holds current value
  • ReplaySubject — replays N last values

Key Operators

OperatorPurpose
mapTransform emitted values
filterEmit only matching values
switchMapCancel previous inner observable
mergeMapRun inner observables concurrently
combineLatestCombine latest from multiple streams
takeUntilDestroyedAuto-unsubscribe on destroy

AsyncPipe

Subscribes in the template and auto-unsubscribes. Preferred over manual .subscribe() for cleaner code.

12

Routing & Navigation

// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  {
    path: 'heroes',
    loadComponent: () =>
      import('./heroes/list.component')
        .then(m => m.HeroListComponent),
  },
  {
    path: 'heroes/:id',
    loadComponent: () =>
      import('./heroes/detail.component')
        .then(m => m.HeroDetailComponent),
    resolve: { hero: heroResolver },
    canActivate: [authGuard],
  },
  {
    path: 'admin',
    loadChildren: () =>
      import('./admin/admin.routes')
        .then(m => m.ADMIN_ROUTES),
    canMatch: [adminGuard],
  },
  { path: '**', component: NotFoundComponent },
];

Lazy Loading

loadComponent and loadChildren split code into separate chunks — loaded on demand for faster initial load.

Functional Guards (v15+)

export const authGuard: CanActivateFn =
  (route, state) => {
    const auth = inject(AuthService);
    return auth.isLoggedIn()
      ? true
      : inject(Router)
          .createUrlTree(['/login']);
  };

Resolvers

export const heroResolver: ResolveFn<Hero> =
  (route) => {
    const id = +route.paramMap.get('id')!;
    return inject(HeroService).getById(id);
  };
13

Forms

Angular offers two approaches: Template-driven (simple, directive-based) and Reactive (explicit, code-based).

Template-Driven

<form #f="ngForm" (ngSubmit)="save(f)">
  <input name="name"
         ngModel required
         minlength="3"
         #name="ngModel">
  @if (name.invalid && name.touched) {
    <span class="error">
      Name is required (min 3 chars)
    </span>
  }
  <button [disabled]="f.invalid">
    Save
  </button>
</form>

Uses FormsModule. Good for simple forms with minimal validation.

Reactive Forms

@Component({ /* ... */ })
export class ProfileFormComponent {
  private fb = inject(FormBuilder);

  form = this.fb.group({
    name: ['', [
      Validators.required,
      Validators.minLength(3)
    ]],
    email: ['', [
      Validators.required,
      Validators.email
    ]],
    address: this.fb.group({
      street: [''],
      city: [''],
      zip: ['', Validators.pattern(/\d{5}/)]
    })
  });

  save() {
    if (this.form.valid) {
      console.log(this.form.getRawValue());
    }
  }
}

Uses ReactiveFormsModule. Typed, testable, dynamic.

14

HTTP Client

// app.config.ts — provide HttpClient
import { provideHttpClient, withInterceptors }
  from '@angular/common/http';

export const appConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor])
    ),
  ]
};

// auth.interceptor.ts
export const authInterceptor: HttpInterceptorFn =
  (req, next) => {
    const token = inject(AuthService).getToken();
    if (token) {
      req = req.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }
    return next(req).pipe(
      catchError(err => {
        if (err.status === 401) {
          inject(Router).navigate(['/login']);
        }
        return throwError(() => err);
      })
    );
  };

provideHttpClient()

Standalone replacement for HttpClientModule. Supports functional interceptors and fetch backend.

Typed Responses

interface Hero {
  id: number;
  name: string;
  power: string;
}

this.http.get<Hero[]>('/api/heroes')
  .subscribe(heroes => {
    // heroes is Hero[]
  });

Error Handling

  • catchError — handle per-request errors
  • retry(3) — automatic retries
  • Interceptors — global error handling
  • HttpErrorResponse — typed error object
15

Pipes

Pipes transform displayed values in templates. Angular ships with many built-in pipes and supports custom pipes.

Built-in Pipes

PipeExample Output
date{{ d | date:'mediumDate' }} → Apr 6, 2026
currency{{ 42.5 | currency:'EUR' }} → €42.50
uppercase{{ 'hello' | uppercase }} → HELLO
jsonDebug-prints object as JSON
asyncSubscribes to Observable/Promise
sliceSubset of an array or string
keyvalueIterates over object entries

Custom Pipe

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

@Pipe({
  name: 'truncate',
  standalone: true,
  pure: true  // default
})
export class TruncatePipe
  implements PipeTransform {

  transform(
    value: string,
    limit = 50,
    ellipsis = '...'
  ): string {
    if (!value) return '';
    return value.length > limit
      ? value.substring(0, limit) + ellipsis
      : value;
  }
}

// Usage in template
// {{ longText | truncate:30:'…' }}

Pure Pipes

Only re-evaluate when input reference changes. Default and performant.

Impure Pipes

Re-evaluate on every change detection cycle. Use sparingly — pure: false.

16

Component Communication

@Input / @Output

// child.component.ts
@Component({
  selector: 'app-child',
  template: `
    <p>{{ title }}</p>
    <button (click)="notify.emit('hi')">
      Notify Parent
    </button>
  `
})
export class ChildComponent {
  @Input({ required: true }) title!: string;
  @Output() notify = new EventEmitter<string>();
}

// parent template
<app-child
  [title]="parentTitle"
  (notify)="onNotify($event)">
</app-child>

Signal Inputs (v17.1+)

@Component({
  selector: 'app-card',
  template: `
    <h2>{{ title() }}</h2>
    <p>{{ uppercaseTitle() }}</p>
  `
})
export class CardComponent {
  title = input.required<string>();
  subtitle = input('Default value');

  uppercaseTitle = computed(
    () => this.title().toUpperCase()
  );
}

ViewChild / ContentChild

@ViewChild('chart') chartRef!: ElementRef;
@ViewChild(ChildComponent) child!: ChildComponent;
@ContentChild(HeaderDirective) header!: HeaderDirective;

Content Projection

<!-- card.component.html -->
<div class="card">
  <ng-content select="[header]"></ng-content>
  <ng-content></ng-content>
  <ng-content select="[footer]"></ng-content>
</div>

<!-- Usage -->
<app-card>
  <h2 header>Title</h2>
  <p>Body content here</p>
  <button footer>Action</button>
</app-card>
17

Standalone Components

Since Angular 14+, components can be standalone — no NgModule required. This is now the recommended default in Angular 17+.

// standalone component
@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [
    CommonModule,
    RouterLink,
    HeroCardComponent,
    TruncatePipe
  ],
  template: `
    @for (hero of heroes(); track hero.id) {
      <app-hero-card [hero]="hero" />
    }
    <a routerLink="/settings">Settings</a>
  `
})
export class DashboardComponent {
  heroes = signal<Hero[]>([]);
}

// bootstrap without NgModule
// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([authInterceptor])
    ),
    provideAnimationsAsync(),
  ]
});

Why Standalone?

  • Simpler mental model — no NgModule boilerplate
  • Better tree-shaking — imports are explicit
  • Easier lazy loading — loadComponent
  • Faster compilation — fewer files to process

Migration from NgModules

  • CLI schematic: ng g @angular/core:standalone
  • Add standalone: true to components
  • Move declarations to imports array
  • Replace module providers with provide* functions

importProvidersFrom()

// Bridge for libraries still using NgModule
providers: [
  importProvidersFrom(
    SomeLibraryModule.forRoot()
  )
]
18

Testing

Unit Testing with TestBed

import { ComponentFixture, TestBed }
  from '@angular/core/testing';

describe('HeroCardComponent', () => {
  let component: HeroCardComponent;
  let fixture: ComponentFixture<
    HeroCardComponent
  >;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [HeroCardComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(
      HeroCardComponent
    );
    component = fixture.componentInstance;
  });

  it('should display hero name', () => {
    component.hero =
      { id: 1, name: 'Storm', power: 'Weather' };
    fixture.detectChanges();

    const el: HTMLElement =
      fixture.nativeElement;
    expect(el.querySelector('h2')?.textContent)
      .toContain('Storm');
  });

  it('should emit on select', () => {
    spyOn(component.notify, 'emit');
    const btn = fixture.nativeElement
      .querySelector('button');
    btn.click();
    expect(component.notify.emit)
      .toHaveBeenCalled();
  });
});

Service Testing

describe('HeroService', () => {
  let service: HeroService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting()
      ]
    });
    service = TestBed.inject(HeroService);
    httpMock = TestBed.inject(
      HttpTestingController
    );
  });

  it('should fetch heroes', () => {
    service.getAll().subscribe(heroes => {
      expect(heroes.length).toBe(2);
    });
    const req = httpMock.expectOne('/api/heroes');
    req.flush([{ id: 1 }, { id: 2 }]);
  });
});

Testing Stack

ToolRole
JasmineTest framework (default)
KarmaTest runner (legacy)
JestModern alternative (v16+)
Web Test RunnerOfficial replacement for Karma
Cypress / PlaywrightE2E testing
19

Performance & Build

AOT Compilation

  • Compiles templates at build time
  • Catches template errors early
  • Smaller bundle — no compiler shipped
  • Faster rendering — pre-compiled views
  • Default since Angular 9

Tree Shaking

  • Removes unused code from bundles
  • providedIn: 'root' enables tree-shaking for services
  • Standalone imports improve tree-shaking
  • Webpack & esbuild both support it

Lazy Routes

  • Split features into separate chunks
  • loadComponent / loadChildren
  • Preloading strategies available
  • Reduces initial load time significantly

esbuild + Vite (v17+)

// angular.json — new builder
{
  "builder": "@angular-devkit/build-angular:application",
  "options": {
    "outputMode": "static",
    "ssr": false
  }
}
  • Up to 87% faster builds than Webpack
  • HMR with Vite dev server
  • Default in Angular 17+ new projects

SSR with Angular Universal

# Add SSR to existing project
ng add @angular/ssr

# Generates:
# - server.ts (Express server)
# - app.config.server.ts
# - Build outputs browser + server bundles
  • Server-side rendering for SEO
  • Hydration transfers state to client
  • Incremental hydration (v18+)
  • Pre-rendering static routes at build time
20

Summary & Next Steps

What We Covered

  • Angular architecture & CLI
  • Components, templates, data binding
  • Directives & new control flow
  • Signals & reactive state
  • Services & dependency injection
  • RxJS & Observables

Also Covered

  • Routing & lazy loading
  • Template & Reactive forms
  • HTTP client & interceptors
  • Pipes (built-in & custom)
  • Component communication
  • Standalone components

Production

  • Testing with TestBed & Jest
  • AOT & tree shaking
  • esbuild + Vite
  • SSR with Angular Universal
  • Performance optimization

Next Steps & Resources

  • angular.dev — Official docs & tutorials
  • Angular Blog — Release announcements
  • Angular Material — Component library
  • NgRx — State management with Redux pattern
  • Build a CRUD app with routing & forms
  • Add authentication with guards
  • Implement state management (NgRx / signals)
  • Deploy with SSR for production

Thank you! — Built with Reveal.js · Single self-contained HTML file