refactor(ivy): ViewRef needs embededViewRef declaration (#33074)
PR Close #33074
This commit is contained in:
		
							parent
							
								
									f1ffd57105
								
							
						
					
					
						commit
						5632424d04
					
				| @ -122,7 +122,7 @@ export function createTemplateRef<T>( | |||||||
| 
 | 
 | ||||||
|         renderView(lView, embeddedTView, context); |         renderView(lView, embeddedTView, context); | ||||||
| 
 | 
 | ||||||
|         const viewRef = new ViewRef(lView, context, -1); |         const viewRef = new ViewRef<T>(lView); | ||||||
|         viewRef._tViewNode = lView[T_HOST] as TViewNode; |         viewRef._tViewNode = lView[T_HOST] as TViewNode; | ||||||
|         return viewRef; |         return viewRef; | ||||||
|       } |       } | ||||||
| @ -295,7 +295,7 @@ export function createContainerRef( | |||||||
| 
 | 
 | ||||||
|         const wasDetached = |         const wasDetached = | ||||||
|             view && removeFromArray(this._lContainer[VIEW_REFS] !, adjustedIdx) != null; |             view && removeFromArray(this._lContainer[VIEW_REFS] !, adjustedIdx) != null; | ||||||
|         return wasDetached ? new ViewRef(view !, view ![CONTEXT], -1) : null; |         return wasDetached ? new ViewRef(view !) : null; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       private _adjustIndex(index?: number, shift: number = 0) { |       private _adjustIndex(index?: number, shift: number = 0) { | ||||||
| @ -370,22 +370,27 @@ export function injectChangeDetectorRef(isPipe = false): ViewEngine_ChangeDetect | |||||||
| /** | /** | ||||||
|  * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias). |  * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias). | ||||||
|  * |  * | ||||||
|  * @param hostTNode The node that is requesting a ChangeDetectorRef |  * @param tNode The node that is requesting a ChangeDetectorRef | ||||||
|  * @param hostView The view to which the node belongs |  * @param lView The view to which the node belongs | ||||||
|  * @param isPipe Whether the view is being injected into a pipe. |  * @param isPipe Whether the view is being injected into a pipe. | ||||||
|  * @returns The ChangeDetectorRef to use |  * @returns The ChangeDetectorRef to use | ||||||
|  */ |  */ | ||||||
| function createViewRef( | function createViewRef(tNode: TNode, lView: LView, isPipe: boolean): ViewEngine_ChangeDetectorRef { | ||||||
|     hostTNode: TNode, hostView: LView, isPipe: boolean): ViewEngine_ChangeDetectorRef { |   // `isComponentView` will be true for Component and Directives (but not for Pipes).
 | ||||||
|   if (isComponentHost(hostTNode) && !isPipe) { |   // See https://github.com/angular/angular/pull/33072 for proper fix
 | ||||||
|     const componentIndex = hostTNode.directiveStart; |   const isComponentView = !isPipe && isComponentHost(tNode); | ||||||
|     const componentView = getComponentLViewByIndex(hostTNode.index, hostView); |   if (isComponentView) { | ||||||
|     return new ViewRef(componentView, null, componentIndex); |     // The LView represents the location where the component is declared.
 | ||||||
|  |     // Instead we want the LView for the component View and so we need to look it up.
 | ||||||
|  |     const componentView = getComponentLViewByIndex(tNode.index, lView);  // look down
 | ||||||
|  |     return new ViewRef(componentView, componentView); | ||||||
|   } else if ( |   } else if ( | ||||||
|       hostTNode.type === TNodeType.Element || hostTNode.type === TNodeType.Container || |       tNode.type === TNodeType.Element || tNode.type === TNodeType.Container || | ||||||
|       hostTNode.type === TNodeType.ElementContainer) { |       tNode.type === TNodeType.ElementContainer) { | ||||||
|     const hostComponentView = findComponentView(hostView); |     // The LView represents the location where the injection is requested from.
 | ||||||
|     return new ViewRef(hostComponentView, hostComponentView[CONTEXT], -1); |     // We need to locate the containing LView (in case where the `lView` is an embedded view)
 | ||||||
|  |     const hostComponentView = findComponentView(lView);  // look up
 | ||||||
|  |     return new ViewRef(hostComponentView, lView); | ||||||
|   } |   } | ||||||
|   return null !; |   return null !; | ||||||
| } | } | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEn | |||||||
| 
 | 
 | ||||||
| import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions/shared'; | import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions/shared'; | ||||||
| import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; | import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; | ||||||
| import {FLAGS, HOST, LView, LViewFlags, T_HOST} from './interfaces/view'; | import {CONTEXT, FLAGS, HOST, LView, LViewFlags, T_HOST} from './interfaces/view'; | ||||||
| import {destroyLView, renderDetachView} from './node_manipulation'; | import {destroyLView, renderDetachView} from './node_manipulation'; | ||||||
| import {findComponentView, getLViewParent} from './util/view_traversal_utils'; | import {findComponentView, getLViewParent} from './util/view_traversal_utils'; | ||||||
| import {getNativeByTNode, getNativeByTNodeOrNull} from './util/view_utils'; | import {getNativeByTNode, getNativeByTNodeOrNull} from './util/view_utils'; | ||||||
| @ -35,11 +35,6 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int | |||||||
|    */ |    */ | ||||||
|   public _tViewNode: TViewNode|null = null; |   public _tViewNode: TViewNode|null = null; | ||||||
| 
 | 
 | ||||||
|   /** |  | ||||||
|    * @internal |  | ||||||
|    */ |  | ||||||
|   public _lView: LView; |  | ||||||
| 
 |  | ||||||
|   get rootNodes(): any[] { |   get rootNodes(): any[] { | ||||||
|     if (this._lView[HOST] == null) { |     if (this._lView[HOST] == null) { | ||||||
|       const tView = this._lView[T_HOST] as TViewNode; |       const tView = this._lView[T_HOST] as TViewNode; | ||||||
| @ -48,11 +43,29 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int | |||||||
|     return []; |     return []; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   constructor(_lView: LView, private _context: T|null, private _componentIndex: number) { |   constructor( | ||||||
|     this._lView = _lView; |       /** | ||||||
|   } |        * This represents `LView` associated with the component when ViewRef is a ChangeDetectorRef. | ||||||
|  |        * | ||||||
|  |        * When ViewRef is created for a dynamic component, this also represents the `LView` for the | ||||||
|  |        * component. | ||||||
|  |        * | ||||||
|  |        * For a "regular" ViewRef created for an embedded view, this is the `LView` for the embedded | ||||||
|  |        * view. | ||||||
|  |        * | ||||||
|  |        * @internal | ||||||
|  |        */ | ||||||
|  |       public _lView: LView, | ||||||
| 
 | 
 | ||||||
|   get context(): T { return this._context ? this._context : this._lookUpContext(); } |       /** | ||||||
|  |        * This represents the `LView` associated with the point where `ChangeDetectorRef` was | ||||||
|  |        * requested. | ||||||
|  |        * | ||||||
|  |        * This may be different from `_lView` if the `_cdRefInjectingView` is an embedded view. | ||||||
|  |        */ | ||||||
|  |       private _cdRefInjectingView?: LView) {} | ||||||
|  | 
 | ||||||
|  |   get context(): T { return this._lView[CONTEXT] as T; } | ||||||
| 
 | 
 | ||||||
|   get destroyed(): boolean { |   get destroyed(): boolean { | ||||||
|     return (this._lView[FLAGS] & LViewFlags.Destroyed) === LViewFlags.Destroyed; |     return (this._lView[FLAGS] & LViewFlags.Destroyed) === LViewFlags.Destroyed; | ||||||
| @ -109,7 +122,7 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int | |||||||
|    * } |    * } | ||||||
|    * ``` |    * ``` | ||||||
|    */ |    */ | ||||||
|   markForCheck(): void { markViewDirty(this._lView); } |   markForCheck(): void { markViewDirty(this._cdRefInjectingView || this._lView); } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Detaches the view from the change detection tree. |    * Detaches the view from the change detection tree. | ||||||
| @ -273,15 +286,11 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int | |||||||
|     } |     } | ||||||
|     this._appRef = appRef; |     this._appRef = appRef; | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   private _lookUpContext(): T { |  | ||||||
|     return this._context = getLViewParent(this._lView) ![this._componentIndex] as T; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** @internal */ | /** @internal */ | ||||||
| export class RootViewRef<T> extends ViewRef<T> { | export class RootViewRef<T> extends ViewRef<T> { | ||||||
|   constructor(public _view: LView) { super(_view, null, -1); } |   constructor(public _view: LView) { super(_view); } | ||||||
| 
 | 
 | ||||||
|   detectChanges(): void { detectChangesInRootView(this._view); } |   detectChanges(): void { detectChangesInRootView(this._view); } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ import {CommonModule} from '@angular/common'; | |||||||
| import {ApplicationRef, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, DoCheck, EmbeddedViewRef, ErrorHandler, Input, NgModule, OnInit, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core'; | import {ApplicationRef, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, DoCheck, EmbeddedViewRef, ErrorHandler, Input, NgModule, OnInit, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core'; | ||||||
| import {TestBed} from '@angular/core/testing'; | import {TestBed} from '@angular/core/testing'; | ||||||
| import {expect} from '@angular/platform-browser/testing/src/matchers'; | import {expect} from '@angular/platform-browser/testing/src/matchers'; | ||||||
|  | import {BehaviorSubject} from 'rxjs'; | ||||||
| 
 | 
 | ||||||
| describe('change detection', () => { | describe('change detection', () => { | ||||||
| 
 | 
 | ||||||
| @ -913,6 +914,41 @@ describe('change detection', () => { | |||||||
|         expect(fixture.nativeElement.textContent).toEqual('two - two'); |         expect(fixture.nativeElement.textContent).toEqual('two - two'); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  |       it('async pipe should trigger CD for embedded views where the declaration and insertion views are different', | ||||||
|  |          () => { | ||||||
|  |            @Component({ | ||||||
|  |              selector: 'insertion', | ||||||
|  |              changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
|  |              template: ` <ng-container [ngTemplateOutlet]="template"> </ng-container> ` | ||||||
|  |            }) | ||||||
|  |            class Insertion { | ||||||
|  |              @Input() template !: TemplateRef<{}>; | ||||||
|  |            } | ||||||
|  | 
 | ||||||
|  |            // This component uses async pipe (which calls markForCheck) in a view that has different
 | ||||||
|  |            // insertion and declaration views.
 | ||||||
|  |            @Component({ | ||||||
|  |              changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
|  |              template: ` | ||||||
|  |           <insertion [template]="ref"></insertion> | ||||||
|  |           <ng-template #ref> | ||||||
|  |             <span>{{value | async}}</span> | ||||||
|  |           </ng-template> | ||||||
|  |           ` | ||||||
|  |            }) | ||||||
|  |            class Declaration { | ||||||
|  |              value = new BehaviorSubject('initial value'); | ||||||
|  |            } | ||||||
|  | 
 | ||||||
|  |            const fixture = TestBed.configureTestingModule({declarations: [Insertion, Declaration]}) | ||||||
|  |                                .createComponent(Declaration); | ||||||
|  |            fixture.detectChanges(); | ||||||
|  |            expect(fixture.debugElement.nativeElement.textContent).toContain('initial value'); | ||||||
|  |            fixture.componentInstance.value.next('new value'); | ||||||
|  |            fixture.detectChanges(); | ||||||
|  |            expect(fixture.debugElement.nativeElement.textContent).toContain('new value'); | ||||||
|  |          }); | ||||||
|  | 
 | ||||||
|       // TODO(kara): add test for dynamic views once bug fix is in
 |       // TODO(kara): add test for dynamic views once bug fix is in
 | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -282,7 +282,7 @@ | |||||||
|     "name": "getComponentDef" |     "name": "getComponentDef" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "getComponentViewByIndex" |     "name": "getComponentLViewByIndex" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "getContainerRenderParent" |     "name": "getContainerRenderParent" | ||||||
|  | |||||||
| @ -225,7 +225,7 @@ | |||||||
|     "name": "getComponentDef" |     "name": "getComponentDef" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "getComponentViewByIndex" |     "name": "getComponentLViewByIndex" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "getContainerRenderParent" |     "name": "getContainerRenderParent" | ||||||
|  | |||||||
| @ -621,7 +621,7 @@ | |||||||
|     "name": "getComponentDef" |     "name": "getComponentDef" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "getComponentViewByIndex" |     "name": "getComponentLViewByIndex" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "getComponentViewByInstance" |     "name": "getComponentViewByInstance" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user