Angular Framework Architecture

Module 14: JavaScript Frontend Frameworks - Vue & Angular

Introduction to Angular

Angular is a platform and framework for building single-page client applications using HTML and TypeScript. Developed and maintained by Google, Angular is a complete rewrite of AngularJS (Angular 1.x), with a focus on modern web development practices, improved performance, and better scalability.

Real-world analogy: If web development were construction, Angular would be a comprehensive construction system with standardized, prefabricated components, specialized tools, and detailed blueprints. It provides everything you need to build a complete structure from foundation to roof, with strict guidelines on how components should fit together.

2010: AngularJS (1.x)

Original framework developed by Miško Hevery at Google

2016: Angular 2

Complete rewrite, introducing TypeScript, component-based architecture, and modern tooling

2017-Present: Angular 4+ (Now Angular 17+)

Semantic versioning adopted with more frequent, smaller updates and improved performance

Angular vs Other Frameworks

To understand Angular's approach, let's compare it with other popular JavaScript frameworks:

Angular

  • Complete framework with built-in solutions for routing, forms, HTTP, testing, etc.
  • TypeScript-first approach with strong typing
  • Opinionated with strict architectural guidelines
  • RxJS integration for reactive programming
  • Enterprise-ready with focus on large-scale applications

Vue

  • Progressive framework that can be adopted incrementally
  • Optional TypeScript support
  • Less opinionated with flexible architecture
  • Simple and intuitive with gentle learning curve
  • Lightweight core with optional plugins

React

  • Library rather than a complete framework
  • JSX for templating
  • Unidirectional data flow with focus on immutability
  • Ecosystem of third-party libraries
  • Virtual DOM focus

When to choose Angular: Angular is particularly well-suited for large enterprise applications, teams that prefer strongly typed languages, and projects that benefit from a comprehensive, opinionated framework with built-in solutions for common challenges.

Core Angular Concepts

Angular's architecture is built on a few key concepts:

flowchart TD A[Angular Application] --> B[Modules] B --> C[Components] C --> D[Templates] C --> E[Metadata] C --> F[Data Binding] C --> G[Directives] A --> H[Services] H --> I[Dependency Injection] A --> J[Routing] style A fill:#DD0031,color:white style B fill:#DD0031,color:white style C fill:#DD0031,color:white style H fill:#C3002F,color:white style I fill:#C3002F,color:white style J fill:#C3002F,color:white

Modules (NgModules)

NgModules are containers for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. They help organize the application into cohesive functionality blocks.

@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent,
    HeroDetailComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [HeroService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Real-world analogy: NgModules are like departments in a company. Each department has its own specialized team members (components, directives), tools (services), relationships with other departments (imports/exports), and specific responsibilities.

Components

Components are the basic building blocks of Angular applications. A component controls a patch of screen called a view, consisting of a template (HTML), styles (CSS), and a class (TypeScript) that handles behavior.

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent {
  @Input() hero: Hero;
  
  constructor(private heroService: HeroService) { }
  
  saveHero(): void {
    this.heroService.updateHero(this.hero)
      .subscribe(() => this.goBack());
  }
}

Real-world analogy: Components are like specialized machine operators in a factory. Each operator manages their own station (view), follows a specific blueprint (template), has a unique appearance (styles), and knows exactly what to do and how to do it (class).

Templates

Templates define the component's view with HTML enhanced by Angular directives and binding syntax.

<div class="hero-detail">
  <h2>{{hero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{hero.id}}</div>
  <div>
    <label>name:
      <input [(ngModel)]="hero.name" placeholder="name"/>
    </label>
  </div>
  <button (click)="saveHero()">Save</button>
  <button (click)="goBack()">Back</button>
</div>

Services

Services are classes with a focused purpose. They're used for data fetching, logging, or any functionality that isn't tied to a specific view.

@Injectable({
  providedIn: 'root'
})
export class HeroService {
  private heroesUrl = 'api/heroes';
  
  constructor(
    private http: HttpClient,
    private messageService: MessageService
  ) { }
  
  getHeroes(): Observable<Hero[]> {
    return this.http.get<Hero[]>(this.heroesUrl)
      .pipe(
        tap(_ => this.log('fetched heroes')),
        catchError(this.handleError<Hero[]>('getHeroes', []))
      );
  }
}

Real-world analogy: Services are like utility companies or specialized contractors in a city. They provide specific functionalities (water, electricity, maintenance) that multiple buildings (components) can use without having to implement these functionalities themselves.

Dependency Injection

Dependency Injection (DI) is a design pattern in which a class requests dependencies from external sources rather than creating them. Angular's DI system provides declared dependencies to a class when it's instantiated.

@Component({
  selector: 'app-hero-list',
  templateUrl: './hero-list.component.html'
})
export class HeroListComponent implements OnInit {
  heroes: Hero[] = [];
  
  // HeroService is injected into this component
  constructor(private heroService: HeroService) { }
  
  ngOnInit(): void {
    this.getHeroes();
  }
  
  getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
  }
}

Real-world analogy: Dependency Injection is like a hospitality service at a conference. Instead of each speaker (component) bringing their own projector, microphone, and water (services), the conference organizer (Angular DI) provides these resources as needed.

Angular Architecture Diagram

The following diagram illustrates how different parts of an Angular application interact:

flowchart TB U[User] --> V[View / UI] V --> C[Component] C --> V C --> S[Services] S --> BE[Backend API] C --> |provides context to| T[Template] T --> |renders to| V subgraph Angular Application C T S V end style C fill:#DD0031,color:white style S fill:#DD0031,color:white style T fill:#DD0031,color:white style V fill:#C3002F,color:white style U fill:#ffffff,stroke:#000000 style BE fill:#ffffff,stroke:#000000
  1. Users interact with the View (UI)
  2. The View is defined by the Template, which is bound to the Component
  3. Components contain the application logic and can use Services for cross-cutting concerns
  4. Services can communicate with external sources like Backend APIs
  5. Changes in the Component are reflected in the View through binding

Data Binding

Angular's data binding mechanism synchronizes the application state with the view. There are four forms of data binding:

flowchart LR A[DOM] <-- Interpolation {{value}} --> B[Component] A <-- Property Binding [property]="value" --> B A --> |Event Binding (event)="handler"| B A <--> |Two-way Binding [(ngModel)]="property"| B style A fill:#C3002F,color:white style B fill:#DD0031,color:white

Interpolation

<h1>{{title}}</h1>
<p>{{hero.name}} details!</p>

Displays component property values in the template.

Property Binding

<img [src]="heroImageUrl">
<button [disabled]="isDisabled">Save</button>

Sets an element property to a component property value.

Event Binding

<button (click)="save()">Save</button>
<input (input)="handleInput($event)">

Responds to user events by calling component methods.

Two-way Binding

<input [(ngModel)]="hero.name">

Combines property and event binding to create a two-way flow of data.

Real-world analogy: Data binding is like a sophisticated automation system in a smart home. Interpolation and property binding are like displays that show the current state (e.g., thermostat showing temperature). Event binding is like motion sensors that trigger actions when detected. Two-way binding is like a smart thermostat that both displays the current temperature and allows you to change it.

Directives

Directives are classes that add behavior to elements in Angular applications. There are three kinds of directives:

Components

Components are directives with a template. They are the most common type of directive.

<app-hero-detail [hero]="selectedHero"></app-hero-detail>

Structural Directives

Structural directives alter the DOM layout by adding and removing elements.

<!-- ngFor iterates over heroes array -->
<li *ngFor="let hero of heroes">
  {{hero.name}}
</li>

<!-- ngIf conditionally includes an element -->
<div *ngIf="selectedHero">
  <h2>{{selectedHero.name | uppercase}} Details</h2>
</div>

<!-- ngSwitch conditionally displays one of multiple elements -->
<div [ngSwitch]="hero.type">
  <app-warrior-hero *ngSwitchCase="'warrior'" [hero]="hero"></app-warrior-hero>
  <app-mage-hero *ngSwitchCase="'mage'" [hero]="hero"></app-mage-hero>
  <app-default-hero *ngSwitchDefault [hero]="hero"></app-default-hero>
</div>

Attribute Directives

Attribute directives alter the appearance or behavior of an existing element.

<!-- ngStyle dynamically sets CSS properties -->
<div [ngStyle]="{'color': hero.type === 'warrior' ? 'red' : 'blue'}">
  {{hero.name}}
</div>

<!-- ngClass dynamically adds/removes CSS classes -->
<div [ngClass]="{'selected': hero === selectedHero, 
                'warrior': hero.type === 'warrior'}">
  {{hero.name}}
</div>

Custom Directives

You can create your own directives to encapsulate reusable behavior.

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input('appHighlight') highlightColor: string;
  
  constructor(private el: ElementRef) { }
  
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || 'yellow');
  }
  
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }
  
  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}

// Usage in template
<p appHighlight="lightblue">Highlight me!</p>

Real-world analogy: If components are like specialized machine operators, directives are like add-on tools and accessories that enhance their capabilities. Structural directives are like jigs that determine what parts go where. Attribute directives are like settings on a machine that modify how it operates.

Angular Routing

Angular Router enables navigation between views based on user actions and provides features like route parameters, guards, and lazy loading.

Basic Routing Configuration

// app-routing.module.ts
const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'heroes', component: HeroesComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: '**', component: PageNotFoundComponent }
];

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

// app.component.html
<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>

Route Parameters

// In component
export class HeroDetailComponent implements OnInit {
  hero: Hero;
  
  constructor(
    private route: ActivatedRoute,
    private heroService: HeroService
  ) {}
  
  ngOnInit(): void {
    const id = this.route.snapshot.paramMap.get('id');
    this.heroService.getHero(id)
      .subscribe(hero => this.hero = hero);
  }
}

Route Guards

Route guards control access to routes based on conditions like authentication or unsaved changes.

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}
  
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    const url: string = state.url;
    return this.checkLogin(url);
  }
  
  checkLogin(url: string): boolean {
    if (this.authService.isLoggedIn) { return true; }
    
    // Store attempted URL for redirecting after login
    this.authService.redirectUrl = url;
    
    // Navigate to login page
    this.router.navigate(['/login']);
    return false;
  }
}

// In routes configuration
{
  path: 'admin',
  component: AdminComponent,
  canActivate: [AuthGuard]
}

Lazy Loading

Lazy loading improves application startup time by loading feature modules only when needed.

const routes: Routes = [
  {
    path: 'customers',
    loadChildren: () => import('./customers/customers.module')
      .then(m => m.CustomersModule)
  },
  {
    path: 'orders',
    loadChildren: () => import('./orders/orders.module')
      .then(m => m.OrdersModule)
  }
];

Real-world analogy: Angular Routing is like a GPS navigation system. It directs users to different "locations" (components) based on the URL. Route parameters are like specific addresses. Guards are like security checkpoints that may prevent access to certain areas. Lazy loading is like only downloading map data for an area when you actually decide to go there.

Angular CLI and Tools

Angular provides a powerful Command Line Interface (CLI) that makes it easy to create, develop, test, and maintain Angular applications.

Key CLI Commands

# Create a new application
ng new my-app

# Generate a new component
ng generate component hero-detail

# Build the application
ng build

# Run the application locally
ng serve

# Run unit tests
ng test

# Run end-to-end tests
ng e2e

Project Structure

A typical Angular project has the following structure:

my-app/
├── src/
│   ├── app/
│   │   ├── app.component.ts
│   │   ├── app.component.html
│   │   ├── app.component.css
│   │   ├── app.component.spec.ts
│   │   ├── app.module.ts
│   │   └── ...
│   ├── assets/
│   │   └── ...
│   ├── environments/
│   │   ├── environment.ts
│   │   └── environment.prod.ts
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   └── test.ts
├── node_modules/
├── angular.json
├── package.json
├── tsconfig.json
└── ...

Angular Ecosystem

Angular comes with a rich ecosystem of supporting libraries and tools:

Angular Versions and Upgrading

Angular follows semantic versioning and has a predictable release schedule:

Angular provides tools and detailed guides for upgrading between versions, including the Angular Update Guide, which provides step-by-step instructions for upgrading between specific versions.

Practical Example: Angular Application Structure

Let's look at a practical example of an Angular application for a simple task management system.

Module Structure

// app.module.ts
@NgModule({
  declarations: [
    AppComponent,
    TaskListComponent,
    TaskDetailComponent,
    TaskFormComponent,
    FilterPipe
  ],
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [
    TaskService,
    AuthService,
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Component Structure

// task-list.component.ts
@Component({
  selector: 'app-task-list',
  templateUrl: './task-list.component.html',
  styleUrls: ['./task-list.component.css']
})
export class TaskListComponent implements OnInit {
  tasks: Task[] = [];
  filteredTasks: Task[] = [];
  filterStatus: string = 'all';
  
  constructor(private taskService: TaskService) { }
  
  ngOnInit(): void {
    this.getTasks();
  }
  
  getTasks(): void {
    this.taskService.getTasks()
      .subscribe(tasks => {
        this.tasks = tasks;
        this.applyFilter();
      });
  }
  
  applyFilter(): void {
    if (this.filterStatus === 'all') {
      this.filteredTasks = this.tasks;
    } else {
      const status = this.filterStatus === 'completed';
      this.filteredTasks = this.tasks.filter(task => task.completed === status);
    }
  }
  
  deleteTask(task: Task): void {
    this.tasks = this.tasks.filter(t => t.id !== task.id);
    this.taskService.deleteTask(task.id).subscribe();
    this.applyFilter();
  }
}

Template Structure

<!-- task-list.component.html -->
<div class="task-list-container">
  <h2>My Tasks</h2>
  
  <div class="filter-controls">
    <label>Show: 
      <select [(ngModel)]="filterStatus" (change)="applyFilter()">
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>
    </label>
    
    <button routerLink="/tasks/new" class="btn-add">Add Task</button>
  </div>
  
  <div *ngIf="filteredTasks.length === 0" class="empty-state">
    No tasks found.
  </div>
  
  <ul class="task-list">
    <li *ngFor="let task of filteredTasks" 
        [class.completed]="task.completed">
      
      <div class="task-item">
        <label class="checkbox-container">
          <input type="checkbox" 
                 [checked]="task.completed"
                 (change)="toggleComplete(task)">
          <span class="checkmark"></span>
        </label>
        
        <span class="task-title">{{ task.title }}</span>
        
        <div class="task-actions">
          <button [routerLink]="['/tasks', task.id]" class="btn-edit">
            Edit
          </button>
          <button (click)="deleteTask(task)" class="btn-delete">
            Delete
          </button>
        </div>
      </div>
    </li>
  </ul>
</div>

Service Structure

// task.service.ts
@Injectable({
  providedIn: 'root'
})
export class TaskService {
  private tasksUrl = 'api/tasks';
  
  constructor(
    private http: HttpClient,
    private messageService: MessageService
  ) { }
  
  getTasks(): Observable<Task[]> {
    return this.http.get<Task[]>(this.tasksUrl)
      .pipe(
        tap(_ => this.log('fetched tasks')),
        catchError(this.handleError<Task[]>('getTasks', []))
      );
  }
  
  getTask(id: number): Observable<Task> {
    const url = `${this.tasksUrl}/${id}`;
    return this.http.get<Task>(url)
      .pipe(
        tap(_ => this.log(`fetched task id=${id}`)),
        catchError(this.handleError<Task>(`getTask id=${id}`))
      );
  }
  
  addTask(task: Task): Observable<Task> {
    return this.http.post<Task>(this.tasksUrl, task)
      .pipe(
        tap((newTask: Task) => this.log(`added task id=${newTask.id}`)),
        catchError(this.handleError<Task>('addTask'))
      );
  }
  
  updateTask(task: Task): Observable<any> {
    return this.http.put(this.tasksUrl, task)
      .pipe(
        tap(_ => this.log(`updated task id=${task.id}`)),
        catchError(this.handleError<any>('updateTask'))
      );
  }
  
  deleteTask(id: number): Observable<Task> {
    const url = `${this.tasksUrl}/${id}`;
    return this.http.delete<Task>(url)
      .pipe(
        tap(_ => this.log(`deleted task id=${id}`)),
        catchError(this.handleError<Task>('deleteTask'))
      );
  }
  
  private log(message: string) {
    this.messageService.add(`TaskService: ${message}`);
  }
  
  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(error);
      this.log(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }
}

Routing Structure

// app-routing.module.ts
const routes: Routes = [
  { path: '', redirectTo: '/tasks', pathMatch: 'full' },
  { path: 'tasks', component: TaskListComponent },
  { path: 'tasks/new', component: TaskFormComponent },
  { path: 'tasks/:id', component: TaskDetailComponent },
  { path: '**', component: PageNotFoundComponent }
];

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

Activities for Practice

Exercise 1: Angular Concepts Map

Create a concept map or diagram showing the relationships between different Angular concepts (modules, components, services, directives, etc.). For each concept, provide a short definition and a real-world analogy that helps explain its purpose.

Exercise 2: Framework Comparison

Create a detailed comparison table of Angular, Vue, and React. Consider factors like:

  • Learning curve
  • Project structure
  • State management
  • Performance
  • Community and ecosystem
  • Use cases and strengths

For each framework, identify at least one type of project where it would be the best choice, and explain why.

Exercise 3: Angular App Planning

Design the structure of an Angular application for a simple e-commerce store. Your design should include:

  • Module structure (feature modules, shared modules)
  • Component hierarchy
  • Services needed
  • Routing architecture
  • Data models

Create a visual diagram showing how these parts interact, and explain why you organized them this way.

Additional Resources