A flexible Angular 1.5 project structure (the “fractal” architecture) [“NG 1.5 from the trenches” 4/7]

This post is the 4th part of the “Angular 1.5 from the trenches” series, presenting the architecture of a component-oriented, “NG 2 ready” Angular 1.5 app we’re building.
The module.component() method, introduced in Angular 1.5, may seem only a cosmetic addition, but the approach it promotes (and which we utilise to the full extent) results in a very different flavour of architecture than most of the “classic” Angular tutorials describe, so I hope you’ll learn a thing or two from our experience.

Table of Contents:

In the previous two articles, I’ve described the conceptual structure of an Angular 1.5 app: how to split an app into components and how components communicate. Now is the time to discuss the physical organization of a component-oriented app: the directory and file structure.

A high-level view: an app structure

there are two main ways to organize an app:

  • per technical layer, with top-level directories like components, services or filters
  • per feature (sometimes called pods architecture), with top-level directories like todos, tags or users

feature-based app structure offers several advantages:

  • the app is easier to understand
  • it’s easier to find stuff
  • it’s easier to split the app (e.g. into separate services)

Read also an excellent post by Uncle Bob Martin, who calls it “screaming architecture” and in his typical style nails the benefits of such an architecture perfectly.

We too prefer feature-based structure and organize our app this way.

Angular modules are a good fit for top-level features

The top-level directories of our app correspond with our main feature areas. Each such directory is a separate, autonomous Angular module:

  • it explicitly specifies all its dependencies (like ngRoute, ngAnimate or ngMessages)
  • it fully defines its configuration (its own angular.module().config() and angular.module().run() blocks)
  • it has its own internal routing configuration

The goal is to make modules self-contained enough so that a module could be extracted into a separate app with a minimal effort.

inside a module, we organize per technical layer

A module has the following directories:

  • separate directories for all main Angular building blocks relevant for a component-oriented app: components, services, and filters, plus an additional utils directory for plain helper functions and objects (either imported directly or injected as angular.module().constant())
  • directories related to the app configuration: config and enums
  • a spec_utils directory for spec-related helpers and custom assertions

And the following top-level files:

  • an index.config.js file with the module static config (run via angular.module().config())
  • an index.run.js file with the module runtime config (run via angular.module().run())
  • a separate index.routes.js file with module routes (technically routes are part of module static config, run via angular.module().config(), but they are conceptually separate enough from the rest of the config to justify their own file)
  • a main index.module.js file, specifying module dependencies and declaring components, services, filters and constants

Of course, not all modules must have all the files and directories I’ve described. The above list is the maximum set, but it’s perfectly possible to have modules that, for example, have no filters (and thus no filters dir) or don’t need the static or runtime configuration (and thus don’t have index.config.js and index.run.js files).

a single main module (app) aggregates all top-level modules

The topmost, app directory contains the same 4 files as modules (index.config.js, index.run.js, index.routes.js, and index.module.js). They don’t define much of their own logic, however, but act mostly as aggregators:

  • the only dependencies of an app-level index.module.js file are other modules; it never defines its own 3rd party dependencies
  • the only route an app-level index.routes.js defines is the default route (.otherwise({ redirectTo: '/some_route' })); all other routes are specified inside modules
  • usually, there’s no app-level index.run.js, and the app-level index.config.js defines mostly Angular configuration like debugEnabled, but no business-specific configuration

Also, an app directory doesn’t have other sub-directories than modules (it doesn’t have its own components, services, config and so on).

the complete app structure looks like this:

[app]
    [todos]
        [components]
        [config]
        [enums]
        [filters]
        [services]
        [spec_utils]
        [utils]     
        index.config.js
        index.module.js
        index.routes.js
        index.run.js
    [tags]
        ...
    [users]
        ...
    [auth]
        ...
    [shared]
        ...
    index.config.js
    index.module.js
    index.routes.js
    index.run.js

a special shared module (and other non-feature modules)

In the ‘complete app structure’ example above, apart from feature modules like ‘todos’ or ‘tags’ you can notice also a shared module. In this special, non-feature module, we keep code reusable among more than one module (Atomic Components, common spec utils, some universal services, and filters etc.).

The shared dir is the most common example of a non-feature module, present in practically every app, but different apps may have other similar modules. For example, in our app, we have also an env module, where we keep the configuration for various environments we deploy our app to and the logic to detect an environment.

What’s important, all the modules, both feature and non-feature ones, share the same directory and file structure and are identical from a purely technical point of view.

the “fractal” architecture

Because all modules have unified structure, are self-contained, and are designed to be aggregated at the app directory level, an interesting option opens up: we can replicate the same structure also iniside a module, like this:

[app]
    [module_with_submodules]
        [sub_module_1]
        [sub_module_2]
        ...
        [sub_module_n]
        [shared]
        index.config.js
        index.module.js
        index.routes.js
        index.run.js        
    [module_without_submodules]
        [components]
        [config]
        [enums]
        [filters]
        [services]
        [spec_utils]
        [utils]     
        index.config.js
        index.module.js
        index.routes.js
        index.run.js
    [shared]
    index.config.js
    index.module.js
    index.routes.js
    index.run.js

The example above shows only a single level of sub-modules, but we can have as many levels as we want (a sub-module can have sub-sub-modules and so on). Because of this, we call it a “fractal architecture”. Such an architecture is very flexible and allows you to divide your app any way you want.

You shouldn’t overuse this power, though. We think it is best to keep the directory structure as flat as possible, adding sub-modules only when a module becomes really unmaintainable (and even in such a case we believe you should first consider splitting the module into two or more top-level modules). We also believe, that although it is possible for a module to contain both sub-modules and low-level elements like components at the same time, a module is more maintainable if it contains either components OR submodules, never both.

A low-level view: a component structure

In the first part of the post, I’ve discussed how to split an app into modules. Modules, however, contain lower-level constructs: components, services, and filters. These lower-level constructs usually consist of several files. What’s the best way to organize those files?

components should also be self-contained (just like modules)

There are two ways to organize low-level files:

  • split them into separate directories per file type (templates, styles, specs, etc.)
  • keep all of the component files inside a component directory

We prefer the second option: keeping all the parts of a component together, in a single directory. (Of course, the same applies also to services, filters, etc.).

A fragment of such a structure can look like this:

[app]
    [todos]
        [components]
            [todo_list]
                todo_list.component.js
                todo_list.html
                todo_list.scss          
                todo_list.spec.js
            [add_todo_form]
                ...
        [services]
            [todos_notifications]
                todos_notifications.service.js
                todos_notifications.spec.js
                notification_formatter.js
        ...

Notice in the above example, how all the related files are kept together – not only the common parts of a component or a service but also all helper files like a notification_formatter.js (a plain helper function for generating properly formatted notification messages).

keeping everything together has two important benefits:

  • it’s easier to work on component (easier to find and navigate all the component-related files, less jumping between directories)
  • it’s easier to move a component to a different module, make it shared, or extract it to a different app

isolating components also at the CSS level

It’s worth noting, that the idea of self-sufficient components can be taken even further than the file and directory structure. A similar isolation can (and should) be achieved also at the CSS level, by namespacing all the CSS classes per component.

One of the most popular methodologies to achieve this – and the one we use in our project – is BEM (Block Element Modifier) methodology. Componentizing CSS is a broad topic, though, and I won’t dive into it any further in this post.

Conclusion

The project structure described in this post is flexible, clean, and searchable. After a couple of months of growing an app in this architecture, we are convinced that this is the right way to go, and we’ll definitely use similar architecture for our next project.

We’ve learned, though, that a proper balance is necessary when working with a “fractal” project structure. It’s possible to keep the structure too flat (which makes it harder to browse) as well as to make it too deep (which makes it harder to refactor).

Over to you

How do you organize your Angular (or other) apps? What do you think about the structure described above? I’d love to hear from you!

Subscribe for the rest of the “NG 1.5 from the trenches” series

If you want to get notified when the next posts in the series get published, subscribe to my blog via RSS, follow me on Twitter or subscribe below via mail:

Enter your email address to follow this blog and receive notifications of new posts by email.

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s