Angular Architecture Talk

Small Footprint Angular - The agnostic way of writing Angular Applications

by Gion Kunz

Gion Kunz

  • Angular GDE since 2018
  • Front-end developer, web standards advocate
  • Specialized in spa development using Angular
  • Onsite ramp-ups, coaching and implementation support in projects
  • Author of the book "Mastering Angular Components"
  • Frequent speaker at conferences and meetups
  • Teacher for web development and UX engineering at the SAE Institute in Zurich
  • Tutor at O'Reilly Safari Online Training
  • Contributor on the Angular project, author of Chartist.js library

Founder, UI Developer, UX Enthusiast at syncrea GmbH

Reducing Framework Footprint

Only use Angular where really needed. Use plain TypeScript for the remaining.

How-to 

  • Follow pragmatic TypeScript guidelines
  • Create global model using TypeScript interfaces
  • Create helper module with pure functions to tackle complex application logic
  • Follow container / presentation component pattern
  • Only create services for API clients / persistence layer
  • Use centralized state

Group Presentational Components by Feature

./projects/my-app
└── src
    ├── app
    │   ├── components
    │   │   ├── common
    │   │   │   └── layout-container
    │   │   ├── payment
    │   │   │   └── payment-modal
    │   │   ├── product
    │   │   │   └── product-details
    │   │   └── shell
    │   │       └── app
...

One container per application view

...
    │   ├── containers
    │   │   ├── app-container
    │   │   ├── payment-container
    │   │   └── product-container
...
./projects/my-app
└── src
    ├── app
    │   ├── app.module.ts
    │   ├── components
    │   │   ├── common
    │   │   │   └── main-layout
    │   │   │       └── main-layout.component.ts
    │   │   ├── payment
    │   │   │   └── payment-modal
    │   │   │       └── payment-modal.component.ts
    │   │   ├── product
    │   │   │   └── product-details
    │   │   │       └── product-details.component.ts
    │   │   └── shell
    │   │       └── app
    │   ├── containers
    │   │   ├── app-container
    │   │   │   └── app-container.component.ts
    │   │   ├── payment-container
    │   │   │   └── payment-container.component.ts
    │   │   └── product-container
    │   │   │   └── product-container.component.ts
    │   ├── guards
    │   │   ├── product.guard.ts
    │   │   └── payment.guard.ts
    │   ├── helpers
    │   │   └── helpers.ts
    │   ├── model
    │   │   └── model.ts
    │   ├── pipes
    │   ├── services
    │   │   ├── payment.service.ts
    │   │   └── product.service.ts
    │   ├── state
    │   │   ├── app
    │   │   │   ├── actions.ts
    │   │   │   ├── effects.ts
    │   │   │   ├── reducers.ts
    │   │   │   └── state.ts
    │   │   ├── payment
    │   │   │   ├── actions.ts
    │   │   │   ├── effects.ts
    │   │   │   ├── reducers.ts
    │   │   │   └── state.ts
    │   │   └── product
    │   │       ├── actions.ts
    │   │       ├── effects.ts
    │   │       ├── reducers.ts
    │   │       └── state.ts
    │   └── tokens.ts
    ├── assets
    ├── index.html
    ├── main.ts
    ├── polyfills.ts
    ├── routes.ts
    └── styles
        └── styles.scss

Complete Structure Overview

Simple UI components

@Component({
  selector: 'user-profile',
  template: `
    <p class="name">{{name}}</p>
  `
})
class UserProfile {
  @Input() name;
}
  • Only display data which gets passed to inputs
  • Delegate actions to parent components using outputs
  • Never manipulate data directly

Container Components

  • Helps you to centralize your data fetching / manipulation
    • Transparency and traceability
  • Re-use Components in different context
    • Only the container component decides what data / actions are performed
  • Loose coupling with Input / Output

Listing App using Container Component

  • List container component
  • List UI component
  • List Item UI component
  • UI Actions

Inversion of control with container components

  • Data gets passed down from the container component
  • Actions get delegated upwards in the component tree
  • Data fetching and manipulation only happens in the container components

Container Component

Data

Data

Action

Action

Typical Application

10%
Container Components

90% Presentational Components

Simple API client services

import {ServerProduct, Product} from '../../model/model';
import {convertServerProduct} from '../../helpers/helpers';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  constructor(private http: HttpService) {}

  loadProducts(): Product[] {
    return this.http.get<ServerProduct[]>('').pipe(
      map(serverProducts => serverProducts.map(convertServerProduct))
    );
  }
}

Pure Helper Functions

...

export function calculateTotalPrice(products: Product[]): number {
  return products
    .filter(product => product.selected)
    .reduce((totalPrice, product) => totalPrice + product.price, 0);
}

export function isAdult(person: Person, now: Date): boolean {
  return !!person.birthdate && getAge(person.birthdate, now) >= 18;
}

...

Code Responsibilities

Data
Persistance

Application and Business Logic

Application State

Obtain State and Trigger Actions

Presentation and View Logic

Angular Services

Helper module with pure functions

Any state management framework (e.g. ngrx, redux etc.)

Container Components

Pure Presentational Components

Framework Footprint

Data
Persistance

Application and Business Logic

Application State

Obtain State and Trigger Actions

Presentation and View Logic

Very simple Angular services which only connect to remote API's

Depends on state management framework. Only handful of containers.

Depends on view rendering capabilities of Angular.

Let's create together.

Thanks!