This document is split into two sections
- On General Code Health. This section contains guidelines intended encourage patterns which produce more maintainable, less bug prone, code.
- On Angular Performance. This section contains guidelines intended to make an Angular app trigger change detection less often and on less entities.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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