Skip to content

Commit ed1c326

Browse files
committed
feat(core): Add NgModule, use it for angular1Module bundling
BREAKING CHANGE: bundle() now takes an NgModule decorated class as its first argument instead of a Component. **Before:** ```ts import { Component, bundle } from 'ng-metadata/core' @component({ selector: 'foo', template: '<h1>Foo!</h1>' }) class FooComponent {} const angular1Module = bundle(FooComponent) ``` **After:** ```ts import { NgModule, Component, bundle } from 'ng-metadata/core' @component({ selector: 'foo', template: '<h1>Foo!</h1>' }) class FooComponent {} @NgModule({ declarations: [FooComponent] }) class FooModule {} const angular1Module = bundle(FooModule) ``` BREAKING CHANGE: bootstrapping is now done on an NgModule, not on a Component. **Before:** ```ts import { bootstrap, Component } from 'ng-metadata/core' @component({ selector: 'app' }) class AppComponent {} bootstrap(AppComponent) ``` **After:** ```ts import { platformBrowserDynamic } from 'ng-metadata/platform-browser-dynamic' import { NgModule, Component } from 'ng-metadata/core' @component({ selector: 'app' }) class AppComponent {} @NgModule({ declarations: [AppComponent] }) class AppModule {} platformBrowserDynamic().bootstrapModule(AppModule) ```
1 parent 13c29ee commit ed1c326

12 files changed

+284
-129
lines changed

core.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { bundle } from './src/core/util'
33
export {
44
Directive,
55
Component,
6+
NgModule,
67
Attr,
78
Input,
89
Output,

src/core/di/provider_util.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isString, isPresent } from '../../facade/lang';
2-
import { DirectiveMetadata, ComponentMetadata } from '../directives/metadata_directives';
2+
import { DirectiveMetadata, ComponentMetadata, NgModuleMetadata } from '../directives/metadata_directives';
33
import { PipeMetadata } from '../pipes/metadata';
44

55
import { Provider } from './provider';
@@ -50,3 +50,7 @@ export function isPipe(annotation: any): annotation is PipeMetadata {
5050
export function isInjectMetadata( injectMeta: any ): injectMeta is InjectMetadata {
5151
return injectMeta instanceof InjectMetadata;
5252
}
53+
54+
export function isNgModule( annotation: any ): annotation is NgModuleMetadata {
55+
return isPresent( annotation.declarations ) && annotation instanceof NgModuleMetadata
56+
}

src/core/di/reflective_provider.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function resolveReflectiveProvider( provider: Provider ): {method: string
5050
* @returns {any}
5151
* @private
5252
*/
53-
export function _getNgModuleMetadataByType( injectable: Type ): { providerName: string, providerMethod: string, moduleMethod: string} {
53+
export function _getAngular1ModuleMetadataByType( injectable: Type ): { providerName: string, providerMethod: string, moduleMethod: string} {
5454
// only the first class annotations is injectable
5555
const [annotation] = reflector.annotations( injectable );
5656

@@ -135,7 +135,7 @@ export function _normalizeProviders(
135135
// const provider = createProvider( {provide:b, useClass:b} );
136136
// const { method, name, value } = resolveReflectiveProvider( provider );
137137
const [name,value] = provide( providerType );
138-
const { providerName, providerMethod, moduleMethod } = _getNgModuleMetadataByType( providerType );
138+
const { providerName, providerMethod, moduleMethod } = _getAngular1ModuleMetadataByType( providerType );
139139

140140
// config phase support
141141
if ( isType( name ) ) {

src/core/directives/decorators.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
OutputMetadata,
1010
HostBindingMetadata,
1111
HostListenerMetadata,
12-
LegacyDirectiveDefinition
12+
LegacyDirectiveDefinition,
13+
NgModuleMetadataType,
14+
NgModuleMetadata
1315
} from './metadata_directives';
1416
import { ChangeDetectionStrategy } from '../change_detection/constants';
1517

@@ -28,6 +30,15 @@ export interface DirectiveDecorator extends TypeDecorator {}
2830
*/
2931
export interface ComponentDecorator extends DirectiveDecorator {}
3032

33+
/**
34+
* Interface for the {@link NgModuleMetadata} decorator function.
35+
*
36+
* See {@link NgModuleMetadataFactory}.
37+
*
38+
* @stable
39+
*/
40+
export interface NgModuleDecorator extends TypeDecorator {}
41+
3142
/**
3243
* {@link DirectiveMetadata} factory for creating annotations, decorators.
3344
*
@@ -194,6 +205,16 @@ export interface HostListenerMetadataFactory {
194205
new (eventName: string, args?: string[]): any;
195206
}
196207

208+
/**
209+
* {@link NgModuleMetadata} factory for creating annotations, decorators or DSL.
210+
*
211+
* @experimental
212+
*/
213+
export interface NgModuleMetadataFactory {
214+
(obj?: NgModuleMetadataType): NgModuleDecorator;
215+
new (obj?: NgModuleMetadataType): NgModuleMetadata;
216+
}
217+
197218

198219
export const Component: ComponentMetadataFactory = makeDecorator(ComponentMetadata) as ComponentMetadataFactory;
199220

@@ -221,3 +242,10 @@ export const Output: OutputMetadataFactory = makePropDecorator(OutputMetadata);
221242
export const HostBinding: HostBindingMetadataFactory = makePropDecorator(HostBindingMetadata);
222243

223244
export const HostListener: HostListenerMetadataFactory = makePropDecorator(HostListenerMetadata);
245+
246+
/**
247+
* Declares an ng module.
248+
* @experimental
249+
* @Annotation
250+
*/
251+
export const NgModule: NgModuleMetadataFactory = makeDecorator(NgModuleMetadata) as NgModuleMetadataFactory;

src/core/directives/metadata_directives.ts

+35
Original file line numberDiff line numberDiff line change
@@ -1225,3 +1225,38 @@ export class HostBindingMetadata {
12251225
export class HostListenerMetadata {
12261226
constructor(public eventName: string, public args?: string[]) {}
12271227
}
1228+
1229+
/**
1230+
* Interface for creating NgModuleMetadata
1231+
*/
1232+
export interface NgModuleMetadataType {
1233+
providers?: any[]; // Decorated providers
1234+
declarations?: Array<Type|Type[]>; // Decorated Components, Directives or Pipes
1235+
imports?: Array<Type|string>; // Other NgModules or string names of Angular 1 modules
1236+
exports?: Array<Type|any[]>; // Not used, only here for interface compatibility
1237+
entryComponents?: Array<Type|any[]>; // Not used, only here for interface compatibility
1238+
bootstrap?: Array<Type|any[]>; // Not used, only here for interface compatibility
1239+
schemas?: Array<any[]>; // Not used, only here for interface compatibility
1240+
}
1241+
1242+
/**
1243+
* Declares an Angular Module.
1244+
*/
1245+
export class NgModuleMetadata extends InjectableMetadata implements NgModuleMetadataType {
1246+
1247+
get providers(): any[] { return this._providers; }
1248+
private _providers: any[];
1249+
1250+
declarations: Array<Type|Type[]>;
1251+
1252+
imports: Array<Type|string>;
1253+
1254+
constructor(options: NgModuleMetadataType = {}) {
1255+
// We cannot use destructuring of the constructor argument because `exports` is a
1256+
// protected symbol in CommonJS and closure tries to aggressively optimize it away.
1257+
super();
1258+
this._providers = options.providers;
1259+
this.declarations = options.declarations;
1260+
this.imports = options.imports;
1261+
}
1262+
}

src/core/util/bundler.ts

+83-22
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,118 @@
1-
import { global } from '../../facade/lang';
1+
import { global, isFunction } from '../../facade/lang';
22
import { reflector } from '../reflection/reflection';
3-
import { ComponentMetadata } from '../directives/metadata_directives';
3+
import { ComponentMetadata, NgModuleMetadata } from '../directives/metadata_directives';
44
import { getInjectableName, provide } from '../di/provider';
5+
import { isNgModule } from '../di/provider_util';
6+
57
import {
6-
_isTypeRegistered, _normalizeProviders, _getNgModuleMetadataByType,
8+
_isTypeRegistered, _normalizeProviders, _getAngular1ModuleMetadataByType,
79
_registerTypeProvider
810
} from '../di/reflective_provider';
911
import { ListWrapper } from '../../facade/collections';
1012

11-
export function bundle( ComponentClass: Type, otherProviders: any[] = [], NgModule?: ng.IModule ): ng.IModule {
13+
function _bundleComponent( ComponentClass: Type, otherProviders: any[] = [], existingAngular1Module?: ng.IModule ): ng.IModule {
1214

1315
// Support registering downgraded ng2 components directly
1416
const downgradedNgComponentName = reflector.downgradedNg2ComponentName( ComponentClass );
1517
if (downgradedNgComponentName) {
16-
const ngModule = NgModule || global.angular.module( downgradedNgComponentName, [] );
17-
ngModule.directive( downgradedNgComponentName, ComponentClass );
18-
return ngModule;
18+
const angular1Module = existingAngular1Module || global.angular.module( downgradedNgComponentName, [] );
19+
angular1Module.directive( downgradedNgComponentName, ComponentClass );
20+
return angular1Module;
1921
}
2022

21-
const ngModuleName = getInjectableName( ComponentClass );
22-
const ngModule = NgModule || global.angular.module( ngModuleName, [] );
23+
const angular1ModuleName = getInjectableName( ComponentClass );
24+
const angular1Module = existingAngular1Module || global.angular.module( angular1ModuleName, [] );
2325
const annotations = reflector.annotations( ComponentClass );
2426
const cmpAnnotation: ComponentMetadata = annotations[ 0 ];
2527
const { directives = [], pipes = [], providers = [], viewProviders = [] }={} = cmpAnnotation;
2628

2729
// process component
2830
const [cmpName,cmpFactoryFn] = provide( ComponentClass );
29-
const { providerName, providerMethod, moduleMethod } = _getNgModuleMetadataByType( ComponentClass );
31+
const { providerName, providerMethod, moduleMethod } = _getAngular1ModuleMetadataByType( ComponentClass );
3032

31-
if ( _isTypeRegistered( cmpName, ngModule, providerName, providerMethod ) ) {
32-
return ngModule;
33+
if ( _isTypeRegistered( cmpName, angular1Module, providerName, providerMethod ) ) {
34+
return angular1Module;
3335
}
3436

3537
// @TODO register via this once requires are resolved for 3 types of attr directive from template
36-
// _registerTypeProvider( ngModule, ComponentClass, { moduleMethod, name: cmpName, value: cmpFactoryFn } );
37-
ngModule[moduleMethod]( cmpName, cmpFactoryFn );
38+
// _registerTypeProvider( angular1Module, ComponentClass, { moduleMethod, name: cmpName, value: cmpFactoryFn } );
39+
angular1Module[moduleMethod]( cmpName, cmpFactoryFn );
3840

3941
// 1. process component/directive decorator providers/viewProviders/pipes
40-
_normalizeProviders( ngModule, providers );
41-
_normalizeProviders( ngModule, viewProviders );
42-
_normalizeProviders( ngModule, pipes );
42+
_normalizeProviders( angular1Module, providers );
43+
_normalizeProviders( angular1Module, viewProviders );
44+
_normalizeProviders( angular1Module, pipes );
4345

4446

4547
// step through all directives
4648
ListWrapper.flattenDeep(directives).forEach( ( directiveType: Type ) => {
47-
bundle( directiveType, [], ngModule );
49+
_bundleComponent( directiveType, [], angular1Module );
4850
} );
4951

5052
// 2. process otherProviders argument
51-
// - providers can be string(ngModule reference), Type, StringMap(providerLiteral)
53+
// - providers can be string(angular1Module reference), Type, StringMap(providerLiteral)
5254
// - directives can't be registered as via global providers only @Injectable,@Pipe,{provide:any,use*:any}
53-
// registerProviders(ngModule, otherProviders);
54-
_normalizeProviders( ngModule, otherProviders );
55+
// registerProviders(angular1Module, otherProviders);
56+
_normalizeProviders( angular1Module, otherProviders );
57+
58+
return angular1Module;
59+
}
60+
61+
export function bundle( NgModuleClass: Type, otherProviders: any[] = [], existingAngular1Module?: ng.IModule ): ng.IModule {
62+
63+
const angular1ModuleName = getInjectableName( NgModuleClass );
64+
const angular1Module = existingAngular1Module || global.angular.module( angular1ModuleName, [] );
65+
const annotations = reflector.annotations( NgModuleClass );
66+
const ngModuleAnnotation: NgModuleMetadata = annotations[ 0 ];
67+
if (!isNgModule(ngModuleAnnotation)) {
68+
throw new Error(`bundle() requires an @NgModule as it's first argument`)
69+
}
70+
const { declarations = [], providers = [], imports = [] }={} = ngModuleAnnotation;
71+
72+
/**
73+
* Process `declarations`
74+
*/
75+
ListWrapper.flattenDeep(declarations).forEach( ( directiveType: Type ) => {
76+
_bundleComponent( directiveType, [], angular1Module );
77+
} );
78+
79+
/**
80+
* Process `providers`
81+
*/
82+
_normalizeProviders( angular1Module, providers );
83+
84+
/**
85+
* Process `imports`
86+
*/
87+
88+
// 1. imports which are not NgModules
89+
const nonNgModuleImports: any[] = imports.filter((imported) => {
90+
if (!isFunction(imported)) {
91+
return true
92+
}
93+
const annotations = reflector.annotations( imported );
94+
return !isNgModule(ngModuleAnnotation)
95+
})
96+
97+
_normalizeProviders( angular1Module, nonNgModuleImports );
98+
99+
// 2.imports which are NgModules
100+
const NgModuleImports: any[] = imports.filter((imported) => {
101+
if (!isFunction(imported)) {
102+
return false
103+
}
104+
const annotations = reflector.annotations( imported );
105+
return isNgModule(ngModuleAnnotation)
106+
})
107+
108+
NgModuleImports.forEach(( importedNgModule: Type ) => {
109+
bundle(importedNgModule, [], angular1Module)
110+
})
111+
112+
/**
113+
* Process `otherProviders`
114+
*/
115+
_normalizeProviders( angular1Module, otherProviders );
55116

56-
return ngModule;
117+
return angular1Module;
57118
}

src/platform/browser.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { createBootstrapFn } from './browser_utils'
22

3-
export const bootstrap = createBootstrapFn()
4-
53
export * from './title';
4+
5+
export const platformBrowserDynamic = () => {
6+
return {
7+
bootstrapModule: createBootstrapFn(),
8+
}
9+
}
10+

src/platform/browser_utils.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@ export function createBootstrapFn(bootstrapFn: Function = angular.bootstrap.bind
2222

2323
/**
2424
* bootstrap angular app
25-
* @param {Type} rootComponent
25+
* @param {Type} NgModule
2626
* @param {Array<any>} providers
2727
*/
28-
return function bootstrap(
29-
rootComponent: Type,
30-
providers: any[]
28+
return function bootstrapModule(
29+
NgModule: Type
3130
) {
3231

33-
const ngModule = bundle( rootComponent, providers );
34-
const ngModuleName = ngModule.name;
32+
const angular1Module = bundle( NgModule );
33+
const angular1ModuleName = angular1Module.name;
3534
const strictDi = true;
3635
const element = document;
3736

@@ -40,13 +39,13 @@ export function createBootstrapFn(bootstrapFn: Function = angular.bootstrap.bind
4039
'Angular is running in the development mode. Call enableProdMode() to enable the production mode.'
4140
);
4241
} else {
43-
angular.module( ngModuleName ).config( prodModeConfig );
42+
angular.module( angular1ModuleName ).config( prodModeConfig );
4443
}
4544

4645
const appRoot = _getAppRoot( element );
4746

4847
angular.element( document ).ready( ()=> {
49-
bootstrapFn( appRoot, [ ngModuleName ], {
48+
bootstrapFn( appRoot, [ angular1ModuleName ], {
5049
strictDi
5150
} )
5251
} );

src/upgrade/upgrade_adapter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { UpgradeAdapter, UpgradeAdapterInstance } from './upgrade';
2-
import { createBootstrapFn } from '../platform/browser_utils';
2+
// import { createBootstrapFn } from '../platform/browser_utils';
33
import { reflector } from '../core/reflection/reflection';
44
import { getInjectableName, OpaqueToken } from '../core/di';
55
import { ProviderLiteral } from '../core/di/provider_util';
@@ -22,7 +22,7 @@ export class NgMetadataUpgradeAdapter {
2222
*
2323
* E.g. `upgradeAdapter.bootstrap(AppComponent, providers)`
2424
*/
25-
this.bootstrap = createBootstrapFn(this._upgradeAdapter.bootstrap.bind(this._upgradeAdapter));
25+
// this.bootstrap = createBootstrapFn(this._upgradeAdapter.bootstrap.bind(this._upgradeAdapter));
2626
}
2727

2828
/**

0 commit comments

Comments
 (0)