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()
.