2016-06-23 12:47:54 -04:00
/ * *
* @license
* Copyright Google Inc . All Rights Reserved .
*
* 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' ;
2019-07-31 16:15:50 -04:00
import { APP_INITIALIZER , CUSTOM_ELEMENTS_SCHEMA , Compiler , Component , Directive , ErrorHandler , Inject , Injector , Input , LOCALE_ID , NgModule , OnDestroy , PLATFORM_ID , PLATFORM_INITIALIZER , Pipe , Provider , Sanitizer , StaticProvider , Type , VERSION , createPlatformFactory } 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' ;
2019-03-11 20:20:40 -04:00
import { AsyncTestCompleter , Log , afterEach , beforeEach , beforeEachProviders , describe , inject , it } 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 ;
constructor ( ) { this . greeting = 'hello' ; }
}
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 ;
constructor ( ) { this . greeting = 'hello' ; }
}
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
2016-06-09 14:04:15 -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 ;
2016-06-09 14:04:15 -04:00
constructor ( @Inject ( ApplicationRef ) appRef : ApplicationRef ) { this . appRef = appRef ; }
2015-11-10 18:42:22 -05:00
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 20:04:36 -05: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 '!'.
2016-07-07 14:57:11 -04:00
@Input ( )
2018-06-18 19:38:33 -04:00
someDir ! : string ;
2016-07-07 14:57:11 -04:00
}
@Pipe ( { name : 'somePipe' } )
class SomePipe {
transform ( value : string ) : any { return ` transformed ${ value } ` ; }
}
@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 [ ] [ ] = [ ] ;
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 ) { }
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 ;
2016-06-23 20:10:22 -04:00
beforeEachProviders ( ( ) = > { return [ Log ] ; } ) ;
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' ) || [ ] ) ;
2017-02-22 19:06:21 -05:00
styles . forEach (
2019-08-30 15:52:48 -04:00
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 = '' ;
ngDoCheck() { log . push ( 'CompA:ngDoCheck' ) ; }
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 = '' ;
ngDoCheck() { log . push ( 'CompB:ngDoCheck' ) ; }
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
} ) ;
}