Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ivy): support markForCheck #22690

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 16 additions & 10 deletions packages/core/src/change_detection/change_detector_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
*/
export abstract class ChangeDetectorRef {
/**
* Marks all {@link ChangeDetectionStrategy#OnPush OnPush} ancestors as to be checked.
* Marks a view and all of its ancestors dirty.
*
* This can be used to ensure an {@link ChangeDetectionStrategy#OnPush OnPush} component is
* checked when it needs to be re-rendered but the two normal triggers haven't marked it
* dirty (i.e. inputs haven't changed and events haven't fired in the view).
*
* <!-- TODO: Add a link to a chapter on OnPush components -->
*
Expand Down Expand Up @@ -39,12 +43,12 @@ export abstract class ChangeDetectorRef {
abstract markForCheck(): void;

/**
* Detaches the change detector from the change detector tree.
*
* The detached change detector will not be checked until it is reattached.
* Detaches the view from the change detection tree.
*
* This can also be used in combination with {@link ChangeDetectorRef#detectChanges detectChanges}
* to implement local change detection checks.
* Detached views will not be checked during change detection runs until they are
* re-attached, even if they are dirty. `detach` can be used in combination with
* {@link ChangeDetectorRef#detectChanges detectChanges} to implement local change
* detection checks.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
Expand Down Expand Up @@ -93,7 +97,7 @@ export abstract class ChangeDetectorRef {
abstract detach(): void;

/**
* Checks the change detector and its children.
* Checks the view and its children.
*
* This can also be used in combination with {@link ChangeDetectorRef#detach detach} to implement
* local change detection checks.
Expand All @@ -108,8 +112,7 @@ export abstract class ChangeDetectorRef {
* we want to check and update the list every five seconds.
*
* We can do that by detaching the component's change detector and doing a local change detection
* check
* every five seconds.
* check every five seconds.
*
* See {@link ChangeDetectorRef#detach detach} for more information.
*/
Expand All @@ -124,7 +127,10 @@ export abstract class ChangeDetectorRef {
abstract checkNoChanges(): void;

/**
* Reattach the change detector to the change detector tree.
* Re-attaches the view to the change detection tree.
*
* This can be used to re-attach views that were previously detached from the tree
* using {@link ChangeDetectorRef#detach detach}. Views are attached to the tree by default.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
*
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/render3/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1499,7 +1499,7 @@ export function wrapListenerWithDirtyAndDefault(
}

/** Marks current view and all ancestors dirty */
function markViewDirty(view: LView): void {
export function markViewDirty(view: LView): void {
let currentView: LView|null = view;

while (currentView.parent != null) {
Expand Down
162 changes: 155 additions & 7 deletions packages/core/src/render3/view_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';

import {detectChanges} from './instructions';
import {detectChanges, markViewDirty} from './instructions';
import {ComponentTemplate} from './interfaces/definition';
import {LViewNode} from './interfaces/node';
import {LView, LViewFlags} from './interfaces/view';
Expand All @@ -26,25 +26,173 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
destroy(): void { notImplemented(); }
destroyed: boolean;
onDestroy(callback: Function) { notImplemented(); }
markForCheck(): void { notImplemented(); }

/**
* Detaches a view from the change detection tree.
* Marks a view and all of its ancestors dirty.
*
* Detached views will not be checked during change detection runs, even if the view
* is dirty. This can be used in combination with detectChanges to implement local
* change detection checks.
* It also triggers change detection by calling `scheduleTick` internally, which coalesces
* multiple `markForCheck` calls to into one change detection run.
*
* This can be used to ensure an {@link ChangeDetectionStrategy#OnPush OnPush} component is
* checked when it needs to be re-rendered but the two normal triggers haven't marked it
* dirty (i.e. inputs haven't changed and events haven't fired in the view).
*
* <!-- TODO: Add a link to a chapter on OnPush components -->
*
* ### Example ([live demo](https://stackblitz.com/edit/angular-kx7rrw))
*
* ```typescript
* @Component({
* selector: 'my-app',
* template: `Number of ticks: {{numberOfTicks}}`
* changeDetection: ChangeDetectionStrategy.OnPush,
* })
* class AppComponent {
* numberOfTicks = 0;
*
* constructor(private ref: ChangeDetectorRef) {
* setInterval(() => {
* this.numberOfTicks++;
* // the following is required, otherwise the view will not be updated
* this.ref.markForCheck();
* }, 1000);
* }
* }
* ```
*/
markForCheck(): void { markViewDirty(this._view); }

/**
* Detaches the view from the change detection tree.
*
* Detached views will not be checked during change detection runs until they are
* re-attached, even if they are dirty. `detach` can be used in combination with
* {@link ChangeDetectorRef#detectChanges detectChanges} to implement local change
* detection checks.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
*
* ### Example
*
* The following example defines a component with a large list of readonly data.
* Imagine the data changes constantly, many times per second. For performance reasons,
* we want to check and update the list every five seconds. We can do that by detaching
* the component's change detector and doing a local check every five seconds.
*
* ```typescript
* class DataProvider {
* // in a real application the returned data will be different every time
* get data() {
* return [1,2,3,4,5];
* }
* }
*
* @Component({
* selector: 'giant-list',
* template: `
* <li *ngFor="let d of dataProvider.data">Data {{d}}</li>
* `,
* })
* class GiantList {
* constructor(private ref: ChangeDetectorRef, private dataProvider: DataProvider) {
* ref.detach();
* setInterval(() => {
* this.ref.detectChanges();
* }, 5000);
* }
* }
*
* @Component({
* selector: 'app',
* providers: [DataProvider],
* template: `
* <giant-list><giant-list>
* `,
* })
* class App {
* }
* ```
*/
detach(): void { this._view.flags &= ~LViewFlags.Attached; }

/**
* Re-attaches a view to the change detection tree.
*
* This can be used to re-attach views that were previously detached from the tree
* using detach(). Views are attached to the tree by default.
* using {@link ChangeDetectorRef#detach detach}. Views are attached to the tree by default.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
*
* ### Example ([live demo](https://stackblitz.com/edit/angular-ymgsxw))
*
* The following example creates a component displaying `live` data. The component will detach
* its change detector from the main change detector tree when the component's live property
* is set to false.
*
* ```typescript
* class DataProvider {
* data = 1;
*
* constructor() {
* setInterval(() => {
* this.data = this.data * 2;
* }, 500);
* }
* }
*
* @Component({
* selector: 'live-data',
* inputs: ['live'],
* template: 'Data: {{dataProvider.data}}'
* })
* class LiveData {
* constructor(private ref: ChangeDetectorRef, private dataProvider: DataProvider) {}
*
* set live(value) {
* if (value) {
* this.ref.reattach();
* } else {
* this.ref.detach();
* }
* }
* }
*
* @Component({
* selector: 'my-app',
* providers: [DataProvider],
* template: `
* Live Update: <input type="checkbox" [(ngModel)]="live">
* <live-data [live]="live"><live-data>
* `,
* })
* class AppComponent {
* live = true;
* }
* ```
*/
reattach(): void { this._view.flags |= LViewFlags.Attached; }

/**
* Checks the view and its children.
*
* This can also be used in combination with {@link ChangeDetectorRef#detach detach} to implement
* local change detection checks.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
*
* ### Example
*
* The following example defines a component with a large list of readonly data.
* Imagine, the data changes constantly, many times per second. For performance reasons,
* we want to check and update the list every five seconds.
*
* We can do that by detaching the component's change detector and doing a local change detection
* check every five seconds.
*
* See {@link ChangeDetectorRef#detach detach} for more information.
*/
detectChanges(): void { detectChanges(this.context); }

checkNoChanges(): void { notImplemented(); }
Expand Down