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);
}
}