HackMD
  • Beta
    Beta  Get a sneak peek of HackMD’s new design
    Turn on the feature preview and give us feedback.
    Go → Got it
      • Create new note
      • Create a note from template
    • Beta  Get a sneak peek of HackMD’s new design
      Beta  Get a sneak peek of HackMD’s new design
      Turn on the feature preview and give us feedback.
      Go → Got it
      • Sharing Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • More (Comment, Invitee)
      • Publishing
        Please check the box to agree to the Community Guidelines.
        Everyone on the web can find and read all notes of this public team.
        After the note is published, everyone on the web can find and read this note.
        See all published notes on profile page.
      • Commenting Enable
        Disabled Forbidden Owners Signed-in users Everyone
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Invitee
      • No invitee
      • Options
      • Versions and GitHub Sync
      • Transfer ownership
      • Delete this note
      • Template
      • Save as template
      • Insert from template
      • Export
      • Dropbox
      • Google Drive Export to Google Drive
      • Gist
      • Import
      • Dropbox
      • Google Drive Import from Google Drive
      • Gist
      • Clipboard
      • Download
      • Markdown
      • HTML
      • Raw HTML
    Menu Sharing Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Versions and GitHub Sync Transfer ownership Delete this note
    Export
    Dropbox Google Drive Export to Google Drive Gist
    Import
    Dropbox Google Drive Import from Google Drive Gist Clipboard
    Download
    Markdown HTML Raw HTML
    Back
    Sharing
    Sharing Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Note Permission
    Read
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Write
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    More (Comment, Invitee)
    Publishing
    Please check the box to agree to the Community Guidelines.
    Everyone on the web can find and read all notes of this public team.
    After the note is published, everyone on the web can find and read this note.
    See all published notes on profile page.
    More (Comment, Invitee)
    Commenting Enable
    Disabled Forbidden Owners Signed-in users Everyone
    Permission
    Owners
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Invitee
    No invitee
       owned this note    owned this note      
    Published Linked with GitHub
    Like BookmarkBookmarked
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Design Doc - Component Render Strategies author: [Michael Hladky] created: 2020-02-03 status: [draft | in review | approved | **implemented** | obsolete] doc url: https://hackmd.io/42oAMrzYReizA65AwQ7ZlQ feedback url: https://github.com/ngrx/platform/issues/2441 design doc template version: 1.0.0 ## Objective <!-- A rough overall explanation of the idea as well as related terms and a quick scatch of the need. --> The goal of this document is to implement an efficient way to coalesce renderings of the view in applications. This is especially of help, in zone-less Angular applications that trigger rendering manually, but also enables us to get zone-less performance in zone-full environments. > As this document uses specific terminology find detailed explanations in the gist here: [Reactive Angular Terminology](https://gist.github.com/BioPhoton/e8e650dc3b8a7798d09d3a66916bbe10). Coalescing means buffering a set of emissions into a smaller set of those emissions. [Here a general explaination](https://gist.github.com/BioPhoton/e8e650dc3b8a7798d09d3a66916bbe10#coalescing-of-work). In the documents manner, it always coalesces all emissions to the last emission of a tick in the event loop scheduled as a microtask. A more technical explanation of the behavior would be very similar to the already existing [RxJS rate-limiting operators](https://rxjs.dev/api/operators/throttle#see-also). Here some [diagrams](https://twitter.com/Michael_Hladky/status/1119277221087150081) so "see" the difference in behavior. Angular itself has implemented several things to enable coalescing. - Flags for ngZoneEventCoalescing - scheduleTick called from ApplicationRef.tick() - scheduleDetectChanges called in elements Those mostly target rendering. Some general things where this solution can be useful are: - state derivations - state updates - rendering - event computations The general goals are: - to create a coalescing logic that improves the performance of reactive and/or zone-less applications. - It should be flexible to configure and easy to use. The technical goal is to create an RxJS operator for a configurable coalescing strategy: - acts as a filter/throttle operator for emitted values. The actual work is done with the coalesced result - enables execution-context scoping (animationFrame, Promise, setTimeout) - enables usage-context scoping (window, Class, component instance, LEmbeddedView) The real challenge here is to find the right usage-context for scoping. Here an [collection/explaintion of thoughts](https://docs.google.com/presentation/d/1QBx3nTyDpQvmNnN5NNV6apJO70rSN6lxCKS-OmPPPA8/edit#slide=id.g6e7b315996_0_226) and some edge cases. ### Expected User/Developer Experience <!-- Describe what will the user be able to do and how will they be able to do it thanks to this new feature. This should be done in a story format, described from the user’s/developer’s perspective, with as many details as possible but without going into the implementation details. --> The developer of a reactive application knows exactly when to rerener. Currently Angular takes over this decision by relying on the zone mechanism only. With this feature the developer would be able to use pipes, directives in the template or operators in the class to triger rendering. With the provided strategies the developers are able to specify how they want to render and which performance optimisations should br used. As there is quite a bit knowledge needed to use the right combintions the strategies are designed to provide an intuitively names set of strategies that ease this decision and take away the in-depth understanding. This enables developer to get zone-less performance even within zonefull applications. ### Background <!-- Stuff one needs to know to understand this doc: motivating examples, previous versions, and problems, links to related projects/design docs, etc. You should mention related work if applicable. This is background; do not write about ideas to solve problems here. --> Relevant material to read first: - [Angular ComponentView and EmbeddedView in Ivy ](https://hackmd.io/X1wy5Y1TSIWibaHiaAHyBA?view) In: Hackmd, Michael Hladky. ### Prior Art To implement the described feature 3 sections need to be understood: - Change detection in Angular - Render methods of Angular - The coalescing mechanism in general - Coalescing and Render Methods in Agnular #### Change Detection in Angular - Default, - OnPush, dirty flagging - Zone, #### Render Methods and Behaviour This information is needed to get an overview of Angulars render methods and it'S differences. The tables describe the following: - Render Method: Name and location of the methods responsible for rendering - Renders: Position in the component tree from where it will render. The app means from the root of the tree, component means from the component on which the method is called downwards. - ZoneLess: If a zone environment is needed. More specifically is the token `ngZone` is derived from `NgZone` or `NgNoopZone`. - Coalesce Scope: Angular v8 and v9 both have variations where coalescing is present and scoped in ApplicationRef - Execution: Tells how the method is executed. e.g. microtask **>=ViewEngine** | Render Method | Renders | | ------------------ | -------- | | cdRef.markForCheck | App | | markForCheck+tick | App | | cdRef.detectChange | Children | **Ivy** | Render Method | Renders | | ------------------ | -------- | | ɵmarkDirty | App | | ɵdetectChange | Children | An interesting thing is `ChangeDetectorRef#markForCheck`. Internally it runs `ApplicationRef#tick` to render the app. It is triggered by logic related to `ngZone`. This menas dirty flagging of a compoent and rendering can be used independent from each other in `NgNoopZone` environments. #### The Coalescing Mechanism in General To understand this section we split it into 2 parts. - Coalescing in general - Scoped Coalescing **General coalescing** The basic idea of coalescing and also the current implementation in Angular holds a global flag that determines if synchrounous code is running and schedules a micro or macro task that runs some logic related to rendering after all the sync cond is executed. Primitive implementation: ```typescript export function getCoalesceFn() { const isCoalescing = false; return (work: () => void) => { // If work is already scheduled to do nothing if (isCoalescing) { return; } // If NO work is scheduled // request a new animationFrame and set isCoalescing to a truthy value isCoalescing = executionContext(() => { // Reset requestAnimationFrameId isCoalescing = false; // Logic here will get buffered in the micro task queue and executed only ones work(); }); } } // Example: const doCoalesce = getCoalesceFn(); doCoalesce(() => console.log(('1')); doCoalesce(() => console.log(('2')); doCoalesce(() => console.log(('3')); doCoalesce(() => console.log(('4')); // 4 ``` **Scoping** If we think a step further, we can see that this implementation includes a sort of scope in which the coalescing is active. In the abofe implementation it was global. e.g. scoped by the window object. On a component level the will lead to situations where only the first compoent will get rerendered, others not as the scoping does not know aboul components. This leads to the need of a local scope. coalescing should be possible on component level. To enable that we need to be able to share a scope tied to the components view. A promitive solution of a shared scope can look like that: ```typescript export interface CoalesceConfig { context?: {isCoalescing: boolean}; } export function getCoalesceFn(cfg: CoalesceConfig = {isCoalescing: boolean} ) { return (work: () => void) => { if (cfg.isCoalescing) { return; } cfg.isCoalescing = executionContext(() => { cfg.isCoalescing = false; work(); }); } } // Example: const scope1 = {isCoalescing: false}; const doCoalesce1 = getCoalesceFn(scope1); const scope2 = {isCoalescing: false}; const doCoalesce2 = getCoalesceFn(scope2); doCoalesce1(() => console.log(('1')); doCoalesce1(() => console.log(('2')); doCoalesce2(() => console.log(('3')); doCoalesce2(() => console.log(('4')); // 2, 4 ``` As this lib targets mainly reactive code we would need to put this logic into an RxJS operator. To get a better idea I put some psudo code of how it could look. RxJS Operator Version: ```typescript // Basic behaviour from([1,2,3,4]).pipe( coalesce(cfg), ).subscribe(console.log); // 4 // Real-life usage stateChanges$.pipe( tap(v => updateModel(v)), coalesce(cfg), tap(_ => renderView()) ).subscribe(); ``` ### Coalescing and Render Methods in Agnular The tables describe the following: - Render Method: Name and location of the methods responsible for rendering - Renders: Position in the component tree from where it will render. The app means from the root of the tree, component means from the component on which the method is called downwards. - ZoneLess: If a zone environment is needed. More specifically is the token `ngZone` is derived from `NgZone` or `NgNoopZone`. - Coalesce Scope: Angular v8 and v9 both have variations where coalescing is present and scoped in ApplicationRef - Execution: Tells how the method is executed. e.g. microtask **>=ViewEngine** | Render Method | Renders | Zone Type | Coalesce Scope | Execution | | ------------------ | -------- | ---------- | -------------- | ---------- | | cdRef.markForCheck | App | NgZone | ApplicationRef | Micro Taks | | markForCheck+tick | App | NgNoopZone | ApplicationRef | Micro Taks | | cdRef.detectChange | Children | NgNoopZone | None | Sync Taks | **Ivy** | Render Method | Renders | Zone Type | Coalesce Scope | Execution | | ------------------ | -------- | ---------- | -------------- | -----------| | ɵmarkDirty | App | NgNoopZone | ApplicationRef | Micro Taks | | ɵdetectChange | Children | NgNoopZone | None | Sync Taks | Especially interesting are the columns ZoneLess and Execution. Zone Type is relevant for migrations to a zone-less environment. Execution is very important in terms of custom coalescing strategies, as the could influence each other badly if both are used together. Performance Features and Incompatibilities Due to some incompatibilities or the combinations are just useless not all Angular built in features can be combined with the ngrx/component performace features. **Working Combinations:** - \>=ViewEngine | | Render Method | Coalescing | Coalesce Scope | | -------- | -------------------| ---------- | -------------- | | ZoneFull | cdRef.markForCheck | ❌ | None | | ZoneLess | markForCheck+tick | ❌ | None | | ZoneLess | cdRef.detectChange | ❌ | None | | ZoneLess | cdRef.detectChange | ✔️ | None | | ZoneLess | cdRef.detectChange | ✔️ | Compoennt | - Ivy | | Render Method | Coalescing | Coalesce Scope | | -------- | -------------------| ---------- | -------------- | | ZoneFull | ɵmarkDirty | ❌ | None | | ZoneLess | ɵmarkDirty | ❌ | None | | ZoneLess | ɵdetectChange | ✔️ | None | | ZoneLess | ɵdetectChange | ✔️ | LView | ## Strategy Design ### Cases to Consider - **Push Pipe** - 01. One single-shot observable bound by one ngrxPush as template expression ```htmlmixed {{sync1$ | ngrxPush}} ``` - 02. One single-shot observable bound by multiple ngrxPush as template expression ```htmlmixed {{sync1$ | ngrxPush}} {{sync1$ | ngrxPush}} {{sync1$ | ngrxPush}} ``` - 03. Multiple single-shot observables bound by multiple ngrxPush as template expression ```htmlmixed {{sync1$ | ngrxPush}} {{sync2$ | ngrxPush}} {{sync3$ | ngrxPush}} ``` - 04. One sync multi-shot observables bound by multiple ngrxPush as template expression ```htmlmixed {{syncArray1$ | ngrxPush}} ``` - 05. One sync multi-shot observables bound by multiple ngrxPush as input binding ```htmlmixed <child1 [value]="sync1$ | ngrxPush"></child> ``` <app-push-child05 [value]="value1$ | ngrxPush: cfg"></app-push-child05> - 11. One single-shot observable bound by one ngrxPush as input binding ```htmlmixed <child1 [value]="sync1$ | ngrxPush"></child> ``` - 12. One single-shot observable passed directly to input binding rendered over ngrxPush ```htmlmixed <child [value]="sync1$"></child> ``` - 13. One single-shot observable bound by multiple ngrxPush as input binding ```htmlmixed <child [value]="sync1$ | ngrxPush"></child> <child [value]="sync1$ | ngrxPush"></child> <child [value]="sync1$ | ngrxPush"></child> ``` - 14. Multiple single-shot observables bound by multiple ngrxPush as input binding ```htmlmixed <child [value]="sync1$ | ngrxPush"></child> <child [value]="sync2$ | ngrxPush"></child> <child [value]="sync3$ | ngrxPush"></child> ``` - 21. One single-shot observable bound by one ngrxPush as input binding. The nested components uses ngrxPush to render changes. ```htmlmixed <child [value]="sync1$ | ngrxPush"> <!-- Internal Template Start --> {{sync1$ | ngrxPush}} <!-- Internal Template End --> </child> ``` - **Let Directive** - 01. One single-shot observable bound by one ngrxLet as input binding with as syntax ```htmlmixed <ng-container *ngrxLet="sync1$ as sync1">{{sync1}}</ng-container> ``` - 02. One single-shot observables bound by multiple ngrxLet as input binding with as syntax ```htmlmixed <ng-container *ngrxLet="sync1$ as sync1">{{sync1}}</ng-container> <ng-container *ngrxLet="sync1$ as sync1">{{sync1}}</ng-container> <ng-container *ngrxLet="sync1$ as sync1">{{sync1}}</ng-container> ``` - 03. Multiple single-shot observables bound by multiple ngrxLet as input binding with as syntax ```htmlmixed <ng-container *ngrxLet="sync1$ as sync1">{{sync1}}</ng-container> <ng-container *ngrxLet="sync2$ as sync2">{{sync2}}</ng-container> <ng-container *ngrxLet="sync3$ as sync3">{{sync3}}</ng-container> ``` - 11. One single-shot observable bound by one ngrxLet as input binding with let syntax ```htmlmixed <ng-container *ngrxLet="sync1$; let sync1">{{sync1}}</ng-container> ``` - 12. One single-shot observables bound by multiple ngrxLet as input binding with let syntax ```htmlmixed <ng-container *ngrxLet="sync1$; let sync1">{{sync1}}</ng-container> <ng-container *ngrxLet="sync1$; let sync1">{{sync1}}</ng-container> <ng-container *ngrxLet="sync1$; let sync1">{{sync1}}</ng-container> ``` - **TemplateRef injection** @TODO consider more edge cases - 01. @TODO inters text https://ng-run.com/edit/vbcTXFp9cVQtsigapDs2?open=app%2Fapp.component.html&aot=true ```htmlmixed <insertion [template]="ref"> <!-- Internal Template Start --> <ng-container [ngTemplateOutlet]="template"> </ng-container> <!-- Internal Template End --> </insertion> <ng-template #ref> <span>{{obs$ | push}}</span> </ng-template> ``` - **Push Pipe and Let Directive Mixed Setup** @TODO consider more edge cases - 01. One single-shot observable bound by one ngrxPush and one ngrxLet as input binding ```htmlmixed <ng-container *ngrxLet="sync1$ as sync1">{{sync1}}</ng-container> <child [value]="sync1$ | ngrxPush"></child> ``` - 02. Kitchensink :D ```htmlmixed {{nums1$ | ngrxPush}} <child [value]="nums1$ | ngrxPush"></child> <p *ngFor="let num of nums1$ | ngrxPush">{{num}}</p> <ng-container *ngIf="nums1$ | ngrxPush as sync1">{{sync1 | json}}</ng-container> <ng-container *ngrxLet="nums1$ as sync1">{{sync1 | json}}</ng-container> ``` - **Push Pipe and Let Directive and Class Based Change Detection** @TODO consider more edge cases - 01. One single-shot observable bound by one ngrxPush and one ngrxLet as input binding and in the component there is CD ```typescript sync1$.pipe( tap(() => detetChange(this)) ) .subscribe(); ``` ```htmlmixed <ng-container *ngrxLet="sync1$ as sync1">{{sync1}}</ng-container> <child [value]="sync1$ | ngrxPush"></child> ``` ### Strategies A strategy is considered as a composition of render methods of the used engine and the additional performance features. Factors impacting the performance are: - the render method - the coalescing feature - the coalesce scope ![](https://i.imgur.com/qYvDXJV.png) **@TODO this section needs to check-in form of code and working examples** Another relevant information is that Angular's coalescing features - markForCheck => @TODO name the exact method and execution context - markDirty => @TODO name the exact method and execution context are compatible with the new coalescing feature. More specific to the actual duratonSelector (the unpatched animationFrame). The strategy names are related to their impact and also serve as a roadmap like listing. **What about:** - VE/I - Options for ViewEngine / Ivy - mFC - `cdRef.markForCheck` - dC - `cdRef.detectChanges` - ɵMD - `ɵmarkDirty` - ɵDC - `ɵdetectChanges` - LV - `LView` - C - `Component` | Name | ZoneLess VE/I | Render Method VE/I | Coalescing VE/I | |-------------| --------------- | ------------------------ | ----------------- | | `asyncLike` | ❌/❌ | mFC / mFC | ❌ | | `global` | ❌/✔️ | mFC / ɵMD | ❌ | | `local` | ✔️/✔️ | dC / ɵDC | ✔️ + C/ LV | | `noop` | ❌/❌ | no rendering | ❌ | **asyncLike** This strategy is the drop-in replacement for Angular's built-in `async` pipe. This is the only strategy that **does not also work in zone-less environments**. **global** **local** **noop** This strategy does nothing but asignin the values to the view context. Handy for debugging. ## Prototype Scoping by LView: ![](https://i.imgur.com/YZgyMR4.png) ![](https://i.imgur.com/LhW2ZiE.png) ![](https://i.imgur.com/JL97WIF.png) ### Minimal Design <!-- Include only the essential parts of the code. No error handling, no performance consideration. This section should help to understand the essential implementation in one small piece of code --> Created a configurable RxJS operator to coalesce the emissions. Roughly speaking, this operator should act as a rate-limiting operator on the event-loop level. Buffer all synchronous emitted values and send the last of them as the first microtask. **Usage in the Push Pipe** ```typescript // Implementation @Pipe({ name: 'ngrxPush', pure: false }) export class PushPipe implements PipeTransform { private renderedValue?: unknown; private subscription: Subscription = new Subscription(); constructor(protected cdRef: ChangeDetectorRef) { } transform<T>(obs: Observable<T> | Promise<T> | null | undefined): T | null { // Use unpatched verson of animation frame const durationSelector = () => generateFrames( (window as any).__zone_symbol__requestAnimationFrame, (window as any).__zone_symbol__cancelAnimationFrame ); const config = {context: this.cdRef['_lView'] as any}; this.subscription.add(obs.pipe( coalesce(durationSelector, config), tap({ next(valueToRender: unknown) { this.renderedValue = valueToRender; this.cdRef.detectChanges(); } }) ) .subscribe()); return this.renderedValue; } ngOnDestroy() { this.subscription.unsubscribe(); } } ``` ```htmlmixed <!-- Real-life usage --> <!-- Coalescing Context Off --> Renders component 3 times: {{value | ngrxPush}} {{value | ngrxPush}} {{value | ngrxPush}} <!-- Coalescing Context On --> Renders component 1 time: {{value | ngrxPush}} {{value | ngrxPush}} {{value | ngrxPush}} ``` ### Detailed Design <!-- Include some important cases. Error handling, and consider how to deal with e.g. performance. This section should help to understand the tricky implementations in more detail. Don't take too much attention to typing if it bloats the code too much. --> ```typescript export interface CoalescingContext { isCoalescing: any | undefined; } export interface CoalesceConfig { context?: CoalescingContext; leading?: boolean; trailing?: boolean; } const cfg: CoalesceConfig = { context: LComponentView; executionContextRef: (window as unknown).__zone_symbol__requestAnimationFrame; }; // @TODO put suggestion for the strategy pattern ``` ### Caveats <!-- You may need to describe what you did not do or why simpler approaches don't work. Mention other things to watch out for (if any). Security Considerations How you’ll be secure. Considerations should include sanitization, escaping, existing security protocols and web standards, permission, user privacy, etc. None --> **Coalescing context in viewing** The actual strategy for coalescing within a context is only working in ivy. **As any context object can be passed and will be patched** We have to apply the flag `isCoalescing` over a symbol to the context avoid conflicts with other present properties. **Used inside a zone.js patched environment** ```typescript // Unpatched requestAnimationFrame reference window.__zone_symbol__requestAnimationFrame; ``` ### Performance Considerations <!-- Try to describe under which conditions the suggested solution is performant and which factors play the key role when starting to get bad performance. Describe a specific situation in which we can run into performance problems If possible provide a POC or a theoretical explanation of a possible solution. --> As this design doc is focusing only on performance this section is not applicable. ## Documentation Plan <!-- Try to describe the important parts of the implementation and how to documented it e.g. importance, a priority by relevance for user, level of detail, example needed. --> As the scope of this part of the package is quite small I consider the documentation in text and some examples in the demo app and as code snippets is everything we need. Following operators needed to get documented: - generateFrames - coalesce - detectChanges - markDirty - markForCheck ### Style Guide Impact <!-- Does the documentation influence the way the current style guide is structured? Also, does the new documentation introduces any technical implementations of the docs? If so please name them and give a detailed description of the impact and if possible some POCs. --> The needed documentation has no impact on the current way the documentation is maintained. ## Developer Impact ### Public API Surface Impact <!-- Are you adding new public APIs? If so, what's the change? (method signature changes, new classes/interfaces, new npm packages, config file properties or new config files altogether) Are the new APIs consistent with the existing APIs? Do they follow naming conventions and use correct terminology? --> The new behavioral RxJS operator does not affect the existing API interface. It will get introduced under a new package `@ngrx/component`. It exposes only the operator and needed interfaces and types. Following dependencies are needed: - @rxjs@>=6.5.4 The new operator is named `coalesce`. It is used the same way as any other RxJS operator is used `o$.pipe(coalesce(cfg))`. The term 'coalesce' is used to refer to its direct meaning. 'Notifications' refers to [RxJS Notification](https://github.com/ReactiveX/rxjs/blob/388c4852948660abcff22d7b82ccb0a29c77428c/src/internal/Notification.ts#L31) and 'ExecutionContext' refers to the task stacks in the event loop (sync, micro, macro) This tries to give the user an intuitive understanding of its behavior out of the operators' name itself. ### Developer Experience Impact <!-- How will this change impact developer experience? Are we adding new tools developers need to learn? Are we asking developers to change their workflows in any way? Are we placing any new constraints on how the developer should write the code to be compatible with this change? --> This has no impact on a user's workflow but can increase performance and enables them to run zone-less. If the users decide to disable zone he may need to get more familiar with RxJS operators. Other than this the user does not need to adapt or change anything. In addition to that, the created operators can be used anywhere else too, to solve related problems. ### Breaking Changes The new pipe will not introduce any breaking changes. The new behavior can be implemented in the underlying `cdAwaer` class and no public API needs to be touched. The configuration is done over pipe parameters or globally over some tokens. And will get introduced wich the above adoption. ### Rollout Plan <!-- Are the implementation delays about to negatively affect or delay the release of other features or increase the size. --> The operator can be rolled out in the following way: **Alpha releases of the operator** include only animationFrame as coalescing frame and scoping by a context. Shipped Features: - scope coalescing by context object `CoalesceConfig.context` - define coalescing by executin context `CoalesceConfig.executionContextRef` - control the emission of leading values `CoalesceConfig.leading` - control the emission of trailing values `CoalesceConfig.trailing` **Beta releases of the @ngrx/component package** parts of the package include a configuration argument to opt-in the operator and its mechanism. **Rc and official releases of the @ngrx/component package** parts of the package have the feature opt-out by default. Alos internal interfaces etc can be exposed in this releases. ### Rollback Plan <!-- How do you plan to roll back the change if major issues are found? --> In the late alpha versions, we introduce the operator. If any critical problems occur in the alpha version we can just don't ship it further versions. Until the first beta, we should have to clarify this. This is the moment we introduce the configuration option for the part of the package that uses this operator. The feature can be rolled back by removing the option from the config object. If a major issue is found in RC or even official release we can remove the parts of the package from the package and disable the related config options. There are no implementation details that could delay the release of other packages. Also, the bundle size of other features will not increase by introducing this feature. ### Maintenance Plan <!-- Explain how this library will be maintained going forward in releases after the initial release. This includes not only releases of the subject of this document but also respects its dependencies. --> The code relies on some RxJS operators naming: `Observable, filter, tap` `tap` may break in the future if Observabls make it in the standard the most probably will have callbacks as arguments instead of an observer object. But this is also a minor refactoring. ## Alternatives considered <!-- Include alternate design ideas you tried out, but didn't continue with them. List their disadvantages or at least why you did not invest more time in researching them. --> **Pipe:** - context: PipeClass, executionContextRef: animationFrame Fails if multiple components are nested @TODO double check **Directive:** - context: DirectiveClass, executionContextRef: animationFrame Fails if multiple `*ngrxLet` directives are in the same template context ## Work Breakdown <!-- Explain how multiple people would actively working on the suggested code base. If needed include branching suggestions or the way code interacts --> As the pipes scope is small enough to get maintained by one person at a time we don't need to describe the breakdown of work. ## Resources **Research Paper/Design Docs/StackBlitz/Video/Podcast/Blog/Tweet/Graphic:** - [Angular9 ivy change-detection preview](https://alexzuza.github.io/angular-9-ivy-change-detection-preview/) - [Examples of the coalescing mechanism and its behavior](https://stackblitz.com/edit/rxjs-coalescing-of-work?file=index.html) In: StackBlitz, Michael Hladky. Hladky. - [Coalescing of Change Detection](https://docs.google.com/presentation/d/1QBx3nTyDpQvmNnN5NNV6apJO70rSN6lxCKS-OmPPPA8/edit?usp=sharing) In: Google Slides, Michael Hladky. - [Reduce Change Detection Cycles with Event Coalescing in Angular](https://netbasal.com/reduce-change-detection-cycles-with-event-coalescing-in-angular-c4037199859f) In: Medium, Netanel Basal, 2019-12-10. - [How can I change the viewContainerRef of an ng-template](https://stackoverflow.com/questions/60117068/how-can-i-change-the-viewcontainerref-of-a-ng-template/60126090#60126090) In: StackOverflow - [Ivy Minor Changes](https://docs.google.com/document/d/1Dije0AsJ0PxL3NaeNPxpYDeapj30b_QC0xfeIvIIzgg/preview) In: Google Docs, Angular. **Github Pull Request/Issue/Doc/Source Link:** - [Angular change detection spec](https://github.com/angular/angular/blob/9bd959076730c4e22ceadda73694198b4f01b9e0/packages/core/test/acceptance/change_detection_spec.ts) - [LComponentView vs LEmbeddedView](https://github.com/ngrx/platform/pull/2046#issuecomment-581167650) - [getNativeRequestAnimationFrame](https://github.com/angular/angular/blob/44623a116158c8048ce61a873ed63a290c039202/packages/core/src/util/raf.ts) - [add a flag in bootstrap to enable coalesce event to improve performance](https://github.com/angular/angular/pull/30533/files#diff-9d549aa403494fd043a146b8db64f503R264-R275) - [RFC: Component: Evaluation of using angular's internal `ɵdetectChanges` method](https://github.com/ngrx/platform/issues/2050) - [isAngularZone](https://github.com/angular/angular/blob/8.2.14/packages/core/src/zone/ng_zone.ts#L143-L144) - [coalesce change detection](https://github.com/angular/angular/blob/master/packages/core/src/zone/ng_zone.ts#L264) - [Angulars scheduleDetectChanges](https://github.com/angular/angular/blob/master/packages/elements/src/component-factory-strategy.ts#L202-L215) - [CHANGELOG v9](https://github.com/angular/angular/blob/master/CHANGELOG.md#breaking-changes)

    Import from clipboard

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lost their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template is not available.


    Upgrade

    All
    • All
    • Team
    No template found.

    Create custom template


    Upgrade

    Delete template

    Do you really want to delete this template?

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Tutorials

    Book Mode Tutorial

    Slide Mode Tutorial

    YAML Metadata

    Contacts

    Facebook

    Twitter

    Feedback

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions

    Versions and GitHub Sync

    Sign in to link this note to GitHub Learn more
    This note is not linked with GitHub Learn more
     
    Add badge Pull Push GitHub Link Settings
    Upgrade now

    Version named by    

    More Less
    • Edit
    • Delete

    Note content is identical to the latest version.
    Compare with
      Choose a version
      No search result
      Version not found

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub

        Please sign in to GitHub and install the HackMD app on your GitHub repo. Learn more

         Sign in to GitHub

        HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Available push count

        Upgrade

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Upgrade

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully