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!

Made with Slides.com