Welcome to Angular Corner: the first in a series of articles that explore strategic advice and practical tips for developing high-quality web applications using Angular. Today we’ll talk about conquering chaos in an Angular project structure.
Far too often, Angular projects start without any thought about the overall structure. With each user story, more and more components are heaped together. A small formless blob of components gradually grows into a gigantic one. Reuse is not taken into consideration. Ever more code is copied and pasted. Before you know it, even a small update becomes a nightmare.
But it doesn’t have to be this way.
Everything in the code should have its place and purpose. Things should be easy to find. Common problems should be solved once and for all in a reusable manner. It should be evident when changing something might break a host of other things. And updating the code should be a pleasant experience. As we’ll see, these are all readily attainable goals for Angular projects.
Let’s assume you’re starting with an Angular CLI-created project (or something similar, such as the Visual Studio Angular SPA project template). Angular CLI creates all the basic files you need to get started and provides a working app right out of the box that would easily take days to set up otherwise. You’ll start with several directories at the top, the most important of which are:
app – most of your application code goes here
assets – where static assets live (images, fonts, etc)
environments – where you define environment-specific configurations
This is already a great start. I recommend using this structure without alteration. Our focus here will be on keeping the app directory organized, since most of your code lives there.
Breaking Up the App into Modules
Modules help tame an app’s complexity by keeping local things local and exposing common code for reuse elsewhere. Modules help limit the number of objects in play in any given context, making it easier to conceptualize the software while reducing the possibility of a problem.
Angular requires a minimum of an app component inside an app module. From there, it’s up to you. So, this is the place to think hard about the best way to organize your code. The following recommendations around module organization are based on the excellent Angular Style Guide.
Most of your code will be in feature modules. These are cohesive modules that implement a single functional area of the app. You may include a security module for your login page and your password reset page. If your app is an e-commerce website, it might also include a catalog module, an orders module, and so on.
A feature module might represent a set of exported components, but the most common feature module pattern is a single exported component with a set of private components that break up the main component and make it more manageable, plus perhaps some additional services to help facilitate coordination across the components.
One major goal of good software design is to create reusable components. A set of reusable common code components is key to a successful app project. The Angular Style Guide calls for two such reusable modules: the core module and the shared module.
The shared module houses Angular components, directives, and pipes that are reused in two or more app feature modules. Successful development of the shared module is key to a successful Angular project. The shared module helps implement a common look and feel throughout the app. For example, if your app calls for buttons having a configurable icon on the right or left depending on the context, you implement that as a component in the shared module. Every feature module that uses shared components (which is most of them) imports the shared module.
Common services, however, should not be included in the shared module. Common services are put into another module: the core module. The core module should only be imported by the app module. This way, services (which are singletons) will, in fact, be singletons when lazy module loading is used. If you also happen to have any components that are solely used by the app module, they should go into the core module as well in order to keep the top-level directory uncluttered.
So far we’ve covered common components, directives, pipes, and services. But what about utilities and data models? In other words, code that doesn’t directly have to do with Angular. I prefer to put those files in the core module because it makes sense to use an existing module of common code instead of creating yet another module of common code. Utilities and data models seem to be more closely related to services than to components and you don’t need an Angular module import on utility classes and data models (just a Typescript import).
Organization within a Module
Typically, components should each have their own directory, containing the typescript, html, css, and unit test spec files. Certainly, small components are better than large ones. But it is very rare to have a component small enough that inline html is appropriate (usually it’s just for tutorial examples or unit test stubs). Normally you want to enjoy the benefits of syntax highlighting when you’re editing the html and CSS. So as the normal course of business, create a directory for each component and separate html and CSS files.
You also may want to consider grouping closely related components into their own subdirectory. When you reach ten or so components in your module, consider making a few subdirectories.
Finally, if you opt out of unit testing your Angular code, I recommend deleting the auto-generated Angular CLI spec file (or possibly rendering it ignored via the jasmine xdescribe function). An auto-generated unit test stub immediately becomes broken with the first dependency injected into the class’s constructor. If you decide to start unit testing later, it’s better to start with a clean slate rather than with a pile of broken unit tests you must first address.
One of Angular’s benefits is the componentization of CSS styles (or SASS, LESS, etc.), but this doesn’t preclude developing a common style sheet that applies to all components. Angular supports this through the styles.css (which lives in the app directory). Note however, that once an app gets large enough, styles.css might become a huge, unmanageable file. In this case, I recommend adding an app/styles directory that contains a set of smaller CSS or SCSS files. The top level styles.css or styles.scss can consist of just a few import statements. If using SASS, you might even consider following an Angularized version of the SASS 7-1 pattern.
Sometimes it’s tricky knowing when a style should be common and when it should be component-specific. Copying and pasting styles from one component to another is a sure sign those styles should be common. Also, styles related to positioning are more often component-specific (since position depends on heavily on context—but this is something worth struggling with). When you achieve the right balance of common styles, create a new component from scratch and get a solid base of styling to start with (and no unwanted baggage).
Angular also includes the concept of deep styles. A component style labeled as deep will apply to everything in the DOM. Thankfully, deep styles are now deprecated. I’ve found them to be nothing but trouble. There’s always a better way to reuse styles (usually common styles in styles.css). The typical problem scenario occurs when you visit a component with deep styles, then mysteriously start seeing visual problems on an unrelated part of the app (because the deep styles are still in effect). Avoid deep styles and try to eliminate them from your code.
It’s nearly impossible to get everything perfect the first time through and priorities often change unexpectedly. Fortunately, moving components around between modules and making subdirectories is easy and low risk. It’s reasonable to expect to spend a few hours every couple of months just keeping things organized. No matter how disorganized your Angular project is, you can always make it better. And it’s always worth the effort.
If your organization needs help increasing customer engagement by delivering adaptive, intuitive, and beautiful web interfaces your customers will love, please reach out to us. We will help you conquer the chaos of any Angular project!