fix(ivy): component ref injector should support change detector ref (#27107)
PR Close #27107
This commit is contained in:
parent
3ec7c5081d
commit
ee12e725c0
|
@ -3,7 +3,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1497,
|
||||
"main": 181839,
|
||||
"main": 185238,
|
||||
"polyfills": 59608
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,8 +121,8 @@ export function renderComponent<T>(
|
|||
|
||||
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
|
||||
const rootView: LViewData = createLViewData(
|
||||
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags);
|
||||
rootView[INJECTOR] = opts.injector || null;
|
||||
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, undefined,
|
||||
opts.injector || null);
|
||||
|
||||
const oldView = enterView(rootView, null);
|
||||
let component: T;
|
||||
|
|
|
@ -20,9 +20,10 @@ import {Type} from '../type';
|
|||
import {assertComponentType, assertDefined} from './assert';
|
||||
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
|
||||
import {getComponentDef} from './definition';
|
||||
import {NodeInjector} from './di';
|
||||
import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
|
||||
import {ComponentDef, RenderFlags} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
|
||||
import {enterView, leaveView} from './state';
|
||||
|
@ -138,10 +139,12 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
ngModule && !isInternalRootView ? ngModule.injector.get(ROOT_CONTEXT) : createRootContext();
|
||||
|
||||
const renderer = rendererFactory.createRenderer(hostRNode, this.componentDef);
|
||||
const rootViewInjector =
|
||||
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
|
||||
// Create the root view. Uses empty TView and ContentTemplate.
|
||||
const rootView: LViewData = createLViewData(
|
||||
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags);
|
||||
rootView[INJECTOR] = ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
|
||||
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, undefined,
|
||||
rootViewInjector);
|
||||
|
||||
// rootView is the parent when bootstrapping
|
||||
const oldView = enterView(rootView, null);
|
||||
|
@ -198,8 +201,8 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
}
|
||||
|
||||
const componentRef = new ComponentRef(
|
||||
this.componentType, component, rootView, injector,
|
||||
createElementRef(viewEngine_ElementRef, tElementNode, rootView));
|
||||
this.componentType, component,
|
||||
createElementRef(viewEngine_ElementRef, tElementNode, rootView), rootView, tElementNode);
|
||||
|
||||
if (isInternalRootView) {
|
||||
// The host element of the internal root view is attached to the component's host view node
|
||||
|
@ -232,23 +235,24 @@ export function injectComponentFactoryResolver(): viewEngine_ComponentFactoryRes
|
|||
*/
|
||||
export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
|
||||
destroyCbs: (() => void)[]|null = [];
|
||||
injector: Injector;
|
||||
instance: T;
|
||||
hostView: ViewRef<T>;
|
||||
changeDetectorRef: ViewEngine_ChangeDetectorRef;
|
||||
componentType: Type<T>;
|
||||
|
||||
constructor(
|
||||
componentType: Type<T>, instance: T, rootView: LViewData, injector: Injector,
|
||||
public location: viewEngine_ElementRef) {
|
||||
componentType: Type<T>, instance: T, public location: viewEngine_ElementRef,
|
||||
private _rootView: LViewData,
|
||||
private _tNode: TElementNode|TContainerNode|TElementContainerNode) {
|
||||
super();
|
||||
this.instance = instance;
|
||||
this.hostView = this.changeDetectorRef = new RootViewRef<T>(rootView);
|
||||
this.hostView._tViewNode = createViewNode(-1, rootView);
|
||||
this.injector = injector;
|
||||
this.hostView = this.changeDetectorRef = new RootViewRef<T>(_rootView);
|
||||
this.hostView._tViewNode = createViewNode(-1, _rootView);
|
||||
this.componentType = componentType;
|
||||
}
|
||||
|
||||
get injector(): Injector { return new NodeInjector(this._tNode, this._rootView); }
|
||||
|
||||
destroy(): void {
|
||||
ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
|
||||
this.destroyCbs !.forEach(fn => fn());
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import './ng_dev_mode';
|
||||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
import {Injector} from '../di/injector';
|
||||
import {InjectFlags} from '../di/injector_compatibility';
|
||||
import {QueryList} from '../linker';
|
||||
import {Sanitizer} from '../sanitization/security';
|
||||
|
@ -156,13 +157,14 @@ function refreshChildComponents(
|
|||
|
||||
export function createLViewData<T>(
|
||||
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
|
||||
sanitizer?: Sanitizer | null): LViewData {
|
||||
sanitizer?: Sanitizer | null, injector?: Injector | null): LViewData {
|
||||
const viewData = getViewData();
|
||||
const instance = tView.blueprint.slice() as LViewData;
|
||||
instance[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit;
|
||||
instance[PARENT] = instance[DECLARATION_VIEW] = viewData;
|
||||
instance[CONTEXT] = context;
|
||||
instance[INJECTOR] = viewData ? viewData[INJECTOR] : null;
|
||||
instance[INJECTOR as any] =
|
||||
injector === undefined ? (viewData ? viewData[INJECTOR] : null) : injector;
|
||||
instance[RENDERER] = renderer;
|
||||
instance[SANITIZER] = sanitizer || null;
|
||||
return instance;
|
||||
|
@ -680,7 +682,7 @@ export function createTView(
|
|||
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
||||
const initialViewLength = bindingStartIndex + vars;
|
||||
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
||||
return blueprint[TVIEW] = {
|
||||
return blueprint[TVIEW as any] = {
|
||||
id: viewIndex,
|
||||
blueprint: blueprint,
|
||||
template: templateFn,
|
||||
|
|
|
@ -69,7 +69,7 @@ export interface LViewData extends Array<any> {
|
|||
* node tree in DI and get the TView.data array associated with a node (where the
|
||||
* directive defs are stored).
|
||||
*/
|
||||
[TVIEW]: TView;
|
||||
readonly[TVIEW]: TView;
|
||||
|
||||
/** Flags for this view. See LViewFlags for more info. */
|
||||
[FLAGS]: LViewFlags;
|
||||
|
@ -147,7 +147,7 @@ export interface LViewData extends Array<any> {
|
|||
[CONTEXT]: {}|RootContext|null;
|
||||
|
||||
/** An optional Module Injector to be used as fall back after Element Injectors are consulted. */
|
||||
[INJECTOR]: Injector|null;
|
||||
readonly[INJECTOR]: Injector|null;
|
||||
|
||||
/** Renderer to be used for this view. */
|
||||
[RENDERER]: Renderer3;
|
||||
|
|
|
@ -275,6 +275,8 @@ export class RootViewRef<T> extends ViewRef<T> {
|
|||
detectChanges(): void { detectChangesInRootView(this._view); }
|
||||
|
||||
checkNoChanges(): void { checkNoChangesInRootView(this._view); }
|
||||
|
||||
get context(): T { return null !; }
|
||||
}
|
||||
|
||||
function collectNativeNodes(lView: LViewData, parentTNode: TNode, result: any[]): any[] {
|
||||
|
|
|
@ -67,11 +67,14 @@ export class Testability implements PublicTestability {
|
|||
private _didWork: boolean = false;
|
||||
private _callbacks: WaitCallback[] = [];
|
||||
|
||||
private taskTrackingZone: any;
|
||||
private taskTrackingZone: {macroTasks: Task[]}|null = null;
|
||||
|
||||
constructor(private _ngZone: NgZone) {
|
||||
this._watchAngularEvents();
|
||||
_ngZone.run(() => { this.taskTrackingZone = Zone.current.get('TaskTrackingZone'); });
|
||||
_ngZone.run(() => {
|
||||
this.taskTrackingZone =
|
||||
typeof Zone == 'undefined' ? null : Zone.current.get('TaskTrackingZone');
|
||||
});
|
||||
}
|
||||
|
||||
private _watchAngularEvents(): void {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component as _Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
|
||||
import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
|
||||
import {ViewEncapsulation} from '../../src/metadata';
|
||||
import {AttributeMarker, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load, query, queryRefresh} from '../../src/render3/index';
|
||||
|
||||
|
@ -1035,6 +1035,72 @@ describe('ViewContainerRef', () => {
|
|||
expect(templateExecutionCounter).toEqual(5);
|
||||
});
|
||||
|
||||
describe('ComponentRef', () => {
|
||||
let dynamicComp !: DynamicComp;
|
||||
|
||||
class AppComp {
|
||||
constructor(public vcr: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: AppComp,
|
||||
selectors: [['app-comp']],
|
||||
factory:
|
||||
() => new AppComp(
|
||||
directiveInject(ViewContainerRef as any), injectComponentFactoryResolver()),
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, cmp: AppComp) => {}
|
||||
});
|
||||
}
|
||||
|
||||
class DynamicComp {
|
||||
doCheckCount = 0;
|
||||
|
||||
ngDoCheck() { this.doCheckCount++; }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: DynamicComp,
|
||||
selectors: [['dynamic-comp']],
|
||||
factory: () => dynamicComp = new DynamicComp(),
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, cmp: DynamicComp) => {}
|
||||
});
|
||||
}
|
||||
|
||||
it('should return ComponentRef with ChangeDetectorRef attached to root view', () => {
|
||||
const fixture = new ComponentFixture(AppComp);
|
||||
|
||||
const dynamicCompFactory = fixture.component.cfr.resolveComponentFactory(DynamicComp);
|
||||
const ref = fixture.component.vcr.createComponent(dynamicCompFactory);
|
||||
fixture.update();
|
||||
expect(dynamicComp.doCheckCount).toEqual(1);
|
||||
|
||||
// The change detector ref should be attached to the root view that contains
|
||||
// DynamicComp, so the doCheck hook for DynamicComp should run upon ref.detectChanges().
|
||||
ref.changeDetectorRef.detectChanges();
|
||||
expect(dynamicComp.doCheckCount).toEqual(2);
|
||||
expect((ref.changeDetectorRef as any).context).toBeNull();
|
||||
});
|
||||
|
||||
it('should return ComponentRef that can retrieve component ChangeDetectorRef through its injector',
|
||||
() => {
|
||||
const fixture = new ComponentFixture(AppComp);
|
||||
|
||||
const dynamicCompFactory = fixture.component.cfr.resolveComponentFactory(DynamicComp);
|
||||
const ref = fixture.component.vcr.createComponent(dynamicCompFactory);
|
||||
fixture.update();
|
||||
expect(dynamicComp.doCheckCount).toEqual(1);
|
||||
|
||||
// The injector should retrieve the change detector ref for DynamicComp. As such,
|
||||
// the doCheck hook for DynamicComp should NOT run upon ref.detectChanges().
|
||||
const changeDetector = ref.injector.get(ChangeDetectorRef);
|
||||
changeDetector.detectChanges();
|
||||
expect(dynamicComp.doCheckCount).toEqual(1);
|
||||
expect(changeDetector.context).toEqual(dynamicComp);
|
||||
});
|
||||
});
|
||||
|
||||
class EmbeddedComponentWithNgContent {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: EmbeddedComponentWithNgContent,
|
||||
|
|
Loading…
Reference in New Issue