Skip to main content
โšก Calmops

Angular: Framework Basics

Angular: Framework Basics

Angular is a comprehensive framework for building enterprise applications. This article covers Angular fundamentals.

Introduction

Angular provides:

  • Component-based architecture
  • Dependency injection
  • TypeScript support
  • RxJS integration
  • Powerful CLI tools

Understanding Angular helps you:

  • Build enterprise applications
  • Create reusable components
  • Manage application state
  • Handle asynchronous operations
  • Scale large applications

Angular Setup

Installation and Project Setup

# โœ… Good: Install Angular CLI
npm install -g @angular/cli

# โœ… Good: Create new Angular project
ng new my-app
cd my-app
ng serve

# โœ… Good: Generate components
ng generate component components/user-card
ng g c components/user-list

# โœ… Good: Generate services
ng generate service services/user
ng g s services/auth

Project Structure

my-app/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ app/
โ”‚   โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ services/
โ”‚   โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ”œโ”€โ”€ app.component.ts
โ”‚   โ”‚   โ”œโ”€โ”€ app.component.html
โ”‚   โ”‚   โ”œโ”€โ”€ app.component.css
โ”‚   โ”‚   โ””โ”€โ”€ app.module.ts
โ”‚   โ”œโ”€โ”€ assets/
โ”‚   โ”œโ”€โ”€ main.ts
โ”‚   โ””โ”€โ”€ index.html
โ”œโ”€โ”€ angular.json
โ”œโ”€โ”€ tsconfig.json
โ””โ”€โ”€ package.json

Angular Components

Basic Component

// user-card.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core'

@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.css']
})
export class UserCardComponent {
  @Input() user: any
  @Output() edit = new EventEmitter<number>()

  onEdit() {
    this.edit.emit(this.user.id)
  }
}

// user-card.component.html
<div class="user-card">
  <h3>{{ user.name }}</h3>
  <p>Email: {{ user.email }}</p>
  <button (click)="onEdit()">Edit</button>
</div>

// user-card.component.css
.user-card {
  border: 1px solid #ccc;
  padding: 1rem;
  border-radius: 4px;
}

Component Lifecycle

// โœ… Good: Component with lifecycle hooks
import {
  Component,
  OnInit,
  OnDestroy,
  OnChanges,
  SimpleChanges
} from '@angular/core'

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html'
})
export class UserProfileComponent implements OnInit, OnDestroy, OnChanges {
  user: any

  ngOnInit() {
    console.log('Component initialized')
    this.loadUser()
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log('Input properties changed', changes)
  }

  ngOnDestroy() {
    console.log('Component destroyed')
  }

  loadUser() {
    // Load user data
  }
}

Two-Way Binding

// form.component.ts
import { Component } from '@angular/core'

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html'
})
export class FormComponent {
  formData = {
    email: '',
    password: '',
    rememberMe: false
  }

  onSubmit() {
    console.log('Form submitted:', this.formData)
  }
}

// form.component.html
<form (ngSubmit)="onSubmit()">
  <input
    [(ngModel)]="formData.email"
    name="email"
    type="email"
    placeholder="Email"
  />
  <input
    [(ngModel)]="formData.password"
    name="password"
    type="password"
    placeholder="Password"
  />
  <label>
    <input
      [(ngModel)]="formData.rememberMe"
      name="rememberMe"
      type="checkbox"
    />
    Remember me
  </label>
  <button type="submit">Login</button>
</form>

Angular Services

Basic Service

// user.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = '/api/users'

  constructor(private http: HttpClient) {}

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

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

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

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

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

// user-list.component.ts
import { Component, OnInit } from '@angular/core'
import { UserService } from '../services/user.service'

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html'
})
export class UserListComponent implements OnInit {
  users: any[] = []
  loading = true
  error: string | null = null

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.loadUsers()
  }

  loadUsers() {
    this.userService.getUsers().subscribe({
      next: (data) => {
        this.users = data
        this.loading = false
      },
      error: (err) => {
        this.error = err.message
        this.loading = false
      }
    })
  }
}

Dependency Injection

// โœ… Good: Dependency injection
import { Injectable } from '@angular/core'

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  constructor(private http: HttpClient) {}

  login(email: string, password: string) {
    return this.http.post('/api/login', { email, password })
  }
}

// Component using injected service
import { Component } from '@angular/core'

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})
export class LoginComponent {
  constructor(private authService: AuthService) {}

  onLogin(email: string, password: string) {
    this.authService.login(email, password).subscribe({
      next: (response) => {
        console.log('Login successful')
      },
      error: (error) => {
        console.error('Login failed')
      }
    })
  }
}

Angular Routing

Basic Routing

// app-routing.module.ts
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { HomeComponent } from './pages/home.component'
import { AboutComponent } from './pages/about.component'
import { UserProfileComponent } from './pages/user-profile.component'

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'users/:id', component: UserProfileComponent },
  { path: '**', redirectTo: '' }
]

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

// app.component.html
<nav>
  <a routerLink="/">Home</a>
  <a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>

Route Parameters

// user-profile.component.ts
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { UserService } from '../services/user.service'

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html'
})
export class UserProfileComponent implements OnInit {
  user: any

  constructor(
    private route: ActivatedRoute,
    private userService: UserService
  ) {}

  ngOnInit() {
    this.route.params.subscribe((params) => {
      const userId = params['id']
      this.userService.getUser(userId).subscribe((data) => {
        this.user = data
      })
    })
  }
}

RxJS and Observables

Working with Observables

// โœ… Good: Observable subscription
import { Component, OnInit, OnDestroy } from '@angular/core'
import { Subject } from 'rxjs'
import { takeUntil } from 'rxjs/operators'

@Component({
  selector: 'app-data-list',
  templateUrl: './data-list.component.html'
})
export class DataListComponent implements OnInit, OnDestroy {
  data: any[] = []
  private destroy$ = new Subject<void>()

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.getData()
      .pipe(takeUntil(this.destroy$))
      .subscribe((data) => {
        this.data = data
      })
  }

  ngOnDestroy() {
    this.destroy$.next()
    this.destroy$.complete()
  }
}

// โœ… Good: Async pipe
// component.ts
export class UserListComponent {
  users$ = this.userService.getUsers()

  constructor(private userService: UserService) {}
}

// component.html
<div *ngFor="let user of users$ | async">
  {{ user.name }}
</div>

// โœ… Good: Combining observables
import { combineLatest } from 'rxjs'

export class DashboardComponent implements OnInit {
  constructor(
    private userService: UserService,
    private postService: PostService
  ) {}

  ngOnInit() {
    combineLatest([
      this.userService.getUsers(),
      this.postService.getPosts()
    ]).subscribe(([users, posts]) => {
      console.log('Users:', users)
      console.log('Posts:', posts)
    })
  }
}

Angular Forms

Reactive Forms

// form.component.ts
import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup, Validators } from '@angular/forms'

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html'
})
export class FormComponent implements OnInit {
  form: FormGroup

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(8)]],
      rememberMe: [false]
    })
  }

  ngOnInit() {}

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

// form.component.html
<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <input
    formControlName="email"
    type="email"
    placeholder="Email"
  />
  <div *ngIf="form.get('email')?.hasError('required')">
    Email is required
  </div>

  <input
    formControlName="password"
    type="password"
    placeholder="Password"
  />
  <div *ngIf="form.get('password')?.hasError('minlength')">
    Password must be at least 8 characters
  </div>

  <label>
    <input formControlName="rememberMe" type="checkbox" />
    Remember me
  </label>

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

Best Practices

  1. Use services for data:

    // โœ… Good: Service for data
    constructor(private userService: UserService) {}
    
    // โŒ Bad: Direct HTTP calls
    constructor(private http: HttpClient) {}
    
  2. Use async pipe:

    <!-- โœ… Good: Async pipe -->
    <div *ngFor="let user of users$ | async">
      {{ user.name }}
    </div>
    
    <!-- โŒ Bad: Manual subscription -->
    <div *ngFor="let user of users">
      {{ user.name }}
    </div>
    
  3. Unsubscribe from observables:

    // โœ… Good: Unsubscribe
    private destroy$ = new Subject<void>()
    
    ngOnInit() {
      this.service.data$
        .pipe(takeUntil(this.destroy$))
        .subscribe(...)
    }
    
    ngOnDestroy() {
      this.destroy$.next()
    }
    

Summary

Angular is powerful. Key takeaways:

  • Use components for UI
  • Use services for data
  • Use dependency injection
  • Implement routing
  • Use RxJS observables
  • Use reactive forms
  • Follow Angular best practices
  • Build scalable applications

Next Steps

Comments