Angular Opinionated Guide

Angular Opinionated Guide

This guide extends without overriding the official Angular guide as well as following the same style vocabulary.

Take a look at this example repository to see the following guidelines and more in action.

Without further introduction, let’s dive in!

Components

Encapsulate dependencies

Do create a @NgModule() for each component.

Do place the component module in the same-named folder as the component.

Why? By encapsulating their dependencies in a module, each component can be used and tested independently.

Why? Component dependencies can be replaced and removed without affecting other components.

Why? Components can easily be moved around with their dependencies.

Control change detection implicitly

Do use the OnPush change detection strategy.

Why? Enforces good practices such as immutability.

Why? Improves performance.

Do let Angular control change detection implicitly using @Inputs() reference changes, @Outputs() emitted events, and the async pipe.

Avoid triggering change detection explicitly using ApplicationRef.tick(), ChangeDetectorRef.detectChanges(), NgZone or any other manual methods.

Do mark object properties as readonly to enforce immutability.

Why? OnPush change detection does not work with mutable objects.

Separate between presentational and container components

Do separate components into presentational and container components (aka. smart and dumb components).

Presentational components

Expose data structure through a model

Do create a model for each presentational component.

Do place the component module in the same-named folder as the component.

Do match the name of the model with its component and remove the Component suffix.

Why? Models shared by multiples components lead to mixed dependencies and eventual breaking changes.

Why? By exposing their own data structures, components are unlikely to be affected by outside changes.

Delegate to the parent container

Avoid calculated properties in component models. For example, if a component displays a hero real name (aka. alter ego), its model should contain a property name instead of having two properties firstName and lastName.

Avoid injecting store/services into presentational components.

Why? presentational components are responsible for displaying data. Any other logic is handled by container components.

Communicate through inputs and outputs

Do communicate with presentational components through @Inputs() and @Outputs().

Avoid communicating with presentational components through @ViewChild(), @ViewChildren() or any equivalent methods.

Split into child components

Do split large presentational components into smaller ones.

Do place child components directly under their parent.

Consider composing the component model with models from child presentational components.

Container components

Interact with the store/service

Do interact with the store (or service if no state management) in container components.

Do pass data returned by the store (or service if no state management into child presentational components using the async pipe.

Why? Hides Observables<> complexity from presentational components.

Do handle events emitted by presentational components to dispatch actions (or invoke service functions if no state management).

Map to presentational models

Do map types returned by the store/service into models exposed by child presentational components from inside the container component.

Why? This way, presentational models and store models are completely independent.

Pre-load the store with guards

Do Pre-load the store by dispatching load actions from a guard.

Avoid dispatching actions from ngOnInit().

Why? Guards open the possibility to wait until the data is loaded before navigating.

Why? Adding routing dependencies to components makes them harder to test.

Models

Use functionless interfaces

Do define functionless models using interfaces.

Why? Functions cannot be serialized/deserialized meaning that all functions from types coming out of the store or an HTTP request will be undefined.

Why? Functional programming encourages separating the data from the behavior.

RxJS

Prefer pure operators

Avoid unwrapping Observables<>.

Do prefer pure RxJS operators.

Why? Side effects make the code harder to read and debug.

Unsubscribe

Avoid subscribing as much as possible using the async pipe or by returning an Observable<>.

Do unsubscribe using take(1) and/or takeUntil() after each subscribe().

Senior software developer, independent consultant and blogger