Files

8.1 KiB

Angular Tooling Patterns

Table of Contents

Custom Schematics

Generate Schematic Collection

# Install schematics CLI
npm install -g @angular-devkit/schematics-cli

# Create schematic collection
schematics blank --name=my-schematics

Simple Component Schematic

// src/my-component/index.ts
import { Rule, SchematicContext, Tree, apply, url, template, move, mergeWith } from '@angular-devkit/schematics';
import { strings } from '@angular-devkit/core';

export function myComponent(options: { name: string; path: string }): Rule {
    return (tree: Tree, context: SchematicContext) => {
        const templateSource = apply(url('./files'), [
            template({
                ...options,
                ...strings,
            }),
            move(options.path),
        ]);

        return mergeWith(templateSource)(tree, context);
    };
}

Use Custom Schematics

# Link locally
npm link ./my-schematics

# Use
ng generate my-schematics:my-component --name=test --path=src/app

Build Optimization

Budget Configuration

{
    "budgets": [
        {
            "type": "initial",
            "maximumWarning": "500kB",
            "maximumError": "1MB"
        },
        {
            "type": "anyComponentStyle",
            "maximumWarning": "4kB",
            "maximumError": "8kB"
        },
        {
            "type": "anyScript",
            "maximumWarning": "100kB",
            "maximumError": "200kB"
        }
    ]
}

Differential Loading

Automatic in v20+ - builds for modern browsers by default.

// .browserslistrc
last 2 Chrome versions
last 2 Firefox versions
last 2 Safari versions
last 2 Edge versions

Code Splitting

// Lazy load routes for automatic code splitting
export const routes: Routes = [
    {
        path: 'admin',
        loadChildren: () => import('./admin/admin.routes').then((m) => m.adminRoutes),
    },
    {
        path: 'reports',
        loadComponent: () => import('./reports/reports.component').then((m) => m.Reports),
    },
];

Tree Shaking

Ensure proper imports for tree shaking:

// Good - tree shakeable
import { map, filter } from 'rxjs';

// Avoid - imports entire library
import * as rxjs from 'rxjs';

Preload Strategy

// app.config.ts
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';

export const appConfig: ApplicationConfig = {
    providers: [provideRouter(routes, withPreloading(PreloadAllModules))],
};

Multi-Project Workspace

Create Workspace

# Create empty workspace
ng new my-workspace --create-application=false

cd my-workspace

# Add applications
ng generate application main-app
ng generate application admin-app

# Add library
ng generate library shared-ui
ng generate library data-access

Workspace Structure

my-workspace/
├── projects/
│   ├── main-app/
│   │   └── src/
│   ├── admin-app/
│   │   └── src/
│   ├── shared-ui/
│   │   └── src/
│   └── data-access/
│       └── src/
├── angular.json
└── package.json

Build Specific Project

ng build main-app
ng build shared-ui
ng serve admin-app

Library Configuration

// projects/shared-ui/ng-package.json
{
    "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
    "dest": "../../dist/shared-ui",
    "lib": {
        "entryFile": "src/public-api.ts"
    }
}

Using Library in App

// After building library: ng build shared-ui
import { Button } from 'shared-ui';

@Component({
    imports: [Button],
    template: `<lib-button>Click</lib-button>`,
})
export class App {}

CI/CD Configuration

GitHub Actions

# .github/workflows/ci.yml
name: CI

on:
    push:
        branches: [main]
    pull_request:
        branches: [main]

jobs:
    build:
        runs-on: ubuntu-latest

        steps:
            - uses: actions/checkout@v4

            - name: Setup Node.js
              uses: actions/setup-node@v4
              with:
                  node-version: '20'
                  cache: 'npm'

            - name: Install dependencies
              run: npm ci

            - name: Lint
              run: npm run lint

            - name: Test
              run: npm run test -- --watch=false --browsers=ChromeHeadless --code-coverage

            - name: Build
              run: npm run build -- -c production

            - name: Upload coverage
              uses: codecov/codecov-action@v3
              with:
                  files: ./coverage/lcov.info

GitLab CI

# .gitlab-ci.yml
image: node:20

cache:
    paths:
        - node_modules/
        - .angular/cache/

stages:
    - install
    - test
    - build

install:
    stage: install
    script:
        - npm ci

test:
    stage: test
    script:
        - npm run lint
        - npm run test -- --watch=false --browsers=ChromeHeadless

build:
    stage: build
    script:
        - npm run build -- -c production
    artifacts:
        paths:
            - dist/

Path Aliases

Configure tsconfig.json

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@app/*": ["src/app/*"],
            "@env/*": ["src/environments/*"],
            "@shared/*": ["src/app/shared/*"],
            "@features/*": ["src/app/features/*"],
            "@core/*": ["src/app/core/*"]
        }
    }
}

Usage

// Instead of relative imports
import { User } from '../../../core/services/user.service';

// Use path alias
import { User } from '@core/services/user.service';

Proxy Configuration

Development Proxy

// proxy.conf.json
{
    "/api": {
        "target": "http://localhost:3000",
        "secure": false,
        "changeOrigin": true
    },
    "/auth": {
        "target": "http://localhost:4000",
        "secure": false,
        "pathRewrite": {
            "^/auth": ""
        }
    }
}

Configure in angular.json

{
    "serve": {
        "options": {
            "proxyConfig": "proxy.conf.json"
        }
    }
}

Or via CLI

ng serve --proxy-config proxy.conf.json

Custom Builders

Using esbuild (Default in v20+)

{
    "architect": {
        "build": {
            "builder": "@angular-devkit/build-angular:application",
            "options": {
                "browser": "src/main.ts"
            }
        }
    }
}

SSR Configuration

# Add SSR
ng add @angular/ssr
{
    "architect": {
        "build": {
            "options": {
                "server": "src/main.server.ts",
                "prerender": true,
                "ssr": {
                    "entry": "server.ts"
                }
            }
        }
    }
}

Debugging

Source Maps

{
    "configurations": {
        "development": {
            "sourceMap": true
        },
        "production": {
            "sourceMap": {
                "scripts": true,
                "styles": false,
                "hidden": true,
                "vendor": false
            }
        }
    }
}

Verbose Logging

ng build --verbose
ng serve --verbose

Debug Tests

# Run tests with debugging
ng test --browsers=Chrome

# In Chrome DevTools, open Sources tab and set breakpoints

Package Scripts

{
    "scripts": {
        "start": "ng serve",
        "build": "ng build",
        "build:prod": "ng build -c production",
        "test": "ng test",
        "test:ci": "ng test --watch=false --browsers=ChromeHeadless --code-coverage",
        "lint": "ng lint",
        "lint:fix": "ng lint --fix",
        "analyze": "ng build -c production --stats-json && npx esbuild-visualizer --metadata dist/my-app/browser/stats.json --open",
        "update": "ng update"
    }
}