feat(ivy): Forward refs now supported (#27439)
Adds deferred execution of scope setting for modules such that forward refs can be supported in ivy. Design docs can be found at https://docs.google.com/document/d/11KTbybis9rt0cZgMKd1wo_IKb6y1PMU-RoTDVLTXK4Y/edit# PR Close #27439
This commit is contained in:
parent
2bc39860bb
commit
cd858323f2
|
@ -24,7 +24,6 @@ jasmine_node_test(
|
|||
name = "test",
|
||||
bootstrap = ["angular/tools/testing/init_node_spec.js"],
|
||||
tags = [
|
||||
"fixme-ivy-aot",
|
||||
"ivy-only",
|
||||
],
|
||||
deps = [
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
import {Injectable, InjectionToken, Injector, NgModule, createInjector, forwardRef} from '@angular/core';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {AOT_TOKEN, AotModule, AotService} from 'app_built/src/module';
|
||||
|
||||
describe('Ivy NgModule', () => {
|
||||
|
@ -41,24 +40,23 @@ describe('Ivy NgModule', () => {
|
|||
|
||||
it('works', () => { createInjector(JitAppModule); });
|
||||
|
||||
fixmeIvy('FW-645: jit doesn\'t support forwardRefs')
|
||||
.it('throws an error on circular module dependencies', () => {
|
||||
@NgModule({
|
||||
imports: [forwardRef(() => BModule)],
|
||||
})
|
||||
class AModule {
|
||||
}
|
||||
it('throws an error on circular module dependencies', () => {
|
||||
@NgModule({
|
||||
imports: [forwardRef(() => BModule)],
|
||||
})
|
||||
class AModule {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [AModule],
|
||||
})
|
||||
class BModule {
|
||||
}
|
||||
@NgModule({
|
||||
imports: [AModule],
|
||||
})
|
||||
class BModule {
|
||||
}
|
||||
|
||||
expect(() => createInjector(AModule))
|
||||
.toThrowError(
|
||||
'Circular dependency in DI detected for type AModule. Dependency path: AModule > BModule > AModule.');
|
||||
});
|
||||
expect(() => createInjector(AModule))
|
||||
.toThrowError(
|
||||
'Circular dependency in DI detected for type AModule. Dependency path: AModule > BModule > AModule.');
|
||||
});
|
||||
|
||||
it('merges imports and exports', () => {
|
||||
const TOKEN = new InjectionToken<string>('TOKEN');
|
||||
|
@ -84,4 +82,4 @@ describe('Ivy NgModule', () => {
|
|||
expect(injector.get(TOKEN)).toEqual('provided from B');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF} from '../fields';
|
|||
import {R3DirectiveMetadataFacade, getCompilerFacade} from './compiler_facade';
|
||||
import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from './compiler_facade_interface';
|
||||
import {angularCoreEnv} from './environment';
|
||||
import {patchComponentDefWithScope, transitiveScopesFor} from './module';
|
||||
import {flushModuleScopingQueueAsMuchAsPossible, patchComponentDefWithScope, transitiveScopesFor} from './module';
|
||||
import {getReflect, reflectDependencies} from './util';
|
||||
|
||||
|
||||
|
@ -51,6 +51,7 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
|||
error.push(`Did you run and wait for 'resolveComponentResources()'?`);
|
||||
throw new Error(error.join('\n'));
|
||||
}
|
||||
|
||||
const meta: R3ComponentMetadataFacade = {
|
||||
...directiveMetadata(type, metadata),
|
||||
template: metadata.template || '',
|
||||
|
@ -67,6 +68,13 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
|||
ngComponentDef = compiler.compileComponent(
|
||||
angularCoreEnv, `ng://${stringify(type)}/template.html`, meta);
|
||||
|
||||
// When NgModule decorator executed, we enqueued the module definition such that
|
||||
// it would only dequeue and add itself as module scope to all of its declarations,
|
||||
// but only if if all of its declarations had resolved. This call runs the check
|
||||
// to see if any modules that are in the queue can be dequeued and add scope to
|
||||
// their declarations.
|
||||
flushModuleScopingQueueAsMuchAsPossible();
|
||||
|
||||
// If component compilation is async, then the @NgModule annotation which declares the
|
||||
// component may execute and set an ngSelectorScope property on the component type. This
|
||||
// allows the component to patch itself with directiveDefs from the module after it
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {resolveForwardRef} from '../../di/forward_ref';
|
||||
import {ModuleWithProviders, NgModule, NgModuleDef, NgModuleTransitiveScopes} from '../../metadata/ng_module';
|
||||
import {Type} from '../../type';
|
||||
import {assertDefined} from '../assert';
|
||||
|
@ -19,6 +20,55 @@ import {reflectDependencies} from './util';
|
|||
|
||||
const EMPTY_ARRAY: Type<any>[] = [];
|
||||
|
||||
interface ModuleQueueItem {
|
||||
moduleType: Type<any>;
|
||||
ngModule: NgModule;
|
||||
}
|
||||
|
||||
const moduleQueue: ModuleQueueItem[] = [];
|
||||
|
||||
/**
|
||||
* Enqueues moduleDef to be checked later to see if scope can be set on its
|
||||
* component declarations.
|
||||
*/
|
||||
function enqueueModuleForDelayedScoping(moduleType: Type<any>, ngModule: NgModule) {
|
||||
moduleQueue.push({moduleType, ngModule});
|
||||
}
|
||||
|
||||
let flushingModuleQueue = false;
|
||||
/**
|
||||
* Loops over queued module definitions, if a given module definition has all of its
|
||||
* declarations resolved, it dequeues that module definition and sets the scope on
|
||||
* its declarations.
|
||||
*/
|
||||
export function flushModuleScopingQueueAsMuchAsPossible() {
|
||||
if (!flushingModuleQueue) {
|
||||
flushingModuleQueue = true;
|
||||
for (let i = moduleQueue.length - 1; i >= 0; i--) {
|
||||
const {moduleType, ngModule} = moduleQueue[i];
|
||||
|
||||
if (ngModule.declarations && ngModule.declarations.every(isResolvedDeclaration)) {
|
||||
// dequeue
|
||||
moduleQueue.splice(i, 1);
|
||||
setScopeOnDeclaredComponents(moduleType, ngModule);
|
||||
}
|
||||
}
|
||||
flushingModuleQueue = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns truthy if a declaration has resolved. If the declaration happens to be
|
||||
* an array of declarations, it will recurse to check each declaration in that array
|
||||
* (which may also be arrays).
|
||||
*/
|
||||
function isResolvedDeclaration(declaration: any[] | Type<any>): boolean {
|
||||
if (Array.isArray(declaration)) {
|
||||
return declaration.every(isResolvedDeclaration);
|
||||
}
|
||||
return !!resolveForwardRef(declaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a module in JIT mode.
|
||||
*
|
||||
|
@ -26,7 +76,12 @@ const EMPTY_ARRAY: Type<any>[] = [];
|
|||
*/
|
||||
export function compileNgModule(moduleType: Type<any>, ngModule: NgModule = {}): void {
|
||||
compileNgModuleDefs(moduleType, ngModule);
|
||||
setScopeOnDeclaredComponents(moduleType, ngModule);
|
||||
|
||||
// Because we don't know if all declarations have resolved yet at the moment the
|
||||
// NgModule decorator is executing, we're enqueueing the setting of module scope
|
||||
// on its declarations to be run at a later time when all declarations for the module,
|
||||
// including forward refs, have resolved.
|
||||
enqueueModuleForDelayedScoping(moduleType, ngModule);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -205,8 +205,8 @@ ivyEnabled && describe('render3 jit', () => {
|
|||
}
|
||||
|
||||
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
|
||||
expect(cmpDef.directiveDefs instanceof Function).toBe(true);
|
||||
expect((cmpDef.directiveDefs as Function)()).toEqual([cmpDef]);
|
||||
// directive defs are still null, since no directives were in that component
|
||||
expect(cmpDef.directiveDefs).toBeNull();
|
||||
});
|
||||
|
||||
it('should add hostbindings and hostlisteners', () => {
|
||||
|
|
Loading…
Reference in New Issue