Lesson 18-Angular Project Architecture Design

Architecture Design

Introduction

Angular is a popular frontend framework for building dynamic web applications. Maintained by Google, it provides a comprehensive solution including components, templates, services, routing, and more.

Angular Overview

Angular is written in TypeScript and is a component-driven framework. It emphasizes unidirectional data flow and immutability, offering a modular, reusable component structure.

Project Setup and Basic Configuration

Installing Angular CLI:

npm install -g @angular/cli

Creating a Project:

ng new my-angular-app
cd my-angular-app

Project Structure:

  • src/: Source code directory
  • app/: Main application module and components
  • assets/: Static resources
  • environments/: Environment configurations
  • Basic Configuration: Configure project settings in angular.json, such as build and development server options.

Components, Templates, and Data Binding

Creating a Component:

ng generate component my-component

Component Code:

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

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.css']
})
export class MyComponent {
  title = 'Hello Angular!';
}

Template Binding:

<h1>{{ title }}</h1>
<input [(ngModel)]="title" />

Directives and Pipes

Built-in Directives:

  • *ngIf: Conditional rendering
  • *ngFor: Iterative rendering

Custom Directive:

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor(el: ElementRef, renderer: Renderer2) {
    renderer.setStyle(el.nativeElement, 'backgroundColor', 'yellow');
  }
}

Pipe:

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

@Pipe({
  name: 'capitalize'
})
export class CapitalizePipe implements PipeTransform {
  transform(value: string): string {
    return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
  }
}

Services and Dependency Injection

Creating a Service:

ng generate service my-service

Service Code:

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

@Injectable({
  providedIn: 'root'
})
export class MyService {
  getData() {
    return 'Service Data';
  }
}

Dependency Injection:

import { Component, OnInit } from '@angular/core';
import { MyService } from './my-service.service';

@Component({
  selector: 'app-my-component',
  template: '{{ data }}'
})
export class MyComponent implements OnInit {
  data: string;
  constructor(private myService: MyService) {}
  ngOnInit() {
    this.data = this.myService.getData();
  }
}

Routing and Navigation

Setting Up Routing:

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

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
<router-outlet></router-outlet>

Form Handling

Template-Driven Forms:

<form #form="ngForm" (ngSubmit)="onSubmit(form)">
  <input name="name" ngModel required />
  <button type="submit">Submit</button>
</form>

Reactive Forms:

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

@Component({
  selector: 'app-my-form',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <input formControlName="name" />
      <button type="submit">Submit</button>
    </form>
  `
})
export class MyFormComponent {
  form = new FormGroup({
    name: new FormControl('', Validators.required)
  });

  onSubmit() {
    console.log(this.form.value);
  }
}

HTTP Client

Setting Up HTTP Module:

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [HttpClientModule],
})
export class AppModule {}

HTTP Requests:

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

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

  getData() {
    return this.http.get('https://api.example.com/data');
  }
}

State Management with NgRx

Installing NgRx:

ng add @ngrx/store

Setting Up the Store:

import { Action, createReducer, on } from '@ngrx/store';
import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter Component] Increment');

const _counterReducer = createReducer(0,
  on(increment, state => state + 1)
);

export function counterReducer(state: number | undefined, action: Action) {
  return _counterReducer(state, action);
}

Using the Store:

import { Store } from '@ngrx/store';
import { Component } from '@angular/core';
import { increment } from './counter.reducer';

@Component({
  selector: 'app-my-counter',
  template: `
    <button (click)="increment()">Increment</button>
    <div>Current Count: {{ count$ | async }}</div>
  `
})
export class MyCounterComponent {
  count$ = this.store.select('count');

  constructor(private store: Store<{ count: number }>) {}

  increment() {
    this.store.dispatch(increment());
  }
}

Performance Optimization and Best Practices

Lazy Loading Modules:

const routes: Routes = [
  { path: '', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) }
];

Using OnPush Change Detection:

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

@Component({
  selector: 'app-my-component',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `...`
})
export class MyComponent {}

Unit and Integration Testing

Unit Testing:

import { TestBed } from '@angular/core/testing';
import { MyComponent } from './my-component.component';

describe('MyComponent', () => {
  beforeEach(() => TestBed.configureTestingModule({ declarations: [MyComponent] }));

  it('should create the component', () => {
    const fixture = TestBed.createComponent(MyComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
});

Integration Testing:

import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { Router } from '@angular/router';

describe('AppComponent', () => {
  let router: Router;
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes([])],
      declarations: [AppComponent]
    });
    router = TestBed.inject(Router);
  });

  it('should navigate to "home" on click', fakeAsync(() => {
    router.navigate(['/home']);
    tick();
    expect(router.url).toBe('/home');
  }));
});

Deployment and Release

Production Build:

ng build --prod

Deploy to Server: Upload the dist folder to a server (e.g., Apache, Nginx) or host on platforms like Firebase or Netlify.

Share your love