fix(ivy): align NgModuleRef implementation between Ivy and ViewEngine (#27482)
Solves FW-765 and FW-767 PR Close #27482
This commit is contained in:
parent
159ab1c257
commit
8e9858fadb
@ -34,10 +34,15 @@ import {createElementRef} from './view_engine_compatibility';
|
|||||||
import {RootViewRef, ViewRef} from './view_ref';
|
import {RootViewRef, ViewRef} from './view_ref';
|
||||||
|
|
||||||
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
|
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
|
||||||
|
/**
|
||||||
|
* @param ngModule The NgModuleRef to which all resolved factories are bound.
|
||||||
|
*/
|
||||||
|
constructor(private ngModule?: viewEngine_NgModuleRef<any>) { super(); }
|
||||||
|
|
||||||
resolveComponentFactory<T>(component: Type<T>): viewEngine_ComponentFactory<T> {
|
resolveComponentFactory<T>(component: Type<T>): viewEngine_ComponentFactory<T> {
|
||||||
ngDevMode && assertComponentType(component);
|
ngDevMode && assertComponentType(component);
|
||||||
const componentDef = getComponentDef(component) !;
|
const componentDef = getComponentDef(component) !;
|
||||||
return new ComponentFactory(componentDef);
|
return new ComponentFactory(componentDef, this.ngModule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,10 +80,13 @@ function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injec
|
|||||||
get: <T>(token: Type<T>| InjectionToken<T>, notFoundValue?: T): T => {
|
get: <T>(token: Type<T>| InjectionToken<T>, notFoundValue?: T): T => {
|
||||||
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
|
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
|
||||||
|
|
||||||
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
|
||||||
|
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
||||||
// Return the value from the root element injector when
|
// Return the value from the root element injector when
|
||||||
// - it provides it
|
// - it provides it
|
||||||
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
||||||
|
// - the module injector should not be checked
|
||||||
|
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +111,12 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||||||
return toRefArray(this.componentDef.outputs);
|
return toRefArray(this.componentDef.outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private componentDef: ComponentDef<any>) {
|
/**
|
||||||
|
* @param componentDef The component definition.
|
||||||
|
* @param ngModule The NgModuleRef to which the factory is bound.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private componentDef: ComponentDef<any>, private ngModule?: viewEngine_NgModuleRef<any>) {
|
||||||
super();
|
super();
|
||||||
this.componentType = componentDef.type;
|
this.componentType = componentDef.type;
|
||||||
this.selector = componentDef.selectors[0][0] as string;
|
this.selector = componentDef.selectors[0][0] as string;
|
||||||
@ -114,6 +127,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||||||
injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any,
|
injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any,
|
||||||
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
|
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
|
||||||
const isInternalRootView = rootSelectorOrNode === undefined;
|
const isInternalRootView = rootSelectorOrNode === undefined;
|
||||||
|
ngModule = ngModule || this.ngModule;
|
||||||
|
|
||||||
const rootViewInjector =
|
const rootViewInjector =
|
||||||
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
|
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injector} from '../di/injector';
|
import {INJECTOR, Injector} from '../di/injector';
|
||||||
|
import {InjectFlags} from '../di/injector_compatibility';
|
||||||
import {StaticProvider} from '../di/provider';
|
import {StaticProvider} from '../di/provider';
|
||||||
import {createInjector} from '../di/r3_injector';
|
import {createInjector} from '../di/r3_injector';
|
||||||
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
|
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
|
||||||
@ -14,27 +15,29 @@ import {InternalNgModuleRef, NgModuleFactory as viewEngine_NgModuleFactory, NgMo
|
|||||||
import {NgModuleDef} from '../metadata/ng_module';
|
import {NgModuleDef} from '../metadata/ng_module';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
import {stringify} from '../util';
|
import {stringify} from '../util';
|
||||||
|
|
||||||
import {assertDefined} from './assert';
|
import {assertDefined} from './assert';
|
||||||
import {ComponentFactoryResolver} from './component_ref';
|
import {ComponentFactoryResolver} from './component_ref';
|
||||||
import {getNgModuleDef} from './definition';
|
import {getNgModuleDef} from './definition';
|
||||||
|
|
||||||
export interface NgModuleType { ngModuleDef: NgModuleDef<any>; }
|
export interface NgModuleType { ngModuleDef: NgModuleDef<any>; }
|
||||||
|
|
||||||
export const COMPONENT_FACTORY_RESOLVER: StaticProvider = {
|
const COMPONENT_FACTORY_RESOLVER: StaticProvider = {
|
||||||
provide: viewEngine_ComponentFactoryResolver,
|
provide: viewEngine_ComponentFactoryResolver,
|
||||||
useFactory: () => new ComponentFactoryResolver(),
|
useClass: ComponentFactoryResolver,
|
||||||
deps: [],
|
deps: [viewEngine_NgModuleRef],
|
||||||
};
|
};
|
||||||
|
|
||||||
export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements InternalNgModuleRef<T> {
|
export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements InternalNgModuleRef<T> {
|
||||||
// tslint:disable-next-line:require-internal-with-underscore
|
// tslint:disable-next-line:require-internal-with-underscore
|
||||||
_bootstrapComponents: Type<any>[] = [];
|
_bootstrapComponents: Type<any>[] = [];
|
||||||
injector: Injector;
|
// tslint:disable-next-line:require-internal-with-underscore
|
||||||
componentFactoryResolver: viewEngine_ComponentFactoryResolver;
|
_r3Injector: Injector;
|
||||||
|
injector: Injector = this;
|
||||||
instance: T;
|
instance: T;
|
||||||
destroyCbs: (() => void)[]|null = [];
|
destroyCbs: (() => void)[]|null = [];
|
||||||
|
|
||||||
constructor(ngModuleType: Type<T>, parentInjector: Injector|null) {
|
constructor(ngModuleType: Type<T>, public _parent: Injector|null) {
|
||||||
super();
|
super();
|
||||||
const ngModuleDef = getNgModuleDef(ngModuleType);
|
const ngModuleDef = getNgModuleDef(ngModuleType);
|
||||||
ngDevMode && assertDefined(
|
ngDevMode && assertDefined(
|
||||||
@ -43,14 +46,26 @@ export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements Interna
|
|||||||
|
|
||||||
this._bootstrapComponents = ngModuleDef !.bootstrap;
|
this._bootstrapComponents = ngModuleDef !.bootstrap;
|
||||||
const additionalProviders: StaticProvider[] = [
|
const additionalProviders: StaticProvider[] = [
|
||||||
COMPONENT_FACTORY_RESOLVER, {
|
{
|
||||||
provide: viewEngine_NgModuleRef,
|
provide: viewEngine_NgModuleRef,
|
||||||
useValue: this,
|
useValue: this,
|
||||||
}
|
},
|
||||||
|
COMPONENT_FACTORY_RESOLVER
|
||||||
];
|
];
|
||||||
this.injector = createInjector(ngModuleType, parentInjector, additionalProviders);
|
this._r3Injector = createInjector(ngModuleType, _parent, additionalProviders);
|
||||||
this.instance = this.injector.get(ngModuleType);
|
this.instance = this.get(ngModuleType);
|
||||||
this.componentFactoryResolver = new ComponentFactoryResolver();
|
}
|
||||||
|
|
||||||
|
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||||
|
injectFlags: InjectFlags = InjectFlags.Default): any {
|
||||||
|
if (token === Injector || token === viewEngine_NgModuleRef || token === INJECTOR) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return this._r3Injector.get(token, notFoundValue, injectFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
get componentFactoryResolver(): viewEngine_ComponentFactoryResolver {
|
||||||
|
return this.get(viewEngine_ComponentFactoryResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
|
@ -235,7 +235,7 @@ export function createContainerRef(
|
|||||||
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
|
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
|
||||||
ngModuleRef?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
|
ngModuleRef?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
|
||||||
const contextInjector = injector || this.parentInjector;
|
const contextInjector = injector || this.parentInjector;
|
||||||
if (!ngModuleRef && contextInjector) {
|
if (!ngModuleRef && (componentFactory as any).ngModule == null && contextInjector) {
|
||||||
ngModuleRef = contextInjector.get(viewEngine_NgModuleRef, null);
|
ngModuleRef = contextInjector.get(viewEngine_NgModuleRef, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,5 +187,87 @@ describe('ComponentFactory', () => {
|
|||||||
expect(mSanitizerFactorySpy).toHaveBeenCalled();
|
expect(mSanitizerFactorySpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('(when the factory is bound to a `ngModuleRef`)', () => {
|
||||||
|
it('should retrieve `RendererFactory2` from the specified injector first', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
{provide: RendererFactory2, useValue: {createRenderer: createRenderer2Spy}},
|
||||||
|
]);
|
||||||
|
(cf as any).ngModule = {
|
||||||
|
injector: Injector.create([
|
||||||
|
{provide: RendererFactory2, useValue: {createRenderer: createRenderer3Spy}},
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
cf.create(injector);
|
||||||
|
|
||||||
|
expect(createRenderer2Spy).toHaveBeenCalled();
|
||||||
|
expect(createRenderer3Spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve `RendererFactory2` from the `ngModuleRef` if not provided by the injector',
|
||||||
|
() => {
|
||||||
|
const injector = Injector.create([]);
|
||||||
|
(cf as any).ngModule = {
|
||||||
|
injector: Injector.create([
|
||||||
|
{provide: RendererFactory2, useValue: {createRenderer: createRenderer2Spy}},
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
cf.create(injector);
|
||||||
|
|
||||||
|
expect(createRenderer2Spy).toHaveBeenCalled();
|
||||||
|
expect(createRenderer3Spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to `domRendererFactory3` if `RendererFactory2` is not provided', () => {
|
||||||
|
const injector = Injector.create([]);
|
||||||
|
(cf as any).ngModule = {injector: Injector.create([])};
|
||||||
|
|
||||||
|
cf.create(injector);
|
||||||
|
|
||||||
|
expect(createRenderer2Spy).not.toHaveBeenCalled();
|
||||||
|
expect(createRenderer3Spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve `Sanitizer` from the specified injector first', () => {
|
||||||
|
const iSanitizerFactorySpy =
|
||||||
|
jasmine.createSpy('Injector#sanitizerFactory').and.returnValue({});
|
||||||
|
const injector = Injector.create([
|
||||||
|
{provide: Sanitizer, useFactory: iSanitizerFactorySpy, deps: []},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mSanitizerFactorySpy =
|
||||||
|
jasmine.createSpy('NgModuleRef#sanitizerFactory').and.returnValue({});
|
||||||
|
(cf as any).ngModule = {
|
||||||
|
injector: Injector.create([
|
||||||
|
{provide: Sanitizer, useFactory: mSanitizerFactorySpy, deps: []},
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
cf.create(injector);
|
||||||
|
|
||||||
|
expect(iSanitizerFactorySpy).toHaveBeenCalled();
|
||||||
|
expect(mSanitizerFactorySpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve `Sanitizer` from the `ngModuleRef` if not provided by the injector',
|
||||||
|
() => {
|
||||||
|
const injector = Injector.create([]);
|
||||||
|
|
||||||
|
const mSanitizerFactorySpy =
|
||||||
|
jasmine.createSpy('NgModuleRef#sanitizerFactory').and.returnValue({});
|
||||||
|
(cf as any).ngModule = {
|
||||||
|
injector: Injector.create([
|
||||||
|
{provide: Sanitizer, useFactory: mSanitizerFactorySpy, deps: []},
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
cf.create(injector);
|
||||||
|
|
||||||
|
expect(mSanitizerFactorySpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3561,94 +3561,93 @@ describe('Integration', () => {
|
|||||||
expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
|
expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-646: Directive providers don\'t support primitive types as DI tokens')
|
it('should have 2 injector trees: module and element',
|
||||||
.it('should have 2 injector trees: module and element',
|
fakeAsync(inject(
|
||||||
fakeAsync(inject(
|
[Router, Location, NgModuleFactoryLoader],
|
||||||
[Router, Location, NgModuleFactoryLoader],
|
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||||
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
@Component({
|
||||||
@Component({
|
selector: 'lazy',
|
||||||
selector: 'lazy',
|
template: 'parent[<router-outlet></router-outlet>]',
|
||||||
template: 'parent[<router-outlet></router-outlet>]',
|
viewProviders: [
|
||||||
viewProviders: [
|
{provide: 'shadow', useValue: 'from parent component'},
|
||||||
{provide: 'shadow', useValue: 'from parent component'},
|
],
|
||||||
],
|
})
|
||||||
})
|
class Parent {
|
||||||
class Parent {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'lazy', template: 'child'})
|
@Component({selector: 'lazy', template: 'child'})
|
||||||
class Child {
|
class Child {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Parent],
|
declarations: [Parent],
|
||||||
imports: [RouterModule.forChild([{
|
imports: [RouterModule.forChild([{
|
||||||
path: 'parent',
|
path: 'parent',
|
||||||
component: Parent,
|
component: Parent,
|
||||||
children: [
|
children: [
|
||||||
{path: 'child', loadChildren: 'child'},
|
{path: 'child', loadChildren: 'child'},
|
||||||
]
|
]
|
||||||
}])],
|
}])],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: 'moduleName', useValue: 'parent'},
|
{provide: 'moduleName', useValue: 'parent'},
|
||||||
{provide: 'fromParent', useValue: 'from parent'},
|
{provide: 'fromParent', useValue: 'from parent'},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
class ParentModule {
|
class ParentModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Child],
|
declarations: [Child],
|
||||||
imports: [RouterModule.forChild([{path: '', component: Child}])],
|
imports: [RouterModule.forChild([{path: '', component: Child}])],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: 'moduleName', useValue: 'child'},
|
{provide: 'moduleName', useValue: 'child'},
|
||||||
{provide: 'fromChild', useValue: 'from child'},
|
{provide: 'fromChild', useValue: 'from child'},
|
||||||
{provide: 'shadow', useValue: 'from child module'},
|
{provide: 'shadow', useValue: 'from child module'},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
class ChildModule {
|
class ChildModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.stubbedModules = {
|
loader.stubbedModules = {
|
||||||
parent: ParentModule,
|
parent: ParentModule,
|
||||||
child: ChildModule,
|
child: ChildModule,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
router.resetConfig([{path: 'lazy', loadChildren: 'parent'}]);
|
router.resetConfig([{path: 'lazy', loadChildren: 'parent'}]);
|
||||||
router.navigateByUrl('/lazy/parent/child');
|
router.navigateByUrl('/lazy/parent/child');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/lazy/parent/child');
|
expect(location.path()).toEqual('/lazy/parent/child');
|
||||||
expect(fixture.nativeElement).toHaveText('parent[child]');
|
expect(fixture.nativeElement).toHaveText('parent[child]');
|
||||||
|
|
||||||
const pInj = fixture.debugElement.query(By.directive(Parent)).injector !;
|
const pInj = fixture.debugElement.query(By.directive(Parent)).injector !;
|
||||||
const cInj = fixture.debugElement.query(By.directive(Child)).injector !;
|
const cInj = fixture.debugElement.query(By.directive(Child)).injector !;
|
||||||
|
|
||||||
expect(pInj.get('moduleName')).toEqual('parent');
|
expect(pInj.get('moduleName')).toEqual('parent');
|
||||||
expect(pInj.get('fromParent')).toEqual('from parent');
|
expect(pInj.get('fromParent')).toEqual('from parent');
|
||||||
expect(pInj.get(Parent)).toBeAnInstanceOf(Parent);
|
expect(pInj.get(Parent)).toBeAnInstanceOf(Parent);
|
||||||
expect(pInj.get('fromChild', null)).toEqual(null);
|
expect(pInj.get('fromChild', null)).toEqual(null);
|
||||||
expect(pInj.get(Child, null)).toEqual(null);
|
expect(pInj.get(Child, null)).toEqual(null);
|
||||||
|
|
||||||
expect(cInj.get('moduleName')).toEqual('child');
|
expect(cInj.get('moduleName')).toEqual('child');
|
||||||
expect(cInj.get('fromParent')).toEqual('from parent');
|
expect(cInj.get('fromParent')).toEqual('from parent');
|
||||||
expect(cInj.get('fromChild')).toEqual('from child');
|
expect(cInj.get('fromChild')).toEqual('from child');
|
||||||
expect(cInj.get(Parent)).toBeAnInstanceOf(Parent);
|
expect(cInj.get(Parent)).toBeAnInstanceOf(Parent);
|
||||||
expect(cInj.get(Child)).toBeAnInstanceOf(Child);
|
expect(cInj.get(Child)).toBeAnInstanceOf(Child);
|
||||||
// The child module can not shadow the parent component
|
// The child module can not shadow the parent component
|
||||||
expect(cInj.get('shadow')).toEqual('from parent component');
|
expect(cInj.get('shadow')).toEqual('from parent component');
|
||||||
|
|
||||||
const pmInj = pInj.get(NgModuleRef).injector;
|
const pmInj = pInj.get(NgModuleRef).injector;
|
||||||
const cmInj = cInj.get(NgModuleRef).injector;
|
const cmInj = cInj.get(NgModuleRef).injector;
|
||||||
|
|
||||||
expect(pmInj.get('moduleName')).toEqual('parent');
|
expect(pmInj.get('moduleName')).toEqual('parent');
|
||||||
expect(cmInj.get('moduleName')).toEqual('child');
|
expect(cmInj.get('moduleName')).toEqual('child');
|
||||||
|
|
||||||
expect(pmInj.get(Parent, '-')).toEqual('-');
|
expect(pmInj.get(Parent, '-')).toEqual('-');
|
||||||
expect(cmInj.get(Parent, '-')).toEqual('-');
|
expect(cmInj.get(Parent, '-')).toEqual('-');
|
||||||
expect(pmInj.get(Child, '-')).toEqual('-');
|
expect(pmInj.get(Child, '-')).toEqual('-');
|
||||||
expect(cmInj.get(Child, '-')).toEqual('-');
|
expect(cmInj.get(Child, '-')).toEqual('-');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// https://github.com/angular/angular/issues/12889
|
// https://github.com/angular/angular/issues/12889
|
||||||
it('should create a single instance of lazy-loaded modules',
|
it('should create a single instance of lazy-loaded modules',
|
||||||
@ -3689,57 +3688,55 @@ describe('Integration', () => {
|
|||||||
})));
|
})));
|
||||||
|
|
||||||
// https://github.com/angular/angular/issues/13870
|
// https://github.com/angular/angular/issues/13870
|
||||||
fixmeIvy(
|
it('should create a single instance of guards for lazy-loaded modules',
|
||||||
'FW-767: Lazy loaded modules are not used when resolving dependencies in one of their components')
|
fakeAsync(inject(
|
||||||
.it('should create a single instance of guards for lazy-loaded modules',
|
[Router, Location, NgModuleFactoryLoader],
|
||||||
fakeAsync(inject(
|
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||||
[Router, Location, NgModuleFactoryLoader],
|
@Injectable()
|
||||||
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
class Service {
|
||||||
@Injectable()
|
}
|
||||||
class Service {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class Resolver implements Resolve<Service> {
|
class Resolver implements Resolve<Service> {
|
||||||
constructor(public service: Service) {}
|
constructor(public service: Service) {}
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||||
return this.service;
|
return this.service;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'lazy', template: 'lazy'})
|
@Component({selector: 'lazy', template: 'lazy'})
|
||||||
class LazyLoadedComponent {
|
class LazyLoadedComponent {
|
||||||
resolvedService: Service;
|
resolvedService: Service;
|
||||||
constructor(public injectedService: Service, route: ActivatedRoute) {
|
constructor(public injectedService: Service, route: ActivatedRoute) {
|
||||||
this.resolvedService = route.snapshot.data['service'];
|
this.resolvedService = route.snapshot.data['service'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [LazyLoadedComponent],
|
declarations: [LazyLoadedComponent],
|
||||||
providers: [Service, Resolver],
|
providers: [Service, Resolver],
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([{
|
RouterModule.forChild([{
|
||||||
path: 'loaded',
|
path: 'loaded',
|
||||||
component: LazyLoadedComponent,
|
component: LazyLoadedComponent,
|
||||||
resolve: {'service': Resolver},
|
resolve: {'service': Resolver},
|
||||||
}]),
|
}]),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
class LoadedModule {
|
class LoadedModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.stubbedModules = {expected: LoadedModule};
|
loader.stubbedModules = {expected: LoadedModule};
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||||
router.navigateByUrl('/lazy/loaded');
|
router.navigateByUrl('/lazy/loaded');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(fixture.nativeElement).toHaveText('lazy');
|
expect(fixture.nativeElement).toHaveText('lazy');
|
||||||
const lzc = fixture.debugElement.query(By.directive(LazyLoadedComponent))
|
const lzc =
|
||||||
.componentInstance;
|
fixture.debugElement.query(By.directive(LazyLoadedComponent)).componentInstance;
|
||||||
expect(lzc.injectedService).toBe(lzc.resolvedService);
|
expect(lzc.injectedService).toBe(lzc.resolvedService);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
|
||||||
it('should emit RouteConfigLoadStart and RouteConfigLoadEnd event when route is lazy loaded',
|
it('should emit RouteConfigLoadStart and RouteConfigLoadEnd event when route is lazy loaded',
|
||||||
@ -3990,28 +3987,26 @@ describe('Integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy(
|
it('should use the injector of the lazily-loaded configuration',
|
||||||
'FW-767: Lazy loaded modules are not used when resolving dependencies in one of their components')
|
fakeAsync(inject(
|
||||||
.it('should use the injector of the lazily-loaded configuration',
|
[Router, Location, NgModuleFactoryLoader],
|
||||||
fakeAsync(inject(
|
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||||
[Router, Location, NgModuleFactoryLoader],
|
loader.stubbedModules = {expected: LoadedModule};
|
||||||
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
|
||||||
loader.stubbedModules = {expected: LoadedModule};
|
|
||||||
|
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'eager-parent',
|
path: 'eager-parent',
|
||||||
component: EagerParentComponent,
|
component: EagerParentComponent,
|
||||||
children: [{path: 'lazy', loadChildren: 'expected'}]
|
children: [{path: 'lazy', loadChildren: 'expected'}]
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
|
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
|
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
|
||||||
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
|
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works when given a callback',
|
it('works when given a callback',
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
import {Compiler, Component, NgModule, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
|
import {Compiler, Component, NgModule, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
|
||||||
import {TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
import {TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||||
import {fixmeIvy} from '@angular/private/testing';
|
|
||||||
import {PreloadAllModules, PreloadingStrategy, RouterPreloader} from '@angular/router';
|
import {PreloadAllModules, PreloadingStrategy, RouterPreloader} from '@angular/router';
|
||||||
|
|
||||||
import {Route, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterModule} from '../index';
|
import {Route, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterModule} from '../index';
|
||||||
@ -60,66 +59,63 @@ describe('RouterPreloader', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef],
|
||||||
|
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router,
|
||||||
|
testModule: NgModuleRef<any>) => {
|
||||||
|
const events: Array<RouteConfigLoadStart|RouteConfigLoadEnd> = [];
|
||||||
|
@NgModule({
|
||||||
|
declarations: [LazyLoadedCmp],
|
||||||
|
imports:
|
||||||
|
[RouterModule.forChild([{path: 'LoadedModule2', component: LazyLoadedCmp}])]
|
||||||
|
})
|
||||||
|
class LoadedModule2 {
|
||||||
|
}
|
||||||
|
|
||||||
fixmeIvy(
|
@NgModule({
|
||||||
'FW-765: NgModuleRef hierarchy is differently constructed when the router preloads modules')
|
imports:
|
||||||
.it('should work',
|
[RouterModule.forChild([{path: 'LoadedModule1', loadChildren: 'expected2'}])]
|
||||||
fakeAsync(inject(
|
})
|
||||||
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef],
|
class LoadedModule1 {
|
||||||
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router,
|
}
|
||||||
testModule: NgModuleRef<any>) => {
|
|
||||||
const events: Array<RouteConfigLoadStart|RouteConfigLoadEnd> = [];
|
|
||||||
@NgModule({
|
|
||||||
declarations: [LazyLoadedCmp],
|
|
||||||
imports: [RouterModule.forChild(
|
|
||||||
[{path: 'LoadedModule2', component: LazyLoadedCmp}])]
|
|
||||||
})
|
|
||||||
class LoadedModule2 {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
router.events.subscribe(e => {
|
||||||
imports: [RouterModule.forChild(
|
if (e instanceof RouteConfigLoadEnd || e instanceof RouteConfigLoadStart) {
|
||||||
[{path: 'LoadedModule1', loadChildren: 'expected2'}])]
|
events.push(e);
|
||||||
})
|
}
|
||||||
class LoadedModule1 {
|
});
|
||||||
}
|
|
||||||
|
|
||||||
router.events.subscribe(e => {
|
loader.stubbedModules = {
|
||||||
if (e instanceof RouteConfigLoadEnd || e instanceof RouteConfigLoadStart) {
|
expected: LoadedModule1,
|
||||||
events.push(e);
|
expected2: LoadedModule2,
|
||||||
}
|
};
|
||||||
});
|
|
||||||
|
|
||||||
loader.stubbedModules = {
|
preloader.preload().subscribe(() => {});
|
||||||
expected: LoadedModule1,
|
|
||||||
expected2: LoadedModule2,
|
|
||||||
};
|
|
||||||
|
|
||||||
preloader.preload().subscribe(() => {});
|
tick();
|
||||||
|
|
||||||
tick();
|
const c = router.config;
|
||||||
|
expect(c[0].loadChildren).toEqual('expected');
|
||||||
|
|
||||||
const c = router.config;
|
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
|
||||||
expect(c[0].loadChildren).toEqual('expected');
|
const module: any = loadedConfig.module;
|
||||||
|
expect(loadedConfig.routes[0].path).toEqual('LoadedModule1');
|
||||||
|
expect(module._parent).toBe(testModule);
|
||||||
|
|
||||||
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
|
const loadedConfig2: LoadedRouterConfig =
|
||||||
const module: any = loadedConfig.module;
|
(loadedConfig.routes[0] as any)._loadedConfig !;
|
||||||
expect(loadedConfig.routes[0].path).toEqual('LoadedModule1');
|
const module2: any = loadedConfig2.module;
|
||||||
expect(module._parent).toBe(testModule);
|
expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2');
|
||||||
|
expect(module2._parent).toBe(module);
|
||||||
|
|
||||||
const loadedConfig2: LoadedRouterConfig =
|
expect(events.map(e => e.toString())).toEqual([
|
||||||
(loadedConfig.routes[0] as any)._loadedConfig !;
|
'RouteConfigLoadStart(path: lazy)',
|
||||||
const module2: any = loadedConfig2.module;
|
'RouteConfigLoadEnd(path: lazy)',
|
||||||
expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2');
|
'RouteConfigLoadStart(path: LoadedModule1)',
|
||||||
expect(module2._parent).toBe(module);
|
'RouteConfigLoadEnd(path: LoadedModule1)',
|
||||||
|
]);
|
||||||
expect(events.map(e => e.toString())).toEqual([
|
})));
|
||||||
'RouteConfigLoadStart(path: lazy)',
|
|
||||||
'RouteConfigLoadEnd(path: lazy)',
|
|
||||||
'RouteConfigLoadStart(path: LoadedModule1)',
|
|
||||||
'RouteConfigLoadEnd(path: LoadedModule1)',
|
|
||||||
]);
|
|
||||||
})));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should support modules that have already been loaded', () => {
|
describe('should support modules that have already been loaded', () => {
|
||||||
@ -130,60 +126,57 @@ describe('RouterPreloader', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy(
|
it('should work', fakeAsync(inject(
|
||||||
'FW-765: NgModuleRef hierarchy is differently constructed when the router preloads modules')
|
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef, Compiler],
|
||||||
.it('should work',
|
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader,
|
||||||
fakeAsync(inject(
|
router: Router, testModule: NgModuleRef<any>, compiler: Compiler) => {
|
||||||
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef, Compiler],
|
@NgModule()
|
||||||
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router,
|
class LoadedModule2 {
|
||||||
testModule: NgModuleRef<any>, compiler: Compiler) => {
|
}
|
||||||
@NgModule()
|
|
||||||
class LoadedModule2 {
|
|
||||||
}
|
|
||||||
|
|
||||||
const module2 = compiler.compileModuleSync(LoadedModule2).create(null);
|
const module2 = compiler.compileModuleSync(LoadedModule2).create(null);
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild([
|
imports: [RouterModule.forChild([
|
||||||
<Route>{
|
<Route>{
|
||||||
path: 'LoadedModule2',
|
path: 'LoadedModule2',
|
||||||
loadChildren: 'no',
|
loadChildren: 'no',
|
||||||
_loadedConfig: {
|
_loadedConfig: {
|
||||||
routes: [{path: 'LoadedModule3', loadChildren: 'expected3'}],
|
routes: [{path: 'LoadedModule3', loadChildren: 'expected3'}],
|
||||||
module: module2,
|
module: module2,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
])]
|
])]
|
||||||
})
|
})
|
||||||
class LoadedModule1 {
|
class LoadedModule1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({imports: [RouterModule.forChild([])]})
|
@NgModule({imports: [RouterModule.forChild([])]})
|
||||||
class LoadedModule3 {
|
class LoadedModule3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.stubbedModules = {
|
loader.stubbedModules = {
|
||||||
expected: LoadedModule1,
|
expected: LoadedModule1,
|
||||||
expected3: LoadedModule3,
|
expected3: LoadedModule3,
|
||||||
};
|
};
|
||||||
|
|
||||||
preloader.preload().subscribe(() => {});
|
preloader.preload().subscribe(() => {});
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const c = router.config;
|
const c = router.config;
|
||||||
|
|
||||||
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
|
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
|
||||||
const module: any = loadedConfig.module;
|
const module: any = loadedConfig.module;
|
||||||
expect(module._parent).toBe(testModule);
|
expect(module._parent).toBe(testModule);
|
||||||
|
|
||||||
const loadedConfig2: LoadedRouterConfig =
|
const loadedConfig2: LoadedRouterConfig =
|
||||||
(loadedConfig.routes[0] as any)._loadedConfig !;
|
(loadedConfig.routes[0] as any)._loadedConfig !;
|
||||||
const loadedConfig3: LoadedRouterConfig =
|
const loadedConfig3: LoadedRouterConfig =
|
||||||
(loadedConfig2.routes[0] as any)._loadedConfig !;
|
(loadedConfig2.routes[0] as any)._loadedConfig !;
|
||||||
const module3: any = loadedConfig3.module;
|
const module3: any = loadedConfig3.module;
|
||||||
expect(module3._parent).toBe(module2);
|
expect(module3._parent).toBe(module2);
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should ignore errors', () => {
|
describe('should ignore errors', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user