To have a common terminology I list some of the used terms here and explain their meaning and usage.
Used terms:
- Asynchronous/Observable Primitive
- Reactive Primitives
- Primitive Reactive Problems
- Bind
- Connect
- Side-effect
- Hold
- Coalescing of Work
Asynchronous primitives are either ECMAScript asynchronous types as e.g. Promises
, setInterval
, etc.
Or RxJS creation functions or operators that are observable but pushed values can also be synchronous.
Reactive primitives are pieces of logic in Angular that easily enables reactive programming. The term primitives are important here. Developers or library authors need to have the problems that those primitives solve in an isolated manner. Also, different parts of the needed mechanisms as state composition, rendering, and behavior have their dedicated place to sit. E.g. triggering change detection and therefore rendering should happen in the template. This gives a good idea of what will trigger rendering and may need to get some rate-limiting. State composition should happen in the service layer and derivation in the component.
This helps developers in using exactly what they need and have the features still composable. On top of it, we can
In reactive programming, there are some things that you need to do nearly always in your code. Those things have their scope and by design, RxJS has separate building blocks and separated responsibilities. In the context of reactive Angular, those things also exist in a certain way. This is meant by primitive reactive problems, separated processes not interfering with each other solving specific reactive problems.
Problems:
- Retrieve asynchronous primitives
- Subscription handling
- Connecting observables and values
- State composition
- State derivation
- Change detection and rendering
- Hold/Maintain side-effects Problems included in the primitives but up to the developer:
- Processing of observables
It's important to ensure that 'Reactive Primitives' only solve 'Primitive Reactive Problems' to have them flexible and composable as well as ensure the 'Reactive Primitives' can get reused to build other more complex things.
When "bind" is used in this document is means that some values are bound to a specific variable, class property or input binding or template context.
This is very similar to the meaning of the bind()
method of a function fn.bind(anyScope)
.
This "binds" the passed value as it's context (this
).
Examples for where the word bind is used are listed in the following:
Bind a stream of values to a template binding:
<component [inputBinding]="singleValue"></component>
<component [inputBinding]="observable$ | async"></component>
<component [inputBinding]="observable$ | ngrxPush"></component>
Bind a stream of values to a template context:
<!-- No CD involved -->
{{singleValue}}
<!-- CD involved -->
{{observable$ | async}}
<!-- CD involved -->
{{observable$ | ngrxPush}}
<!-- CD involved -->
<ng-container *ngIf="observable$ | async as observedValue">
{{observedValue}}
</ng-container>
<!-- CD involved -->
<component *ngIf="observable$ | ngrxPush as observedValue">
{{observedValue}}
</component>
<!-- CD involved -->
<ng-container *ngrxLet="observable$ as observedValue">
{{observedValue}}
</ng-container>
Bind a stream of values to a class property:
class AnyClass {
// CD involved
prop1 = mikeRyansConnect(observable$);
constructor() {
michaelsHladkysHold(
observable$,
(observedValue) => {
// Binding happens here.
// No CD involved
this.prop2 = observedValue
}
)
}
}
When "connect" is used in this document is means that some Observable it connected to another observable in a subscription less manner.
Technically this is done over any of the All
operators.
The essential thing here is that connect sustains reactivity. No, subscribe happened and the observed values can still be composed. (e.g. derivations of the managed state object or created action observable)
Examples for where the word connect is used are listed in the following:
class AnyClass {
constructor() {
michaelsHladkysConnectState(
observable$.pipe(map(value => ({key: value})))
);
michaelsHladkysConnectState(
'key', observable$
);
potentialFutureNgRxStoreConnectAction(
observable$.pipe(map(value => createAction({prop: value})))
);
potentialFutureNgRxStoreConnectAction(
observable$, value => fetchAction({prop: value})
);
}
}
When "side-effect" is used in this document is means that some logic that is NOT related to any rendered state needs to get run as a side-effect.
The essential thing here is the computed values or applied behavior is NOT related to any rendered state directly. Side-effects ALWAYS have a start and end, and are always owned by some context/component and are tied to the owner's lifetime. (e.g. an interval that interacts with other parts of the application)
Examples for where the word side-effect is used are listed in the following:
function doStuff(value): void {
some.logic.execute(value)
}
When "hold" is used in this document is means that some context (class, function scope, etc.) "holds" a side-effect and starts it by subscribing to it.
The essential thing here is that hold helps to make clear who the owner of the side effect is. And also unsubscribes from the side-effect if the owner gets destroyed. (e.g. continues processing of some background task like firing some action)
Examples for where the word hold is used are listed in the following:
class AnyClass {
constructor() {
michaelsHladkysHold(
observable$.pipe(
// side-effect
tap(value => this.ngRxStore.dispatch(fetchAction({prop: value})))
)
);
michaelsHladkysHold(
observable$,
// side-effect
value => this.ngRxStore.dispatch(fetchAction({prop: value}))
);
}
}
This term arises from messaging systems or event-driven applications. Also, other areas of IT deal with such scenarios or problems.
To give a practical example of the frontend, imagine you have a rendered thing that relies on 2 data sources to render. IF the 2 sources would change synchrounouse, or as micro-task, this would lead to 2 render template calls in the same browser tick. To avoid this you can coalesce the changes into one and only render one time for the 2 changes.
Another real-life example is angulars Event-Coalescing in Ivy mode. Imagine an application just containing a button. A click on this button would schedule 2 render template calls. This occurs through event bubbling and the way how zone.js and manages asynchronicity.
Angular coalesce those 2 events within an animation frame of the browser. It basically delays the execution of the micro-task stack of the browser.
The sync-task stack (and also the micro-task stack) coalesces changes,
the micro-task stack picks them up and schedules the related work.
Work is in this case the ApplicationRef.tick
See images of diagrams under Images Coalescing of Work