parent
							
								
									0d8deb0795
								
							
						
					
					
						commit
						fa451bcd19
					
				| @ -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 --> | ||||
|    * | ||||
| @ -39,12 +43,12 @@ export abstract class ChangeDetectorRef { | ||||
|   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. | ||||
|    * | ||||
|    * 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 --> | ||||
| @ -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. | ||||
| @ -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. | ||||
|    */ | ||||
| @ -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 --> | ||||
|    * | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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'; | ||||
| @ -26,14 +26,93 @@ 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; } | ||||
| 
 | ||||
| @ -41,10 +120,79 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> { | ||||
|    * 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(); } | ||||
|  | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user