parent
0d8deb0795
commit
fa451bcd19
|
@ -11,7 +11,11 @@
|
||||||
*/
|
*/
|
||||||
export abstract class ChangeDetectorRef {
|
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 -->
|
* <!-- TODO: Add a link to a chapter on OnPush components -->
|
||||||
*
|
*
|
||||||
|
@ -39,12 +43,12 @@ export abstract class ChangeDetectorRef {
|
||||||
abstract markForCheck(): void;
|
abstract markForCheck(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detaches the change detector from the change detector tree.
|
* Detaches the view from the change detection tree.
|
||||||
*
|
*
|
||||||
* The detached change detector will not be checked until it is reattached.
|
* 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
|
||||||
* This can also be used in combination with {@link ChangeDetectorRef#detectChanges detectChanges}
|
* {@link ChangeDetectorRef#detectChanges detectChanges} to implement local change
|
||||||
* to implement local change detection checks.
|
* detection checks.
|
||||||
*
|
*
|
||||||
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
|
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
|
||||||
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
|
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
|
||||||
|
@ -93,7 +97,7 @@ export abstract class ChangeDetectorRef {
|
||||||
abstract detach(): void;
|
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
|
* This can also be used in combination with {@link ChangeDetectorRef#detach detach} to implement
|
||||||
* local change detection checks.
|
* local change detection checks.
|
||||||
|
@ -108,8 +112,7 @@ export abstract class ChangeDetectorRef {
|
||||||
* we want to check and update the list every five seconds.
|
* 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
|
* We can do that by detaching the component's change detector and doing a local change detection
|
||||||
* check
|
* check every five seconds.
|
||||||
* every five seconds.
|
|
||||||
*
|
*
|
||||||
* See {@link ChangeDetectorRef#detach detach} for more information.
|
* See {@link ChangeDetectorRef#detach detach} for more information.
|
||||||
*/
|
*/
|
||||||
|
@ -124,7 +127,10 @@ export abstract class ChangeDetectorRef {
|
||||||
abstract checkNoChanges(): void;
|
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 -->
|
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
|
||||||
*
|
*
|
||||||
|
|
|
@ -1499,7 +1499,7 @@ export function wrapListenerWithDirtyAndDefault(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Marks current view and all ancestors dirty */
|
/** Marks current view and all ancestors dirty */
|
||||||
function markViewDirty(view: LView): void {
|
export function markViewDirty(view: LView): void {
|
||||||
let currentView: LView|null = view;
|
let currentView: LView|null = view;
|
||||||
|
|
||||||
while (currentView.parent != null) {
|
while (currentView.parent != null) {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
||||||
|
|
||||||
import {detectChanges} from './instructions';
|
import {detectChanges, markViewDirty} from './instructions';
|
||||||
import {ComponentTemplate} from './interfaces/definition';
|
import {ComponentTemplate} from './interfaces/definition';
|
||||||
import {LViewNode} from './interfaces/node';
|
import {LViewNode} from './interfaces/node';
|
||||||
import {LView, LViewFlags} from './interfaces/view';
|
import {LView, LViewFlags} from './interfaces/view';
|
||||||
|
@ -26,14 +26,93 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
|
||||||
destroy(): void { notImplemented(); }
|
destroy(): void { notImplemented(); }
|
||||||
destroyed: boolean;
|
destroyed: boolean;
|
||||||
onDestroy(callback: Function) { notImplemented(); }
|
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
|
* It also triggers change detection by calling `scheduleTick` internally, which coalesces
|
||||||
* is dirty. This can be used in combination with detectChanges to implement local
|
* multiple `markForCheck` calls to into one change detection run.
|
||||||
* change detection checks.
|
*
|
||||||
|
* 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; }
|
detach(): void { this._view.flags &= ~LViewFlags.Attached; }
|
||||||
|
|
||||||
|
@ -41,10 +120,79 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
|
||||||
* Re-attaches a view to the change detection tree.
|
* Re-attaches a view to the change detection tree.
|
||||||
*
|
*
|
||||||
* This can be used to re-attach views that were previously detached from the 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; }
|
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); }
|
detectChanges(): void { detectChanges(this.context); }
|
||||||
|
|
||||||
checkNoChanges(): void { notImplemented(); }
|
checkNoChanges(): void { notImplemented(); }
|
||||||
|
|
|
@ -756,6 +756,161 @@ describe('change detection', () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('markForCheck()', () => {
|
||||||
|
let comp: OnPushComp;
|
||||||
|
|
||||||
|
class OnPushComp {
|
||||||
|
value = 'one';
|
||||||
|
|
||||||
|
doCheckCount = 0;
|
||||||
|
|
||||||
|
constructor(public cdr: ChangeDetectorRef) {}
|
||||||
|
|
||||||
|
ngDoCheck() { this.doCheckCount++; }
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: OnPushComp,
|
||||||
|
tag: 'on-push-comp',
|
||||||
|
factory: () => comp = new OnPushComp(injectChangeDetectorRef()),
|
||||||
|
/** {{ value }} */
|
||||||
|
template: (ctx: OnPushComp, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
text(0);
|
||||||
|
}
|
||||||
|
textBinding(0, bind(ctx.value));
|
||||||
|
},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class OnPushParent {
|
||||||
|
value = 'one';
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: OnPushParent,
|
||||||
|
tag: 'on-push-parent',
|
||||||
|
factory: () => new OnPushParent(),
|
||||||
|
/**
|
||||||
|
* {{ value }} -
|
||||||
|
* <on-push-comp></on-push-comp>
|
||||||
|
*/
|
||||||
|
template: (ctx: OnPushParent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
text(0);
|
||||||
|
elementStart(1, OnPushComp);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
textBinding(0, interpolation1('', ctx.value, ' - '));
|
||||||
|
OnPushComp.ngComponentDef.h(2, 1);
|
||||||
|
directiveRefresh(2, 1);
|
||||||
|
},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should schedule check on OnPush components', () => {
|
||||||
|
const parent = renderComponent(OnPushParent);
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - one');
|
||||||
|
|
||||||
|
comp.value = 'two';
|
||||||
|
tick(parent);
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - one');
|
||||||
|
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - two');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only run change detection once with multiple calls to markForCheck', () => {
|
||||||
|
renderComponent(OnPushParent);
|
||||||
|
expect(comp.doCheckCount).toEqual(1);
|
||||||
|
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
|
||||||
|
expect(comp.doCheckCount).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should schedule check on ancestor OnPush components', () => {
|
||||||
|
const parent = renderComponent(OnPushParent);
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - one');
|
||||||
|
|
||||||
|
parent.value = 'two';
|
||||||
|
tick(parent);
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - one');
|
||||||
|
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(getRenderedText(parent)).toEqual('two - one');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should schedule check on OnPush components in embedded views', () => {
|
||||||
|
class EmbeddedViewParent {
|
||||||
|
value = 'one';
|
||||||
|
showing = true;
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: EmbeddedViewParent,
|
||||||
|
tag: 'embedded-view-parent',
|
||||||
|
factory: () => new EmbeddedViewParent(),
|
||||||
|
/**
|
||||||
|
* {{ value }} -
|
||||||
|
* % if (ctx.showing) {
|
||||||
|
* <on-push-comp></on-push-comp>
|
||||||
|
* % }
|
||||||
|
*/
|
||||||
|
template: (ctx: EmbeddedViewParent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
text(0);
|
||||||
|
container(1);
|
||||||
|
}
|
||||||
|
textBinding(0, interpolation1('', ctx.value, ' - '));
|
||||||
|
containerRefreshStart(1);
|
||||||
|
{
|
||||||
|
if (ctx.showing) {
|
||||||
|
if (embeddedViewStart(0)) {
|
||||||
|
elementStart(0, OnPushComp);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
OnPushComp.ngComponentDef.h(1, 0);
|
||||||
|
directiveRefresh(1, 0);
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = renderComponent(EmbeddedViewParent);
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - one');
|
||||||
|
|
||||||
|
comp.value = 'two';
|
||||||
|
tick(parent);
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - one');
|
||||||
|
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - two');
|
||||||
|
|
||||||
|
parent.value = 'two';
|
||||||
|
tick(parent);
|
||||||
|
expect(getRenderedText(parent)).toEqual('one - two');
|
||||||
|
|
||||||
|
comp.cdr.markForCheck();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(getRenderedText(parent)).toEqual('two - two');
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(kara): add test for dynamic views once bug fix is in
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue