Skip to content

Commit

Permalink
feat(core): Add NgModule, use it for angular1Module bundling
Browse files Browse the repository at this point in the history
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)
```
  • Loading branch information
JamesHenry committed Sep 19, 2016
1 parent 13c29ee commit ed1c326
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 129 deletions.
1 change: 1 addition & 0 deletions core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { bundle } from './src/core/util'
export {
Directive,
Component,
NgModule,
Attr,
Input,
Output,
Expand Down
6 changes: 5 additions & 1 deletion src/core/di/provider_util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isString, isPresent } from '../../facade/lang';
import { DirectiveMetadata, ComponentMetadata } from '../directives/metadata_directives';
import { DirectiveMetadata, ComponentMetadata, NgModuleMetadata } from '../directives/metadata_directives';
import { PipeMetadata } from '../pipes/metadata';

import { Provider } from './provider';
Expand Down Expand Up @@ -50,3 +50,7 @@ export function isPipe(annotation: any): annotation is PipeMetadata {
export function isInjectMetadata( injectMeta: any ): injectMeta is InjectMetadata {
return injectMeta instanceof InjectMetadata;
}

export function isNgModule( annotation: any ): annotation is NgModuleMetadata {
return isPresent( annotation.declarations ) && annotation instanceof NgModuleMetadata
}
4 changes: 2 additions & 2 deletions src/core/di/reflective_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function resolveReflectiveProvider( provider: Provider ): {method: string
* @returns {any}
* @private
*/
export function _getNgModuleMetadataByType( injectable: Type ): { providerName: string, providerMethod: string, moduleMethod: string} {
export function _getAngular1ModuleMetadataByType( injectable: Type ): { providerName: string, providerMethod: string, moduleMethod: string} {
// only the first class annotations is injectable
const [annotation] = reflector.annotations( injectable );

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

// config phase support
if ( isType( name ) ) {
Expand Down
30 changes: 29 additions & 1 deletion src/core/directives/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
OutputMetadata,
HostBindingMetadata,
HostListenerMetadata,
LegacyDirectiveDefinition
LegacyDirectiveDefinition,
NgModuleMetadataType,
NgModuleMetadata
} from './metadata_directives';
import { ChangeDetectionStrategy } from '../change_detection/constants';

Expand All @@ -28,6 +30,15 @@ export interface DirectiveDecorator extends TypeDecorator {}
*/
export interface ComponentDecorator extends DirectiveDecorator {}

/**
* Interface for the {@link NgModuleMetadata} decorator function.
*
* See {@link NgModuleMetadataFactory}.
*
* @stable
*/
export interface NgModuleDecorator extends TypeDecorator {}

/**
* {@link DirectiveMetadata} factory for creating annotations, decorators.
*
Expand Down Expand Up @@ -194,6 +205,16 @@ export interface HostListenerMetadataFactory {
new (eventName: string, args?: string[]): any;
}

/**
* {@link NgModuleMetadata} factory for creating annotations, decorators or DSL.
*
* @experimental
*/
export interface NgModuleMetadataFactory {
(obj?: NgModuleMetadataType): NgModuleDecorator;
new (obj?: NgModuleMetadataType): NgModuleMetadata;
}


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

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

export const HostListener: HostListenerMetadataFactory = makePropDecorator(HostListenerMetadata);

/**
* Declares an ng module.
* @experimental
* @Annotation
*/
export const NgModule: NgModuleMetadataFactory = makeDecorator(NgModuleMetadata) as NgModuleMetadataFactory;
35 changes: 35 additions & 0 deletions src/core/directives/metadata_directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1225,3 +1225,38 @@ export class HostBindingMetadata {
export class HostListenerMetadata {
constructor(public eventName: string, public args?: string[]) {}
}

/**
* Interface for creating NgModuleMetadata
*/
export interface NgModuleMetadataType {
providers?: any[]; // Decorated providers
declarations?: Array<Type|Type[]>; // Decorated Components, Directives or Pipes
imports?: Array<Type|string>; // Other NgModules or string names of Angular 1 modules
exports?: Array<Type|any[]>; // Not used, only here for interface compatibility
entryComponents?: Array<Type|any[]>; // Not used, only here for interface compatibility
bootstrap?: Array<Type|any[]>; // Not used, only here for interface compatibility
schemas?: Array<any[]>; // Not used, only here for interface compatibility
}

/**
* Declares an Angular Module.
*/
export class NgModuleMetadata extends InjectableMetadata implements NgModuleMetadataType {

get providers(): any[] { return this._providers; }
private _providers: any[];

declarations: Array<Type|Type[]>;

imports: Array<Type|string>;

constructor(options: NgModuleMetadataType = {}) {
// We cannot use destructuring of the constructor argument because `exports` is a
// protected symbol in CommonJS and closure tries to aggressively optimize it away.
super();
this._providers = options.providers;
this.declarations = options.declarations;
this.imports = options.imports;
}
}
105 changes: 83 additions & 22 deletions src/core/util/bundler.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,118 @@
import { global } from '../../facade/lang';
import { global, isFunction } from '../../facade/lang';
import { reflector } from '../reflection/reflection';
import { ComponentMetadata } from '../directives/metadata_directives';
import { ComponentMetadata, NgModuleMetadata } from '../directives/metadata_directives';
import { getInjectableName, provide } from '../di/provider';
import { isNgModule } from '../di/provider_util';

import {
_isTypeRegistered, _normalizeProviders, _getNgModuleMetadataByType,
_isTypeRegistered, _normalizeProviders, _getAngular1ModuleMetadataByType,
_registerTypeProvider
} from '../di/reflective_provider';
import { ListWrapper } from '../../facade/collections';

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

// Support registering downgraded ng2 components directly
const downgradedNgComponentName = reflector.downgradedNg2ComponentName( ComponentClass );
if (downgradedNgComponentName) {
const ngModule = NgModule || global.angular.module( downgradedNgComponentName, [] );
ngModule.directive( downgradedNgComponentName, ComponentClass );
return ngModule;
const angular1Module = existingAngular1Module || global.angular.module( downgradedNgComponentName, [] );
angular1Module.directive( downgradedNgComponentName, ComponentClass );
return angular1Module;
}

const ngModuleName = getInjectableName( ComponentClass );
const ngModule = NgModule || global.angular.module( ngModuleName, [] );
const angular1ModuleName = getInjectableName( ComponentClass );
const angular1Module = existingAngular1Module || global.angular.module( angular1ModuleName, [] );
const annotations = reflector.annotations( ComponentClass );
const cmpAnnotation: ComponentMetadata = annotations[ 0 ];
const { directives = [], pipes = [], providers = [], viewProviders = [] }={} = cmpAnnotation;

// process component
const [cmpName,cmpFactoryFn] = provide( ComponentClass );
const { providerName, providerMethod, moduleMethod } = _getNgModuleMetadataByType( ComponentClass );
const { providerName, providerMethod, moduleMethod } = _getAngular1ModuleMetadataByType( ComponentClass );

if ( _isTypeRegistered( cmpName, ngModule, providerName, providerMethod ) ) {
return ngModule;
if ( _isTypeRegistered( cmpName, angular1Module, providerName, providerMethod ) ) {
return angular1Module;
}

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

// 1. process component/directive decorator providers/viewProviders/pipes
_normalizeProviders( ngModule, providers );
_normalizeProviders( ngModule, viewProviders );
_normalizeProviders( ngModule, pipes );
_normalizeProviders( angular1Module, providers );
_normalizeProviders( angular1Module, viewProviders );
_normalizeProviders( angular1Module, pipes );


// step through all directives
ListWrapper.flattenDeep(directives).forEach( ( directiveType: Type ) => {
bundle( directiveType, [], ngModule );
_bundleComponent( directiveType, [], angular1Module );
} );

// 2. process otherProviders argument
// - providers can be string(ngModule reference), Type, StringMap(providerLiteral)
// - providers can be string(angular1Module reference), Type, StringMap(providerLiteral)
// - directives can't be registered as via global providers only @Injectable,@Pipe,{provide:any,use*:any}
// registerProviders(ngModule, otherProviders);
_normalizeProviders( ngModule, otherProviders );
// registerProviders(angular1Module, otherProviders);
_normalizeProviders( angular1Module, otherProviders );

return angular1Module;
}

export function bundle( NgModuleClass: Type, otherProviders: any[] = [], existingAngular1Module?: ng.IModule ): ng.IModule {

const angular1ModuleName = getInjectableName( NgModuleClass );
const angular1Module = existingAngular1Module || global.angular.module( angular1ModuleName, [] );
const annotations = reflector.annotations( NgModuleClass );
const ngModuleAnnotation: NgModuleMetadata = annotations[ 0 ];
if (!isNgModule(ngModuleAnnotation)) {
throw new Error(`bundle() requires an @NgModule as it's first argument`)
}
const { declarations = [], providers = [], imports = [] }={} = ngModuleAnnotation;

/**
* Process `declarations`
*/
ListWrapper.flattenDeep(declarations).forEach( ( directiveType: Type ) => {
_bundleComponent( directiveType, [], angular1Module );
} );

/**
* Process `providers`
*/
_normalizeProviders( angular1Module, providers );

/**
* Process `imports`
*/

// 1. imports which are not NgModules
const nonNgModuleImports: any[] = imports.filter((imported) => {
if (!isFunction(imported)) {
return true
}
const annotations = reflector.annotations( imported );
return !isNgModule(ngModuleAnnotation)
})

_normalizeProviders( angular1Module, nonNgModuleImports );

// 2.imports which are NgModules
const NgModuleImports: any[] = imports.filter((imported) => {
if (!isFunction(imported)) {
return false
}
const annotations = reflector.annotations( imported );
return isNgModule(ngModuleAnnotation)
})

NgModuleImports.forEach(( importedNgModule: Type ) => {
bundle(importedNgModule, [], angular1Module)
})

/**
* Process `otherProviders`
*/
_normalizeProviders( angular1Module, otherProviders );

return ngModule;
return angular1Module;
}
9 changes: 7 additions & 2 deletions src/platform/browser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { createBootstrapFn } from './browser_utils'

export const bootstrap = createBootstrapFn()

export * from './title';

export const platformBrowserDynamic = () => {
return {
bootstrapModule: createBootstrapFn(),
}
}

15 changes: 7 additions & 8 deletions src/platform/browser_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ export function createBootstrapFn(bootstrapFn: Function = angular.bootstrap.bind

/**
* bootstrap angular app
* @param {Type} rootComponent
* @param {Type} NgModule
* @param {Array<any>} providers
*/
return function bootstrap(
rootComponent: Type,
providers: any[]
return function bootstrapModule(
NgModule: Type
) {

const ngModule = bundle( rootComponent, providers );
const ngModuleName = ngModule.name;
const angular1Module = bundle( NgModule );
const angular1ModuleName = angular1Module.name;
const strictDi = true;
const element = document;

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

const appRoot = _getAppRoot( element );

angular.element( document ).ready( ()=> {
bootstrapFn( appRoot, [ ngModuleName ], {
bootstrapFn( appRoot, [ angular1ModuleName ], {
strictDi
} )
} );
Expand Down
4 changes: 2 additions & 2 deletions src/upgrade/upgrade_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UpgradeAdapter, UpgradeAdapterInstance } from './upgrade';
import { createBootstrapFn } from '../platform/browser_utils';
// import { createBootstrapFn } from '../platform/browser_utils';
import { reflector } from '../core/reflection/reflection';
import { getInjectableName, OpaqueToken } from '../core/di';
import { ProviderLiteral } from '../core/di/provider_util';
Expand All @@ -22,7 +22,7 @@ export class NgMetadataUpgradeAdapter {
*
* E.g. `upgradeAdapter.bootstrap(AppComponent, providers)`
*/
this.bootstrap = createBootstrapFn(this._upgradeAdapter.bootstrap.bind(this._upgradeAdapter));
// this.bootstrap = createBootstrapFn(this._upgradeAdapter.bootstrap.bind(this._upgradeAdapter));
}

/**
Expand Down
Loading

0 comments on commit ed1c326

Please sign in to comment.