feat(browser): use AppModules for bootstrap in the browser

This introduces the `BrowserModule` to be used for long form
bootstrap and offline compile bootstrap:

```
@AppModule({
  modules: [BrowserModule],
  precompile: [MainComponent],
  providers: […], // additional providers
  directives: […], // additional platform directives
  pipes: […] // additional platform pipes
})
class MyModule {
  constructor(appRef: ApplicationRef) {
    appRef.bootstrap(MainComponent);
  }
}

// offline compile
import {bootstrapModuleFactory} from ‘@angular/platform-browser’;
bootstrapModuleFactory(MyModuleNgFactory);

// runtime compile long form
import {bootstrapModule} from ‘@angular/platform-browser-dynamic’;
bootstrapModule(MyModule);
```

The short form, `bootstrap(...)`, can now creates a module on the fly,
given `directives`, `pipes, `providers`, `precompile` and `modules`
properties.

Related changes:
- make `SanitizationService`, `SecurityContext` public in `@angular/core` so that the offline compiler can resolve the token
- move `AnimationDriver` to `platform-browser` and make it
  public so that the offline compiler can resolve the token

BREAKING CHANGES:
- short form bootstrap does no longer allow
  to inject compiler internals (i.e. everything 
  from `@angular/compiler). Inject `Compiler` instead.
  To provide custom providers for the compiler,
  create a custom compiler via `browserCompiler({providers: [...]})`
  and pass that into the `bootstrap` method.
This commit is contained in:
Tobias Bosch
2016-06-30 13:07:17 -07:00
parent 74b45dfbf8
commit 3f55aa609f
71 changed files with 793 additions and 406 deletions

View File

@ -6,12 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {APP_INITIALIZER, Component, Directive, ExceptionHandler, Inject, OnDestroy, PLATFORM_INITIALIZER, ReflectiveInjector, coreLoadAndBootstrap, createPlatform, provide} from '@angular/core';
import {LowerCasePipe, NgIf} from '@angular/common';
import {XHR} from '@angular/compiler';
import {APP_INITIALIZER, Component, Directive, ExceptionHandler, Inject, OnDestroy, PLATFORM_DIRECTIVES, PLATFORM_INITIALIZER, PLATFORM_PIPES, ReflectiveInjector, coreLoadAndBootstrap, createPlatform, provide} from '@angular/core';
import {ApplicationRef, disposePlatform} from '@angular/core/src/application_ref';
import {Console} from '@angular/core/src/console';
import {ComponentRef} from '@angular/core/src/linker/component_factory';
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {ComponentFixture} from '@angular/core/testing';
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it} from '@angular/core/testing/testing_internal';
import {BROWSER_APP_PROVIDERS, BROWSER_PLATFORM_PROVIDERS} from '@angular/platform-browser';
import {BROWSER_APP_COMPILER_PROVIDERS, bootstrap} from '@angular/platform-browser-dynamic';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -69,6 +72,19 @@ class HelloOnDestroyTickCmp implements OnDestroy {
ngOnDestroy(): void { this.appRef.tick(); }
}
@Component({selector: 'hello-app', templateUrl: './sometemplate.html'})
class HelloUrlCmp {
greeting = 'hello';
}
@Component({
selector: 'hello-app',
template: `<div [title]="'HELLO' | lowercase"></div><div *ngIf="show"></div>`
})
class HelloCmpUsingPlatformDirectiveAndPipe {
show: boolean = false;
}
class _ArrayLogger {
res: any[] = [];
log(s: any): void { this.res.push(s); }
@ -108,15 +124,9 @@ export function main() {
afterEach(disposePlatform);
it('should throw if bootstrapped Directive is not a Component', () => {
var logger = new _ArrayLogger();
var exceptionHandler = new ExceptionHandler(logger, false);
expect(
() => bootstrap(
HelloRootDirectiveIsNotCmp,
[testProviders, {provide: ExceptionHandler, useValue: exceptionHandler}]))
expect(() => bootstrap(HelloRootDirectiveIsNotCmp))
.toThrowError(
`Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
expect(logger.res.join('')).toContain('Could not compile');
});
it('should throw if no element is found',
@ -270,5 +280,40 @@ export function main() {
});
});
}));
// Note: This will soon be deprecated as bootstrap creates a separate injector for the compiler,
// i.e. such providers needs to go into that injecotr (when calling `browserCompiler`);
it('should still allow to provide a custom xhr via the regular providers',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let spyXhr: XHR = {get: (url: string) => Promise.resolve('{{greeting}} world!')};
bootstrap(HelloUrlCmp, testProviders.concat([{provide: XHR, useValue: spyXhr}]))
.then((compRef) => {
expect(el).toHaveText('hello world!');
async.done();
});
}));
// Note: This will soon be deprecated as bootstrap creates a separate injector for the compiler,
// i.e. such providers needs to go into that injecotr (when calling `browserCompiler`);
it('should still allow to provide platform directives/pipes via the regular providers',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
bootstrap(HelloCmpUsingPlatformDirectiveAndPipe, testProviders.concat([
{provide: PLATFORM_DIRECTIVES, useValue: [NgIf]},
{provide: PLATFORM_PIPES, useValue: [LowerCasePipe]}
])).then((compRef) => {
let compFixture = new ComponentFixture(compRef, null, null);
let el = compFixture.debugElement;
// Test that ngIf works
expect(el.children.length).toBe(1);
compFixture.componentInstance.show = true;
compFixture.detectChanges();
expect(el.children.length).toBe(2);
// Test that lowercase pipe works
expect(el.children[0].properties['title']).toBe('hello');
async.done();
});
}));
});
}