TypeScript with Angular

Building enterprise applications with Angular and TypeScript

🅰️ What is TypeScript with Angular?

Angular is built with TypeScript from the ground up. TypeScript provides strong typing, decorators, and advanced features that power Angular's component architecture, dependency injection, and reactive programming, making enterprise application development robust and scalable.


// Angular component with TypeScript
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  template: `<h1>Hello, {{ name }}!</h1>`
})
export class HelloComponent {
  name: string = 'Angular';
}
                                    

Key Angular Features

🧩

Components

Building blocks of Angular apps

@Component({
  selector: 'app-user',
  templateUrl: './user.html'
})
💉

Dependency Injection

Inject services into components

constructor(
  private userService: UserService
) {}
🔄

RxJS Observables

Reactive programming with streams

this.data$ = this.http
  .get<User[]>('/api/users');
🎨

Decorators

Metadata for classes and properties

@Input() title: string;
@Output() clicked = new EventEmitter();

🔹 Setting Up Angular

Create a new Angular project:

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

# Create new project
ng new my-angular-app

# Navigate to project
cd my-angular-app

# Start development server
ng serve

# Open browser at http://localhost:4200

🔹 Creating Components

Generate and define Angular components:

# Generate a new component
ng generate component user-profile

# Or use shorthand
ng g c user-profile

🔸 user-profile.component.ts

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

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit {
  user: User = {
    id: 1,
    name: 'John Doe',
    email: '[email protected]'
  };

  isEditing: boolean = false;

  ngOnInit(): void {
    console.log('Component initialized');
  }

  editProfile(): void {
    this.isEditing = true;
  }

  saveProfile(): void {
    this.isEditing = false;
    console.log('Profile saved:', this.user);
  }
}

🔸 user-profile.component.html

<div class="user-profile">
  <h2>{{ user.name }}</h2>
  <p>Email: {{ user.email }}</p>
  
  <button *ngIf="!isEditing" (click)="editProfile()">
    Edit Profile
  </button>
  
  <button *ngIf="isEditing" (click)="saveProfile()">
    Save Profile
  </button>
</div>

🔹 Component Input and Output

Pass data between components:

🔸 Child Component (card.component.ts)

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

@Component({
  selector: 'app-card',
  template: `
    <div class="card">
      <h3>{{ title }}</h3>
      <p>{{ content }}</p>
      <button (click)="handleClick()">Click Me</button>
    </div>
  `
})
export class CardComponent {
  @Input() title: string = '';
  @Input() content: string = '';
  @Output() cardClicked = new EventEmitter<string>();

  handleClick(): void {
    this.cardClicked.emit(this.title);
  }
}

🔸 Parent Component

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

@Component({
  selector: 'app-parent',
  template: `
    <app-card
      [title]="cardTitle"
      [content]="cardContent"
      (cardClicked)="onCardClick($event)"
    ></app-card>
  `
})
export class ParentComponent {
  cardTitle: string = 'My Card';
  cardContent: string = 'This is the card content';

  onCardClick(title: string): void {
    console.log('Card clicked:', title);
  }
}

🔹 Services and Dependency Injection

Create reusable services:

# Generate a service
ng generate service services/user

# Or use shorthand
ng g s services/user

🔸 user.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({
  providedIn: 'root' // Available throughout the app
})
export class UserService {
  private apiUrl = 'https://api.example.com/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl);
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`);
  }

  createUser(user: User): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }

  updateUser(id: number, user: User): Observable<User> {
    return this.http.put<User>(`${this.apiUrl}/${id}`, user);
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}

🔸 Using the Service

import { Component, OnInit } from '@angular/core';
import { UserService, User } from './services/user.service';

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngIf="loading">Loading...</div>
    <ul>
      <li *ngFor="let user of users">
        {{ user.name }} - {{ user.email }}
      </li>
    </ul>
  `
})
export class UserListComponent implements OnInit {
  users: User[] = [];
  loading: boolean = true;

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsers().subscribe({
      next: (data) => {
        this.users = data;
        this.loading = false;
      },
      error: (error) => {
        console.error('Error fetching users:', error);
        this.loading = false;
      }
    });
  }
}

🔹 Reactive Forms

Build type-safe forms with validation:

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

@Component({
  selector: 'app-login',
  template: `
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <div>
        <label>Email:</label>
        <input type="email" formControlName="email" />
        <div *ngIf="email?.invalid && email?.touched">
          <span *ngIf="email?.errors?.['required']">Email is required</span>
          <span *ngIf="email?.errors?.['email']">Invalid email</span>
        </div>
      </div>

      <div>
        <label>Password:</label>
        <input type="password" formControlName="password" />
        <div *ngIf="password?.invalid && password?.touched">
          <span *ngIf="password?.errors?.['required']">Password is required</span>
          <span *ngIf="password?.errors?.['minlength']">
            Password must be at least 6 characters
          </span>
        </div>
      </div>

      <button type="submit" [disabled]="loginForm.invalid">
        Login
      </button>
    </form>
  `
})
export class LoginComponent implements OnInit {
  loginForm!: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.loginForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]]
    });
  }

  get email() {
    return this.loginForm.get('email');
  }

  get password() {
    return this.loginForm.get('password');
  }

  onSubmit(): void {
    if (this.loginForm.valid) {
      console.log('Form submitted:', this.loginForm.value);
    }
  }
}

🔹 Routing

Set up navigation between pages:

🔸 app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { UserDetailComponent } from './user-detail/user-detail.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'user/:id', component: UserDetailComponent },
  { path: '**', redirectTo: '' } // Wildcard route
];

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

🔸 Navigation in Component

import { Component } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-navigation',
  template: `
    <nav>
      <a routerLink="/" routerLinkActive="active">Home</a>
      <a routerLink="/about" routerLinkActive="active">About</a>
      <button (click)="goToUser(1)">View User 1</button>
    </nav>
    <router-outlet></router-outlet>
  `
})
export class NavigationComponent {
  constructor(private router: Router) {}

  goToUser(id: number): void {
    this.router.navigate(['/user', id]);
  }
}

// Access route parameters
@Component({
  selector: 'app-user-detail',
  template: `<h2>User ID: {{ userId }}</h2>`
})
export class UserDetailComponent {
  userId: string | null = null;

  constructor(private route: ActivatedRoute) {
    this.userId = this.route.snapshot.paramMap.get('id');
  }
}

🔹 Pipes

Transform data in templates:

// Built-in pipes
<p>{{ name | uppercase }}</p>
<p>{{ price | currency:'USD' }}</p>
<p>{{ today | date:'fullDate' }}</p>

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

@Pipe({
  name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit: number = 50): string {
    if (value.length <= limit) {
      return value;
    }
    return value.substring(0, limit) + '...';
  }
}

// Usage
<p>{{ longText | truncate:100 }}</p>

🔹 Complete Example: Todo App

Full Angular TypeScript application:

// todo.model.ts
export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

// todo.component.ts
import { Component } from '@angular/core';
import { Todo } from './todo.model';

@Component({
  selector: 'app-todo',
  template: `
    <div class="todo-app">
      <h1>Todo List</h1>
      
      <div class="add-todo">
        <input
          [(ngModel)]="newTodoTitle"
          (keyup.enter)="addTodo()"
          placeholder="Add a new todo"
        />
        <button (click)="addTodo()">Add</button>
      </div>

      <ul>
        <li *ngFor="let todo of todos">
          <input
            type="checkbox"
            [(ngModel)]="todo.completed"
          />
          <span [class.completed]="todo.completed">
            {{ todo.title }}
          </span>
          <button (click)="deleteTodo(todo.id)">Delete</button>
        </li>
      </ul>

      <p>{{ remainingTodos }} of {{ todos.length }} remaining</p>
    </div>
  `,
  styles: [`
    .completed {
      text-decoration: line-through;
      color: gray;
    }
  `]
})
export class TodoComponent {
  todos: Todo[] = [];
  newTodoTitle: string = '';
  nextId: number = 1;

  get remainingTodos(): number {
    return this.todos.filter(t => !t.completed).length;
  }

  addTodo(): void {
    if (this.newTodoTitle.trim()) {
      this.todos.push({
        id: this.nextId++,
        title: this.newTodoTitle,
        completed: false
      });
      this.newTodoTitle = '';
    }
  }

  deleteTodo(id: number): void {
    this.todos = this.todos.filter(t => t.id !== id);
  }
}

🧠 Test Your Knowledge

What decorator is used to mark a class as an Angular component?