Skip to content
Go back

Angular Big Project Structure

Published:  at  08:00 PM

When I say “big”, what I really mean is Angular applications that are contributed to by >1 developer and are using automated build and testing, probably CI/CD.

Example Project Structure

Starting at the src directory, treat each part of your application as a feature. Keep the app feature small. It will produce your main.MD5HAS.js on every rebuild, and have to be sent to the users of your application.

The features directory is a peer with app, and has nested directories for each feature. If the feature exposes a route, put that in the root of the directory.

src
├── app
│   ├── app.component.ts
│   ├── app.config.ts
│   ├── app.routes.ts
│   ├── components
│   │   └── nav-bar.ts
│   ├── data.ts
│   ├── pages
│   │   └── home.ts
│   └── services
|       └── telemetry.ts
├── features
│   └── admin
│       ├── admin.ts
│       ├── components
│       │   ├── course-card.ts
│       │   └── track-card.ts
│       ├── pages
│       │   ├── add-course.ts
│       │   ├── add-offering.ts
│       │   ├── add-track.ts
│       │   └── todo.ts
│       ├── routes.ts
│       └── stores
│           ├── admin.ts
│           ├── courses.ts
│           ├── tracks.ts
│           └── with-courses.ts
├── shared
│   └── user-store.ts

Each feature (app, as well as any code in the features directory) should be as isolated as possible.

Some exceptions:

The Goal With Features

Features are usually worked on by a small subset of the team, often in parallel with other work in the application. Features typically have a high rate of change (code churn) initially (while we get feedback from the team, users, testers, etc.). A developer working in a feature should be able to:

Some Rules:

About the shared Directory

Sometimes I’ve done this as a package, but usually I find this not worth the effort. The shared directory is for things that are “up for grabs” across any feature in the application.

What that means, though, is that while a major change to something inside of a feature would have no impact on any other part of the application, any changes in the shared directory may have unintended side effects in unknown places in the application.

Items (for example, components) in the shared directory, by definition, do not have a specific use. They are shared and therefore general use. This increases the requirements for tests to detect regressions, and these tests are fairly “context free”. You tend to have to do more defensive coding on shared assets.

Likewise, if you are working in a feature and your are using something from the shared directory (or from node_modules!) make sure your tests prove your specific use of those imports.

Note: UI Testing for shared items is challenging. No good testing frameworks (other than, arguably, Cypress) allow you to do “component testing”. This is the #1 argument for putting these in a separate library that the Angular app can npm install. In that library you can use tools like Cypress component testing, or something like Playwright with StoryBook or developer created examples of their use that are tested against.

Using Sheriff To Protect Boundaries with ESlint

Sheriff is a great tool for enforcing module boundaries with ES-Lint. Instructions for installing and initializing are at the link.

Here’s a pretty general-purpose config for the above proscribed structure:

import { SheriffConfig, sameTag } from '@softarc/sheriff-core';


import { SheriffConfig, sameTag } from '@softarc/sheriff-core';

export const config: SheriffConfig = {
  autoTagging: true,
  enableBarrelLess: true,
  modules: {
    'src/features/<domain>': ['domain:<domain>', 'type:feature'],
    'src/app': ['domain:app', 'type:app'],
    'src/shared': ['type:shared'],
  }, // apply tags to your modules
  depRules: {
    'type:feature': [sameTag, 'type:shared'],
    'domain:app': [sameTag, 'type:shared', 'type:feature'],
    'domain:*': [sameTag, 'type:shared'],
    'type:app': [sameTag, 'type:feature', 'type:shared'],
    'type:shared': [sameTag],
    root: ['type:app'],
  },
};

Summary

There are a lot of “rules” here, but I think the payoff is worth it in terms of less overall frustration, and increased velocity. Consider using a tool like es-lint to help enforce these rules.


Suggest Changes
Share this post on:

Previous Post
Using Dan Abramov Using AI To Solve My Problems