Skip to content

Instantly share code, notes, and snippets.

@mrmeku
Created August 8, 2018 15:40
Show Gist options
  • Save mrmeku/7d8ed87ac1b6d4a36bb91d3d5e2581e9 to your computer and use it in GitHub Desktop.
Save mrmeku/7d8ed87ac1b6d4a36bb91d3d5e2581e9 to your computer and use it in GitHub Desktop.

Coding guidelines

This document is split into two sections

  1. On General Code Health. This section contains guidelines intended encourage patterns which produce more maintainable, less bug prone, code.
  2. On Angular Performance. This section contains guidelines intended to make an Angular app trigger change detection less often and on less entities.

General Code Health

Copy only once, extract otherwise

By the time you're copying functionality a third time, you can clearly see a useful, reusable, pattern. Its harder to see such a pattern the first time you need to duplicate functionality.

Restrict visibility as much as possible

A Public API is a contract between the author of a library and those who consume that library. The more symobls a library expose, the larger the burdon of the author to maintain particular functionality and behavoir that consumers downstream depend upon. You don't want other libraries to depend on you unless there is a really good reason why they need to, it only makes your life harder.

Enforce common code style with lint rules, not code review

Code review should be spent catching bugs in business logic, not in quibbling over patterns. A pattern needs to be picked at some point, and everyone should use the pattern to be consistent.

Libs should not import from one another (with exceptions)

When you make a change to a library, the depedency graph can be analyzed to such that other affected libraries can be computed. ng serve does this to build the minimal portion of your app that needs to be recompiled. NXs affected:test/lint commands do this to only test/lint the minimal set of files which need to be checked. Ideally, a change to one library affects no others. However, if another library were to import a symbol from one that has been altered, it would also be marked as affected by a change. Thus, we want to decouple dependencies as much as possible.

Datastructures should be flat and use readonly properties.

Often we nests objects inside one another. Sometimes those nested objects are nullable. Often, we are forced into a position where we assume that a nested object will not be null and assert that to be the case in our code. That is problematic since our compiler is no longer asserting that we have correct type safety, instead, we as the author are assuming that responsibility. The compiler is more reliable that humans in 100% of cases. We want to make code structures that let the compiler do its job more effectively and makes our code safe. Flat data structures are one way we can enable the compiler to do that. Readonly properites / constants allow the compiler to enforce that mutation has not happened.

Make your app shell as tiny as possible

One of the most impactful features a webapp can have in regards to percienved performance is a time decrease the time to first paint (first rendering of content) to be as small as possible. Thus our goal is to bundle only the minimal amount of code as is strictly needed when the app first starts. We want to authenticate the user and present them with a small shell of placeholder content that we will fill soon after by lazily loading the actual content. The smaller the shell, the quicker the start up time and the quicker the user's perceived performance.

Angular Performance

OnPush change detection

When you opt to use OnPush change detection, you make a contract with Angular: So long as the inputs to this component have not changed and it recieves no outputs from a child, nothing else will have changed. With that contract established, Angular can now skip running change detection on a compnent and, consequently, make the app feel more responsive.

Pure pipes

When you opt to mark a pipe as pure you make the following contract with Angular: This pipe has no sense of state and has no side effects. Given the same input, the output will always remain the same. With that contract established, Angular can now skip re-running a pipe when the input to that pipe has not changed.

ngForTrackBy for every for ngFor loop

When you opt to use an ngForTrackBy function, you make the following contract with Angular: If the output of this function does not change for a corresponding DOM node, that DOM node should be reused. With that contract established, Angular can now skip destoying and recreating DOM nodes as you add / remove or mutate other elements of a collection you are iterating over in a template.

| async over .subscribe

A major cause of memory leaks within Angular applications are observable subscriptions that are not unsubscribed from upon a the destruction of the components which subscribed in the first place. Angular's async pipe automates the process of cleaning up these subscriptions.

ngIfAs over multiple | async subscriptions

The more we can limit the number of observable subscriptions, the better performance will be. Here, we can leverage ngIf to cache a subscription value to be made use of in multiple places within a template.

Multicasting to share a subscription

Every time an observable goes through a piped operator, there is an assoicated cost. Data is transformed from one form to another, HTTP calls are made to fetch data, things happen upon subscription that do not come for free. These costly operations should be formed as little times as we can help it. Thus, it is often useful to cache the result of an observable such that if there is more than one subjscriber, the costly operations still only happen once. This strategy of caching a prior result for other subscribers to use is called multicasting. It can be accomplished by using the publishLatest operator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment