Scalable Nx structure for Angular application

Lead Frontend Engineer

NG Rome MMXXIΒ - 09 July 2021

About me

Hi, My name is Trung 😊

  • Lead Engineer @cakedefi
  • Author of Angular Spotify, Jira Clone, Tetris
  • Founder @Angular Singapore
  • Community Lead @Angular Vietnam
  • Founded in 2021
  • Advocate and grow the Angular developer community in Singapore
  • Monthly meetup on the first Tuesday Β 
  • FREE one-on-one support πŸ‘‰ BOOK NOW!
  • Biggest Angular group in APAC
  • Advocate and grow the Angular developer community in Vietnam
  • 16k members
  • Founded in 2017 by
  • 100 Days Of AngularΒ series

Agenda

  • What is Nx?

  • Angular Spotify

  • Nx App vs Lib

  • Nx Lib Types

  • Sample Structure

  • Principles

Nx

Nx is a suite of dev tools to help you architect, test, and build at any scale.

Nx has fully integrated support for modern libraries like Jest, Cypress, ESLint, and more.

Β 

Nx works especially well for monorepos

What is monorepo?

A single git repository that holds the source code for multiple applications and libraries, along with the tooling for them.

Why monorepo?

Isn't it the same as putting multiple folders/projects within a repo?

Spotify hypothetical workspace

API

Core

Web

Mobile

  • API
  • Daemon
  • DB Schema

Code Collocation

.
β”œβ”€β”€ api
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ package-lock.json
β”‚   β”œβ”€β”€ node_modules
β”‚   └── tsconfig.json
β”œβ”€β”€ web
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ package-lock.json
β”‚   β”œβ”€β”€ node_modules
β”‚   └── tsconfig.json
└── mobile
    β”œβ”€β”€ package.json
    β”œβ”€β”€ package-lock.json
    β”œβ”€β”€ node_modules
    └── tsconfig.json

How it looks

.
β”œβ”€β”€ api
β”‚   └── types
β”‚       └── album.ts
β”œβ”€β”€ web
β”‚   β”œβ”€β”€ api
β”‚   β”‚   └── album-api.ts
β”‚   └── types
β”‚       └── album.ts
└── mobile
    β”œβ”€β”€ api
    β”‚   └── album-api.ts
    └── types
        └── album.ts

To better share code

API

Core

Core

SDK

Web

Mobile

  • API
  • Daemon
  • DB Schema
  • Data model
  • API endpoint
  • Utils function

@spotify/sdk

How it looks with the SDK

.
β”œβ”€β”€ core
β”‚   └── types
β”‚       └── album.ts
β”œβ”€β”€ sdk (publish to npm)
β”‚   β”œβ”€β”€ interfaces
β”‚   β”‚   └── album.ts
β”‚   └── api
β”‚       └── album-api.ts
β”œβ”€β”€ web
β”‚   └── import { Album } from "@spotify/sdk"
└── mobile
    └── import { Album } from "@spotify/sdk"
.
β”œβ”€β”€ apps
β”‚   β”œβ”€β”€ api
β”‚   β”‚   └── import { Album } from "@spotify/shared/interfaces"
β”‚   β”œβ”€β”€ web
β”‚   β”‚   β”œβ”€β”€ import { Album } from "@spotify/shared/interfaces"
β”‚   β”‚   β”œβ”€β”€ import { PlayButtonComponent } from "@spotify/shared/components"
β”‚   β”‚   └── import { VisualizationComponent } from "@spotify/web"
β”‚   β”œβ”€β”€ mobile
β”‚   β”‚   β”œβ”€β”€ import { Album } from "@spotify/shared/interfaces"
β”‚   β”‚   └── import { Button } from "@spotify/mobile/button"
β”‚   └── sdk
β”‚       └── import { Album } from "@spotify/shared/interfaces"
β”œβ”€β”€ libs
β”‚   β”œβ”€β”€ api
β”‚   β”œβ”€β”€ web
β”‚   β”‚   └── visualization
β”‚   └── mobile
β”‚       └── button
β”œβ”€β”€ shared
β”‚   β”œβ”€β”€ components
β”‚   β”‚   └── play-button
β”‚   └── interfaces
β”‚       └── album.ts
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
└── node_modules

Why monorepo?

  • Single set of dependencies: keep all of our dependencies up to date across an organization
    Β 
  • Shared code: Simplify code sharing/refactoring across an organization
    Β 
  • Improve cross-team collaboration: Get a consistent way of building and testing applicationsΒ 

Why Nx?

  • Faster command execution: run commands only on code that is affected by the current change.
    Β 
  • Local and distributed caching of executors
    Β 
  • Consistent code generation:Β automate code creation and modification tasks

Affected Command

npm run affected:dep-graph

Computation Caching & Scaling

Why not monorepo?

  • Need a standard set of collaboration structure: commit message, branch name and etc.
    Β 
  • Versioning
    Β 
  • Boilerplate

What is Angular Spotify?

Techstack

Nx App and Lib

Applications and libraries are two fundamental building blocks within Nx workspace

Nx App and Lib

β”œβ”€β”€ apps
β”œβ”€β”€ libs
β”œβ”€β”€ tools
β”œβ”€β”€ nx.json
β”œβ”€β”€ package.json
└── tsconfig.base.json

Nx App

  • Produces a binary.
  • Contains a minimal amount of code
  • Organize other libs into a deployable artifact
  • If we have two separate targets (say desktop and mobile), we need two separate apps.

Spotify App

.
└── apps
    β”œβ”€β”€ angular-spotify (web)
    β”œβ”€β”€ electron-spotify (desktop)
    └── ionic-spotify (mobile)

Nx Lib

  • A set of files packaged together to consume by apps
  • Easier to maintain and promote code reuse
  • A typical Nx workspace contains four (4) types of libs
    • feature
    • data-access
    • ui
    • and util

Nx Lib

└── libs 
    β”œβ”€β”€ web (angular-spotify)
    β”‚   β”œβ”€β”€ album
    β”‚   β”œβ”€β”€ browse
    β”‚   └── playlist
    β”œβ”€β”€ electron-spotify
    β”œβ”€β”€ ionic-spotify
    └── shared

Organizing code with libraries

Libraries require classifiers to describe their contents and intended purpose. These classifiers help to organize the libraries and to provide a way to locate them.

Scope

  • Relates to a logical grouping, business use-case, or domain
  • Use folder structure to denote scope

Scope

└── libs 
    β”œβ”€β”€ web (angular-spotify)
    β”‚   β”œβ”€β”€ album
    β”‚   β”‚   β”œβ”€β”€ data-access
    β”‚   β”‚   β”œβ”€β”€ feature
    β”‚   β”‚   └── ui
    β”‚   β”œβ”€β”€ browse
    β”‚   └── playlist
    β”œβ”€β”€ electron-spotify
    β”œβ”€β”€ ionic-spotify
    └── shared

scope:web

Type

Lib type Description
Feature Routable components
UI Presentational and container components
Data-access Interact with backend and state management related
Utility Low-level utilitiesΒ 

Original Nx documentationΒ with modification πŸ˜†

Type

└── libs 
    β”œβ”€β”€ web (angular-spotify)
    β”‚   β”œβ”€β”€ album
    β”‚   β”‚   β”œβ”€β”€ data-access
    β”‚   β”‚   β”œβ”€β”€ feature
    β”‚   β”‚   └── ui
    β”‚   β”œβ”€β”€ browse
    β”‚   └── playlist
    β”œβ”€β”€ electron-spotify
    β”œβ”€β”€ ionic-spotify
    └── shared

scope:web,
type:feature

Summary

Every library should be:

  • Located in the folder tree by scope
  • Have tags in formatΒ 

scope:SCOPE,type:TYPE

Sample Structure

Sample Structure

└── libs 
    β”œβ”€β”€ web (angular-spotify)
    β”‚   β”œβ”€β”€ album                  <-- grouping folder
    β”‚   β”‚   β”œβ”€β”€ data-access        <-- angular lib
    β”‚   β”‚   β”œβ”€β”€ feature            <-- grouping folder
    β”‚   β”‚   β”‚   β”œβ”€β”€ detail         <-- angular lib
    β”‚   β”‚   β”‚   β”œβ”€β”€ list           <-- angular lib
    β”‚   β”‚   β”‚   └── shell          <-- angular lib
    β”‚   β”‚   └── ui                 <-- grouping folder
    β”‚   β”‚       └── album-track    <-- angular lib
    β”‚   β”œβ”€β”€ browse
    β”‚   └── playlist
    β”œβ”€β”€ electron-spotify
    β”œβ”€β”€ ionic-spotify
    └── shared

Sample Structure

Sample Structure

└── libs
    └── client                    <-- grouping folder (dir)
        β”œβ”€β”€ shell                 <-- grouping folder (dir) 
        β”‚   └── feature           <-- angular:lib (3)
        β”œβ”€β”€ feature-1             <-- grouping folder (dir)
        β”‚   β”œβ”€β”€ data-access       <-- angular:lib, service, API calls, state management)
        β”‚   β”œβ”€β”€ feature           <-- grouping folder (dir) or lib (4)
        β”‚   β”‚   β”œβ”€β”€ list          <-- angular:lib e.g. ProductList
        β”‚   β”‚   └── detail        <-- angular:lib e.g. ProductDetail
        β”‚   └── ui                <-- grouping folder (dir)
        β”‚       β”œβ”€β”€ comp-1        <-- angular:lib, SCAM for Component
        β”‚       └── pipe-1        <-- angular:lib, SCAM for Pipe
        └── shared                <-- grouping folder (dir)
            β”œβ”€β”€ data-access       <-- angular:lib, any Service or State management to share across the Client app)
            β”œβ”€β”€ ui                <-- grouping folder (dir) (5)
            └── utils             <-- angular:lib, usually shared Guards, Interceptors, Validators...)

Principles

  • OnPush Change Detection and async pipes
    Β 
  • No function calls in Angular template expressions
    Β 
  • SCAMs (single component Angular modules)
    Β 
  • Everything will stay in the libsΒ folder

Why

  • Consistency: eliminate mental overhead "where to put what"
    Β 
  • Promote (SCAM) to get the benefits from the Nx affected commands.
    Β 
  • Prevent circular dependencies issue.

View the full notes

Reference

Enterprise Monorepo Angular Patterns, by Nitin Vericherla & Victor Savkin.

Thank you!

Gracie!

Thanks everyone for your support!

Scalable Nx Architecture for Angular applications

By Trung Vo

Scalable Nx Architecture for Angular applications

Deck for NG Rome MMXXI πŸ‘‰ https://ngrome.io/

  • 1,001