2016-06-23 12:47:54 -04:00
/ * *
* @license
2020-05-19 15:08:49 -04:00
* Copyright Google LLC All Rights Reserved .
2016-06-23 12:47:54 -04:00
*
* Use of this source code is governed by an MIT - style license that can be
* found in the LICENSE file at https : //angular.io/license
* /
2019-08-22 22:16:25 -04:00
import { DOCUMENT , isPlatformBrowser , ɵ getDOM as getDOM } from '@angular/common' ;
2020-04-13 19:40:21 -04:00
import { APP_INITIALIZER , Compiler , Component , createPlatformFactory , CUSTOM_ELEMENTS_SCHEMA , Directive , ErrorHandler , Inject , Injector , Input , LOCALE_ID , NgModule , OnDestroy , Pipe , PLATFORM_ID , PLATFORM_INITIALIZER , Provider , Sanitizer , StaticProvider , Type , VERSION } from '@angular/core' ;
2016-08-12 17:15:37 -04:00
import { ApplicationRef , destroyPlatform } from '@angular/core/src/application_ref' ;
2016-06-08 19:38:52 -04:00
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' ;
2020-04-13 19:40:21 -04:00
import { afterEach , AsyncTestCompleter , beforeEach , beforeEachProviders , describe , inject , it , Log } from '@angular/core/testing/src/testing_internal' ;
2016-07-18 06:50:31 -04:00
import { BrowserModule } from '@angular/platform-browser' ;
2016-08-16 14:15:01 -04:00
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' ;
2017-03-02 15:12:46 -05:00
import { expect } from '@angular/platform-browser/testing/src/matchers' ;
2019-01-17 12:48:39 -05:00
import { ivyEnabled , modifiedInIvy , onlyInIvy } from '@angular/private/testing' ;
2016-08-16 14:15:01 -04:00
2017-02-17 11:56:36 -05:00
@Component ( { selector : 'non-existent' , template : '' } )
class NonExistentComp {
}
2016-03-08 16:36:48 -05:00
@Component ( { selector : 'hello-app' , template : '{{greeting}} world!' } )
2015-05-26 12:45:15 -04:00
class HelloRootCmp {
greeting : string ;
2020-04-13 19:40:21 -04:00
constructor ( ) {
this . greeting = 'hello' ;
}
2015-05-26 12:45:15 -04:00
}
2016-03-08 16:36:48 -05:00
@Component ( { selector : 'hello-app' , template : 'before: <ng-content></ng-content> after: done' } )
2015-05-26 12:45:15 -04:00
class HelloRootCmpContent {
constructor ( ) { }
}
2016-03-08 16:36:48 -05:00
@Component ( { selector : 'hello-app-2' , template : '{{greeting}} world, again!' } )
2015-05-26 12:45:15 -04:00
class HelloRootCmp2 {
greeting : string ;
2020-04-13 19:40:21 -04:00
constructor ( ) {
this . greeting = 'hello' ;
}
2015-05-26 12:45:15 -04:00
}
2016-03-08 16:36:48 -05:00
@Component ( { selector : 'hello-app' , template : '' } )
2015-05-26 12:45:15 -04:00
class HelloRootCmp3 {
2016-06-08 18:45:15 -04:00
appBinding : any /** TODO #9100 */ ;
2015-05-26 12:45:15 -04:00
2016-06-08 19:38:52 -04:00
constructor ( @Inject ( 'appBinding' ) appBinding : any /** TODO #9100 */ ) {
this . appBinding = appBinding ;
}
2015-05-26 12:45:15 -04:00
}
2016-03-08 16:36:48 -05:00
@Component ( { selector : 'hello-app' , template : '' } )
2015-05-26 12:45:15 -04:00
class HelloRootCmp4 {
2016-06-08 18:45:15 -04:00
appRef : any /** TODO #9100 */ ;
2015-05-26 12:45:15 -04:00
2020-04-13 19:40:21 -04:00
constructor ( @Inject ( ApplicationRef ) appRef : ApplicationRef ) {
this . appRef = appRef ;
}
2015-05-26 12:45:15 -04:00
}
@Component ( { selector : 'hello-app' } )
class HelloRootMissingTemplate {
}
@Directive ( { selector : 'hello-app' } )
class HelloRootDirectiveIsNotCmp {
}
2016-03-08 16:36:48 -05:00
@Component ( { selector : 'hello-app' , template : '' } )
2015-11-10 18:42:22 -05:00
class HelloOnDestroyTickCmp implements OnDestroy {
appRef : ApplicationRef ;
2020-04-13 19:40:21 -04:00
constructor ( @Inject ( ApplicationRef ) appRef : ApplicationRef ) {
this . appRef = appRef ;
}
2015-11-10 18:42:22 -05:00
2020-04-13 19:40:21 -04:00
ngOnDestroy ( ) : void {
this . appRef . tick ( ) ;
}
2015-11-10 18:42:22 -05:00
}
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.
2016-06-30 16:07:17 -04:00
@Component ( { selector : 'hello-app' , templateUrl : './sometemplate.html' } )
class HelloUrlCmp {
greeting = 'hello' ;
}
2016-07-07 14:57:11 -04:00
@Directive ( { selector : '[someDir]' , host : { '[title]' : 'someDir' } } )
class SomeDirective {
2018-06-18 19:38:33 -04:00
// TODO(issue/24571): remove '!'.
2020-04-13 19:40:21 -04:00
@Input ( ) someDir ! : string ;
2016-07-07 14:57:11 -04:00
}
@Pipe ( { name : 'somePipe' } )
class SomePipe {
2020-04-13 19:40:21 -04:00
transform ( value : string ) : any {
return ` transformed ${ value } ` ;
}
2016-07-07 14:57:11 -04:00
}
@Component ( { selector : 'hello-app' , template : ` <div [someDir]="'someValue' | somePipe"></div> ` } )
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.
2016-06-30 16:07:17 -04:00
class HelloCmpUsingPlatformDirectiveAndPipe {
show : boolean = false ;
}
2016-07-25 06:02:57 -04:00
@Component ( { selector : 'hello-app' , template : '<some-el [someProp]="true">hello world!</some-el>' } )
class HelloCmpUsingCustomElement {
}
2016-08-25 03:50:16 -04:00
class MockConsole {
2017-03-14 12:16:15 -04:00
res : any [ ] [ ] = [ ] ;
2020-04-13 19:40:21 -04:00
error ( . . . s : any [ ] ) : void {
this . res . push ( s ) ;
}
2015-07-23 21:00:19 -04:00
}
2015-07-27 18:47:42 -04:00
2015-12-15 11:34:44 -05:00
class DummyConsole implements Console {
2016-07-07 14:57:11 -04:00
public warnings : string [ ] = [ ] ;
log ( message : string ) { }
2020-04-13 19:40:21 -04:00
warn ( message : string ) {
this . warnings . push ( message ) ;
}
2015-12-15 11:34:44 -05:00
}
2016-08-16 14:15:01 -04:00
class TestModule { }
2017-12-06 04:13:50 -05:00
function bootstrap (
cmpType : any , providers : Provider [ ] = [ ] , platformProviders : StaticProvider [ ] = [ ] ,
imports : Type < any > [ ] = [ ] ) : Promise < any > {
2016-08-16 14:15:01 -04:00
@NgModule ( {
2017-12-06 04:13:50 -05:00
imports : [ BrowserModule , . . . imports ] ,
2016-08-16 14:15:01 -04:00
declarations : [ cmpType ] ,
bootstrap : [ cmpType ] ,
providers : providers ,
schemas : [ CUSTOM_ELEMENTS_SCHEMA ]
} )
class TestModule {
}
2017-01-05 12:24:37 -05:00
return platformBrowserDynamic ( platformProviders ) . bootstrapModule ( TestModule ) ;
2016-08-16 14:15:01 -04:00
}
2017-12-16 17:42:55 -05:00
{
2017-02-17 11:56:36 -05:00
let el : any /** TODO #9100 */ , el2 : any /** TODO #9100 */ , testProviders : Provider [ ] ,
lightDom : any /** TODO #9100 */ ;
2015-05-26 12:45:15 -04:00
describe ( 'bootstrap factory method' , ( ) = > {
2017-12-18 01:18:50 -05:00
if ( isNode ) return ;
2016-07-07 14:57:11 -04:00
let compilerConsole : DummyConsole ;
2020-04-13 19:40:21 -04:00
beforeEachProviders ( ( ) = > {
return [ Log ] ;
} ) ;
2016-06-23 20:10:22 -04:00
2017-02-17 11:56:36 -05:00
beforeEach ( inject ( [ DOCUMENT ] , ( doc : any ) = > {
2016-08-12 17:15:37 -04:00
destroyPlatform ( ) ;
2017-02-17 11:56:36 -05:00
compilerConsole = new DummyConsole ( ) ;
testProviders = [ { provide : Console , useValue : compilerConsole } ] ;
2019-08-30 15:52:48 -04:00
const oldRoots = doc . querySelectorAll ( 'hello-app,hello-app-2,light-dom-el' ) ;
2017-02-17 11:56:36 -05:00
for ( let i = 0 ; i < oldRoots . length ; i ++ ) {
getDOM ( ) . remove ( oldRoots [ i ] ) ;
}
el = getDOM ( ) . createElement ( 'hello-app' , doc ) ;
el2 = getDOM ( ) . createElement ( 'hello-app-2' , doc ) ;
lightDom = getDOM ( ) . createElement ( 'light-dom-el' , doc ) ;
2019-08-30 15:52:48 -04:00
doc . body . appendChild ( el ) ;
doc . body . appendChild ( el2 ) ;
el . appendChild ( lightDom ) ;
2019-08-30 00:24:33 -04:00
lightDom . textContent = 'loading' ;
2017-02-17 11:56:36 -05:00
} ) ) ;
2015-06-24 16:46:39 -04:00
2016-08-12 17:15:37 -04:00
afterEach ( destroyPlatform ) ;
2015-11-18 12:18:37 -05:00
2019-01-25 21:18:19 -05:00
// TODO(misko): can't use `modifiedInIvy.it` because the `it` is somehow special here.
modifiedInIvy ( 'bootstrapping non-Component throws in View Engine' ) . isEnabled &&
2018-12-04 09:13:10 -05:00
it ( 'should throw if bootstrapped Directive is not a Component' ,
inject ( [ AsyncTestCompleter ] , ( done : AsyncTestCompleter ) = > {
const logger = new MockConsole ( ) ;
const errorHandler = new ErrorHandler ( ) ;
( errorHandler as any ) . _console = logger as any ;
expect (
( ) = > bootstrap (
HelloRootDirectiveIsNotCmp , [ { provide : ErrorHandler , useValue : errorHandler } ] ) )
. toThrowError ( ` HelloRootDirectiveIsNotCmp cannot be used as an entry component. ` ) ;
done . done ( ) ;
} ) ) ;
2015-05-26 12:45:15 -04:00
2019-01-25 21:18:19 -05:00
// TODO(misko): can't use `onlyInIvy.it` because the `it` is somehow special here.
onlyInIvy ( 'bootstrapping non-Component rejects Promise in Ivy' ) . isEnabled &&
it ( 'should throw if bootstrapped Directive is not a Component' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
const logger = new MockConsole ( ) ;
const errorHandler = new ErrorHandler ( ) ;
( errorHandler as any ) . _console = logger as any ;
bootstrap ( HelloRootDirectiveIsNotCmp , [
{ provide : ErrorHandler , useValue : errorHandler }
] ) . catch ( ( error : Error ) = > {
expect ( error ) . toEqual (
new Error ( ` HelloRootDirectiveIsNotCmp cannot be used as an entry component. ` ) ) ;
async . done ( ) ;
} ) ;
} ) ) ;
2019-07-31 16:15:50 -04:00
it ( 'should retrieve sanitizer' , inject ( [ Injector ] , ( injector : Injector ) = > {
const sanitizer : Sanitizer | null = injector . get ( Sanitizer , null ) ;
if ( ivyEnabled ) {
// In Ivy we don't want to have sanitizer in DI. We use DI only to overwrite the
// sanitizer, but not for default one. The default one is pulled in by the Ivy
// instructions as needed.
expect ( sanitizer ) . toBe ( null ) ;
} else {
// In VE we always need to have Sanitizer available.
expect ( sanitizer ) . not . toBe ( null ) ;
}
} ) ) ;
2016-06-08 19:38:52 -04:00
it ( 'should throw if no element is found' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const logger = new MockConsole ( ) ;
2017-03-16 15:58:41 -04:00
const errorHandler = new ErrorHandler ( ) ;
2017-12-17 18:10:54 -05:00
( errorHandler as any ) . _console = logger as any ;
2017-02-17 11:56:36 -05:00
bootstrap ( NonExistentComp , [
2016-08-25 03:50:16 -04:00
{ provide : ErrorHandler , useValue : errorHandler }
2016-08-16 14:15:01 -04:00
] ) . then ( null , ( reason ) = > {
2017-02-17 11:56:36 -05:00
expect ( reason . message )
. toContain ( 'The selector "non-existent" did not match any elements' ) ;
2015-05-26 12:45:15 -04:00
async . done ( ) ;
return null ;
} ) ;
} ) ) ;
2019-01-17 12:48:39 -05:00
it ( 'should throw if no provider' , inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
const logger = new MockConsole ( ) ;
const errorHandler = new ErrorHandler ( ) ;
( errorHandler as any ) . _console = logger as any ;
2018-12-04 09:13:10 -05:00
2019-01-17 12:48:39 -05:00
class IDontExist { }
@Component ( { selector : 'cmp' , template : 'Cmp' } )
class CustomCmp {
constructor ( iDontExist : IDontExist ) { }
}
@Component ( {
selector : 'hello-app' ,
template : '<cmp></cmp>' ,
} )
class RootCmp {
}
@NgModule ( { declarations : [ CustomCmp ] , exports : [ CustomCmp ] } )
class CustomModule {
}
bootstrap ( RootCmp , [ { provide : ErrorHandler , useValue : errorHandler } ] , [ ] , [
CustomModule
] ) . then ( null , ( e : Error ) = > {
let errorMsg : string ;
if ( ivyEnabled ) {
2019-04-30 23:24:00 -04:00
errorMsg = ` R3InjectorError(TestModule)[IDontExist -> IDontExist -> IDontExist]: \ n ` ;
2019-01-17 12:48:39 -05:00
} else {
errorMsg = ` StaticInjectorError(TestModule)[CustomCmp -> IDontExist]: \ n ` +
' StaticInjectorError(Platform: core)[CustomCmp -> IDontExist]: \n' +
' NullInjectorError: No provider for IDontExist!' ;
}
expect ( e . message ) . toContain ( errorMsg ) ;
async . done ( ) ;
return null ;
} ) ;
} ) ) ;
2017-12-06 04:13:50 -05:00
2016-04-28 20:50:03 -04:00
if ( getDOM ( ) . supportsDOMEvents ( ) ) {
2016-02-25 17:24:17 -05:00
it ( 'should forward the error to promise when bootstrap fails' ,
2016-06-09 14:04:15 -04:00
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const logger = new MockConsole ( ) ;
2017-03-16 15:58:41 -04:00
const errorHandler = new ErrorHandler ( ) ;
2017-12-17 18:10:54 -05:00
( errorHandler as any ) . _console = logger as any ;
2016-02-25 17:24:17 -05:00
2016-11-12 08:08:58 -05:00
const refPromise =
2017-02-17 11:56:36 -05:00
bootstrap ( NonExistentComp , [ { provide : ErrorHandler , useValue : errorHandler } ] ) ;
2016-08-02 18:53:34 -04:00
refPromise . then ( null , ( reason : any ) = > {
2016-02-25 17:24:17 -05:00
expect ( reason . message )
2017-02-17 11:56:36 -05:00
. toContain ( 'The selector "non-existent" did not match any elements' ) ;
2016-02-25 17:24:17 -05:00
async . done ( ) ;
} ) ;
} ) ) ;
2015-07-27 18:47:42 -04:00
it ( 'should invoke the default exception handler when bootstrap fails' ,
2016-06-09 14:04:15 -04:00
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const logger = new MockConsole ( ) ;
2017-03-16 15:58:41 -04:00
const errorHandler = new ErrorHandler ( ) ;
2017-12-17 18:10:54 -05:00
( errorHandler as any ) . _console = logger as any ;
2015-07-27 18:47:42 -04:00
2016-11-12 08:08:58 -05:00
const refPromise =
2017-02-17 11:56:36 -05:00
bootstrap ( NonExistentComp , [ { provide : ErrorHandler , useValue : errorHandler } ] ) ;
2016-08-02 18:53:34 -04:00
refPromise . then ( null , ( reason ) = > {
2017-03-14 12:16:15 -04:00
expect ( logger . res [ 0 ] . join ( '#' ) )
. toContain ( 'ERROR#Error: The selector "non-existent" did not match any elements' ) ;
2015-07-27 18:47:42 -04:00
async . done ( ) ;
return null ;
} ) ;
} ) ) ;
}
2015-05-26 12:45:15 -04:00
it ( 'should create an injector promise' , ( ) = > {
2016-11-12 08:08:58 -05:00
const refPromise = bootstrap ( HelloRootCmp , testProviders ) ;
2018-07-05 08:24:53 -04:00
expect ( refPromise ) . toEqual ( jasmine . any ( Promise ) ) ;
2015-05-26 12:45:15 -04:00
} ) ;
2017-02-22 19:49:46 -05:00
it ( 'should set platform name to browser' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
const refPromise = bootstrap ( HelloRootCmp , testProviders ) ;
refPromise . then ( ( ref ) = > {
expect ( isPlatformBrowser ( ref . injector . get ( PLATFORM_ID ) ) ) . toBe ( true ) ;
async . done ( ) ;
} ) ;
} ) ) ;
2016-06-09 14:04:15 -04:00
it ( 'should display hello world' , inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const refPromise = bootstrap ( HelloRootCmp , testProviders ) ;
2015-05-26 12:45:15 -04:00
refPromise . then ( ( ref ) = > {
expect ( el ) . toHaveText ( 'hello world!' ) ;
2016-12-06 19:21:07 -05:00
expect ( el . getAttribute ( 'ng-version' ) ) . toEqual ( VERSION . full ) ;
2015-05-26 12:45:15 -04:00
async . done ( ) ;
} ) ;
} ) ) ;
2016-08-18 16:34:28 -04:00
it ( 'should throw a descriptive error if BrowserModule is installed again via a lazily loaded module' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
@NgModule ( { imports : [ BrowserModule ] } )
class AsyncModule {
}
bootstrap ( HelloRootCmp , testProviders )
. then ( ( ref : ComponentRef < HelloRootCmp > ) = > {
2016-11-12 08:08:58 -05:00
const compiler : Compiler = ref . injector . get ( Compiler ) ;
2016-08-18 16:34:28 -04:00
return compiler . compileModuleAsync ( AsyncModule ) . then ( factory = > {
expect ( ( ) = > factory . create ( ref . injector ) )
. toThrowError (
` BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead. ` ) ;
} ) ;
} )
. then ( ( ) = > async . done ( ) , err = > async . fail ( err ) ) ;
} ) ) ;
2016-06-08 19:38:52 -04:00
it ( 'should support multiple calls to bootstrap' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const refPromise1 = bootstrap ( HelloRootCmp , testProviders ) ;
const refPromise2 = bootstrap ( HelloRootCmp2 , testProviders ) ;
2016-08-02 18:53:34 -04:00
Promise . all ( [ refPromise1 , refPromise2 ] ) . then ( ( refs ) = > {
2016-06-08 19:38:52 -04:00
expect ( el ) . toHaveText ( 'hello world!' ) ;
expect ( el2 ) . toHaveText ( 'hello world, again!' ) ;
async . done ( ) ;
} ) ;
2015-05-26 12:45:15 -04:00
} ) ) ;
2015-11-13 14:21:16 -05:00
2015-11-10 18:42:22 -05:00
it ( 'should not crash if change detection is invoked when the root component is disposed' ,
2016-06-09 14:04:15 -04:00
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-06-08 19:38:52 -04:00
bootstrap ( HelloOnDestroyTickCmp , testProviders ) . then ( ( ref ) = > {
expect ( ( ) = > ref . destroy ( ) ) . not . toThrow ( ) ;
async . done ( ) ;
} ) ;
2015-11-10 18:42:22 -05:00
} ) ) ;
2015-11-10 13:40:33 -05:00
it ( 'should unregister change detectors when components are disposed' ,
2016-06-09 14:04:15 -04:00
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-07-18 06:50:31 -04:00
bootstrap ( HelloRootCmp , testProviders ) . then ( ( ref ) = > {
const appRef = ref . injector . get ( ApplicationRef ) ;
2016-06-08 19:38:52 -04:00
ref . destroy ( ) ;
2016-07-18 06:50:31 -04:00
expect ( ( ) = > appRef . tick ( ) ) . not . toThrow ( ) ;
2016-06-08 19:38:52 -04:00
async . done ( ) ;
} ) ;
2015-11-10 13:40:33 -05:00
} ) ) ;
2015-05-26 12:45:15 -04:00
2016-06-08 19:38:52 -04:00
it ( 'should make the provided bindings available to the application component' ,
2016-06-09 14:04:15 -04:00
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const refPromise = bootstrap (
2016-06-08 19:38:52 -04:00
HelloRootCmp3 , [ testProviders , { provide : 'appBinding' , useValue : 'BoundValue' } ] ) ;
2015-05-26 12:45:15 -04:00
refPromise . then ( ( ref ) = > {
2016-08-16 14:15:01 -04:00
expect ( ref . injector . get ( 'appBinding' ) ) . toEqual ( 'BoundValue' ) ;
2015-05-26 12:45:15 -04:00
async . done ( ) ;
} ) ;
} ) ) ;
2017-01-05 12:24:37 -05:00
it ( 'should not override locale provided during bootstrap' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
const refPromise =
bootstrap ( HelloRootCmp , [ testProviders ] , [ { provide : LOCALE_ID , useValue : 'fr-FR' } ] ) ;
refPromise . then ( ref = > {
expect ( ref . injector . get ( LOCALE_ID ) ) . toEqual ( 'fr-FR' ) ;
async . done ( ) ;
} ) ;
} ) ) ;
2016-06-08 19:38:52 -04:00
it ( 'should avoid cyclic dependencies when root component requires Lifecycle through DI' ,
2016-06-09 14:04:15 -04:00
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const refPromise = bootstrap ( HelloRootCmp4 , testProviders ) ;
2015-05-26 12:45:15 -04:00
refPromise . then ( ( ref ) = > {
2016-08-16 14:15:01 -04:00
const appRef = ref . injector . get ( ApplicationRef ) ;
expect ( appRef ) . toBeDefined ( ) ;
2015-05-26 12:45:15 -04:00
async . done ( ) ;
} ) ;
} ) ) ;
2016-07-18 06:50:31 -04:00
it ( 'should run platform initializers' ,
inject ( [ Log , AsyncTestCompleter ] , ( log : Log , async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const p = createPlatformFactory ( platformBrowserDynamic , 'someName' , [
2016-06-08 19:38:52 -04:00
{ provide : PLATFORM_INITIALIZER , useValue : log.fn ( 'platform_init1' ) , multi : true } ,
{ provide : PLATFORM_INITIALIZER , useValue : log.fn ( 'platform_init2' ) , multi : true }
2016-07-18 06:50:31 -04:00
] ) ( ) ;
@NgModule ( {
imports : [ BrowserModule ] ,
providers : [
{ provide : APP_INITIALIZER , useValue : log.fn ( 'app_init1' ) , multi : true } ,
{ provide : APP_INITIALIZER , useValue : log.fn ( 'app_init2' ) , multi : true }
]
} )
class SomeModule {
2016-08-02 09:54:08 -04:00
ngDoBootstrap() { }
2016-07-18 06:50:31 -04:00
}
2016-06-08 19:38:52 -04:00
expect ( log . result ( ) ) . toEqual ( 'platform_init1; platform_init2' ) ;
2015-11-18 12:18:37 -05:00
log . clear ( ) ;
2016-07-26 08:21:19 -04:00
p . bootstrapModule ( SomeModule ) . then ( ( ) = > {
2016-07-18 06:50:31 -04:00
expect ( log . result ( ) ) . toEqual ( 'app_init1; app_init2' ) ;
async . done ( ) ;
} ) ;
2015-11-18 12:18:37 -05:00
} ) ) ;
2017-02-22 19:06:21 -05:00
it ( 'should remove styles when transitioning from a server render' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
@Component ( {
selector : 'root' ,
template : 'root' ,
} )
class RootCmp {
}
@NgModule ( {
bootstrap : [ RootCmp ] ,
declarations : [ RootCmp ] ,
imports : [ BrowserModule . withServerTransition ( { appId : 'my-app' } ) ] ,
} )
class TestModule {
}
// First, set up styles to be removed.
const dom = getDOM ( ) ;
const platform = platformBrowserDynamic ( ) ;
const document = platform . injector . get ( DOCUMENT ) ;
const style = dom . createElement ( 'style' , document ) ;
2019-08-30 15:52:48 -04:00
style . setAttribute ( 'ng-transition' , 'my-app' ) ;
document . head . appendChild ( style ) ;
2017-02-22 19:06:21 -05:00
const root = dom . createElement ( 'root' , document ) ;
2019-08-30 15:52:48 -04:00
document . body . appendChild ( root ) ;
2017-02-22 19:06:21 -05:00
platform . bootstrapModule ( TestModule ) . then ( ( ) = > {
const styles : HTMLElement [ ] =
2019-08-30 15:52:48 -04:00
Array . prototype . slice . apply ( document . getElementsByTagName ( 'style' ) || [ ] ) ;
2020-04-13 19:40:21 -04:00
styles . forEach ( style = > {
expect ( style . getAttribute ( 'ng-transition' ) ) . not . toBe ( 'my-app' ) ;
} ) ;
2017-02-22 19:06:21 -05:00
async . done ( ) ;
} ) ;
} ) ) ;
2015-05-26 12:45:15 -04:00
it ( 'should register each application with the testability registry' ,
2016-06-09 14:04:15 -04:00
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-11-12 08:08:58 -05:00
const refPromise1 : Promise < ComponentRef < any > > = bootstrap ( HelloRootCmp , testProviders ) ;
const refPromise2 : Promise < ComponentRef < any > > = bootstrap ( HelloRootCmp2 , testProviders ) ;
2015-05-26 12:45:15 -04:00
2016-08-02 18:53:34 -04:00
Promise . all ( [ refPromise1 , refPromise2 ] ) . then ( ( refs : ComponentRef < any > [ ] ) = > {
2016-11-12 08:08:58 -05:00
const registry = refs [ 0 ] . injector . get ( TestabilityRegistry ) ;
const testabilities =
2016-06-08 19:38:52 -04:00
[ refs [ 0 ] . injector . get ( Testability ) , refs [ 1 ] . injector . get ( Testability ) ] ;
2016-08-02 18:53:34 -04:00
Promise . all ( testabilities ) . then ( ( testabilities : Testability [ ] ) = > {
2016-06-08 19:38:52 -04:00
expect ( registry . findTestabilityInTree ( el ) ) . toEqual ( testabilities [ 0 ] ) ;
expect ( registry . findTestabilityInTree ( el2 ) ) . toEqual ( testabilities [ 1 ] ) ;
async . done ( ) ;
} ) ;
} ) ;
2015-05-26 12:45:15 -04:00
} ) ) ;
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.
2016-06-30 16:07:17 -04:00
2016-07-25 06:02:57 -04:00
it ( 'should allow to pass schemas' , inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
2016-08-08 12:36:09 -04:00
bootstrap ( HelloCmpUsingCustomElement , testProviders ) . then ( ( compRef ) = > {
2016-07-25 06:02:57 -04:00
expect ( el ) . toHaveText ( 'hello world!' ) ;
async . done ( ) ;
} ) ;
} ) ) ;
2019-04-18 20:52:04 -04:00
describe ( 'change detection' , ( ) = > {
const log : string [ ] = [ ] ;
@Component ( {
selector : 'hello-app' ,
template : '<div id="button-a" (click)="onClick()">{{title}}</div>' ,
} )
class CompA {
title : string = '' ;
2020-04-13 19:40:21 -04:00
ngDoCheck() {
log . push ( 'CompA:ngDoCheck' ) ;
}
2019-04-18 20:52:04 -04:00
onClick() {
this . title = 'CompA' ;
log . push ( 'CompA:onClick' ) ;
}
}
@Component ( {
selector : 'hello-app-2' ,
template : '<div id="button-b" (click)="onClick()">{{title}}</div>' ,
} )
class CompB {
title : string = '' ;
2020-04-13 19:40:21 -04:00
ngDoCheck() {
log . push ( 'CompB:ngDoCheck' ) ;
}
2019-04-18 20:52:04 -04:00
onClick() {
this . title = 'CompB' ;
log . push ( 'CompB:onClick' ) ;
}
}
it ( 'should be triggered for all bootstrapped components in case change happens in one of them' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
@NgModule ( {
imports : [ BrowserModule ] ,
declarations : [ CompA , CompB ] ,
bootstrap : [ CompA , CompB ] ,
schemas : [ CUSTOM_ELEMENTS_SCHEMA ]
} )
class TestModuleA {
}
platformBrowserDynamic ( ) . bootstrapModule ( TestModuleA ) . then ( ( ref ) = > {
log . length = 0 ;
el . querySelectorAll ( '#button-a' ) [ 0 ] . click ( ) ;
expect ( log ) . toContain ( 'CompA:onClick' ) ;
expect ( log ) . toContain ( 'CompA:ngDoCheck' ) ;
expect ( log ) . toContain ( 'CompB:ngDoCheck' ) ;
log . length = 0 ;
el2 . querySelectorAll ( '#button-b' ) [ 0 ] . click ( ) ;
expect ( log ) . toContain ( 'CompB:onClick' ) ;
expect ( log ) . toContain ( 'CompA:ngDoCheck' ) ;
expect ( log ) . toContain ( 'CompB:ngDoCheck' ) ;
async . done ( ) ;
} ) ;
} ) ) ;
it ( 'should work in isolation for each component bootstrapped individually' ,
inject ( [ AsyncTestCompleter ] , ( async : AsyncTestCompleter ) = > {
const refPromise1 = bootstrap ( CompA ) ;
const refPromise2 = bootstrap ( CompB ) ;
Promise . all ( [ refPromise1 , refPromise2 ] ) . then ( ( refs ) = > {
log . length = 0 ;
el . querySelectorAll ( '#button-a' ) [ 0 ] . click ( ) ;
expect ( log ) . toContain ( 'CompA:onClick' ) ;
expect ( log ) . toContain ( 'CompA:ngDoCheck' ) ;
expect ( log ) . not . toContain ( 'CompB:ngDoCheck' ) ;
log . length = 0 ;
el2 . querySelectorAll ( '#button-b' ) [ 0 ] . click ( ) ;
expect ( log ) . toContain ( 'CompB:onClick' ) ;
expect ( log ) . toContain ( 'CompB:ngDoCheck' ) ;
expect ( log ) . not . toContain ( 'CompA:ngDoCheck' ) ;
async . done ( ) ;
} ) ;
} ) ) ;
} ) ;
2015-05-26 12:45:15 -04:00
} ) ;
}