Skip to main content

Angular

This section contains coding standards for Angular projects.

These are additional standards

Please also follow other coding standards for consistent Angular code.

Project architecture

Use a monorepo

Use a monorepo to manage your Angular projects. This allows you to share code between projects, and makes it easier to manage dependencies.

Use abstraction layers

Use abstraction layers to separate concerns. This makes your code more maintainable and testable. It also makes it easier to replace parts of your codebase.

Minimize bundle size

For an introduction on how to minimize your Angular bundle size, check out this video.

Analyze your bundle

Periodically check the bundle size of your application. You can analyze your Angular bundle using 3rd party plugins. One of those is source-map-explorer.

Use tree-shakeable libraries

Tree-shaking removes unused code from your bundles. Not all libraries are tree-shakeable. Only use libraries that are capable of tree-shaking.

Lazy load images

Use the NgOptimizedImage directive to optimize using images in your application.

Routing

Lazy load modules

Lazy-load feature modules in your router (docs):

const routes: Routes = [
{
path: 'items',
loadChildren: () => import('./items/items.module').then((m) => m.ItemsModule),
},
];

Lazy load components

Lazy-load components in your routes:

const routes = [
{
path: 'search',
loadComponent: () => import('../search').then((m) => m.SearchComponent),
},
];

Don't use route resolvers

Resolvers are a way to fetch data before a route is activated. This blocks the route from activating until the data is fetched. This is an anti-pattern, especially in Angular and reactive programming.

You should always eagerly load components, and load your data in the correct lifecycle methods of your component.

This way, the user can navigate to the route immediately, and data will be fetched in the background, allowing you to easily show a loading indicator, skeleton loaders, or other UX patterns.

When to use resolvers

You should only use resolvers when you can't fetch data in the component itself. These use-cases are very rare.

Change detection

Angular has two modes for change detection: Default and OnPush. Components that implement the Default strategy re-render when a change detection (CD) cycle runs. In OnPush strategy, change detection only propagates up the view tree from the current view to the topmost. Check out this page for more information.

Use OnPush change detection strategy by default

To prevent unnecessary rendering of your components, all your components should use the OnPush strategy for ChangeDetectionStrategy by default. You do this by adding the property to the @Component decorator for your component:

@Component({
// ...other properties
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MyComponent {}

Using this strategy, the component will trigger its change detection in the following cases:

  • Whenever the value for an @Input changes
  • When events that you subscribe to using () in your template, or @HostListener in your code happen
  • When change detection is triggered manually, using the detectChanges() method of the ChangeDetectorRef

Schematics

You can setup your project to use the OnPush strategy as default when generating components through CLI commands. Add the following to your angular.json or project.json files:

{
"schematics": {
"@schematics/angular:component": {
"changeDetection": "OnPush"
}
}
}

Find more in depth information about this topic in this article.

Run code outside NgZone

For performance sensitive actions, like handling events that trigger rapidly (like mousemove and scroll), consider running those operations outside the NgZone. Changes outside the zone will not trigger ticks in the change detection mechanism.

Find more information in this article.

Reactivity

Prefer observables over promises

A Promise can not be cancelled, and you can not easily repeat or retry them. Using promises instead of observables should be considered an anti-pattern in an Angular context. RxJS is deeply integrated in Angular and you should be (or become) familiar with working with observable streams, anyway.

Use pure functions and pipes

Use pure pipes that can be memoized. They don’t re-render if the input params have not changed.

Styles

Use scoped styles

Use view encapsulation by default. This keeps your styling scoped to the specific component, prevents style leaking to other elements, and allows you to keep your selectors short.

Don't use deprecated selectors

Don't use ::ng-deep or /deep/ to target elements inside or outside of your component. These are deprecated and will be removed in the future.

Keep import paths short

To be able to easily use your shared SCSS resources, you can add include paths to your project.json file. In this example, we have a shared library that has some mixins we want to import in our project.

Normally, we would import styles using relative paths. Alternatively, add the following to your path-to-app/project.json file:

{
"build": {
"stylePreprocessorOptions": {
// can contain multiple import paths
"includePaths": ["libs/path-to-library/src/styles"]
}
}
}

Now when we want to use any file inside that folder, we can use this import statement:

/* 🚫 Ugly long relative import path */
@use '../../../libs/path-to-library/src/styles/reset.scss';
@use '../../../libs/path-to-library/src/styles/mixins.scss';

/* ✅ Awesome short import path! */
@use 'reset';
@use 'mixins';