refactor(core): change module semantics

This contains major changes to the compiler, bootstrap of the platforms
and test environment initialization.

Main part of #10043
Closes #10164

BREAKING CHANGE:
- Semantics and name of `@AppModule` (now `@NgModule`) changed quite a bit.
  This is actually not breaking as `@AppModules` were not part of rc.4.
  We will have detailed docs on `@NgModule` separately.
- `coreLoadAndBootstrap` and `coreBootstrap` can't be used any more (without migration support).
  Use `bootstrapModule` / `bootstrapModuleFactory` instead.
- All Components listed in routes have to be part of the `declarations` of an NgModule.
  Either directly on the bootstrap module / lazy loaded module, or in an NgModule imported by them.
This commit is contained in:
Tobias Bosch
2016-07-18 03:50:31 -07:00
parent ca16fc29a6
commit 46b212706b
129 changed files with 3580 additions and 3366 deletions

View File

@ -9,4 +9,17 @@ The Angular router is designed to solve these problems. Using the router, you ca
Read the overview of the Router [here](http://victorsavkin.com/post/145672529346/angular-router).
## Guide
Read the dev guide [here](https://angular.io/docs/ts/latest/guide/router.html).
Read the dev guide [here](https://angular.io/docs/ts/latest/guide/router.html).
## Local development
```
# keep @angular/router fresh
$ ./scripts/karma.sh
# keep @angular/core fresh
$ ../../../node_modules/.bin/tsc -p modules --emitDecoratorMetadata -w
# start karma
$ ./scripts/karma.sh
```

View File

@ -14,7 +14,7 @@ export {RouterLinkActive} from './src/directives/router_link_active';
export {RouterOutlet} from './src/directives/router_outlet';
export {CanActivate, CanActivateChild, CanDeactivate, Resolve} from './src/interfaces';
export {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationExtras, NavigationStart, Router, RoutesRecognized} from './src/router';
export {ROUTER_DIRECTIVES, RouterModule} from './src/router_module';
export {ROUTER_DIRECTIVES, RouterModule, RouterModuleWithoutProviders} from './src/router_module';
export {RouterOutletMap} from './src/router_outlet_map';
export {provideRouter} from './src/router_providers';
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './src/router_state';

View File

@ -73,8 +73,8 @@ Promise.all([
var testingBrowser = providers[1];
testing.initTestEnvironment(
testingBrowser.BrowserDynamicTestModule,
testingBrowser.browserDynamicTestPlatform());
testingBrowser.BrowserDynamicTestingModule,
testingBrowser.browserDynamicTestingPlatform());
}).then(function() {
// Finally, load all spec files.

View File

@ -7,7 +7,7 @@
*/
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
import {ANALYZE_FOR_PRECOMPILE, APP_INITIALIZER, AppModuleFactoryLoader, ApplicationRef, ComponentResolver, Injector, OpaqueToken, SystemJsAppModuleLoader} from '@angular/core';
import {ANALYZE_FOR_PRECOMPILE, APP_INITIALIZER, ApplicationRef, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core';
import {Routes} from './config';
import {Router} from './router';
@ -26,7 +26,7 @@ export interface ExtraOptions { enableTracing?: boolean; }
export function setupRouter(
ref: ApplicationRef, resolver: ComponentResolver, urlSerializer: UrlSerializer,
outletMap: RouterOutletMap, location: Location, injector: Injector,
loader: AppModuleFactoryLoader, config: Routes, opts: ExtraOptions) {
loader: NgModuleFactoryLoader, config: Routes, opts: ExtraOptions) {
if (ref.componentTypes.length == 0) {
throw new Error('Bootstrap at least one component before injecting Router.');
}
@ -100,7 +100,7 @@ export function provideRouter(routes: Routes, config: ExtraOptions): any[] {
useFactory: setupRouter,
deps: [
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
AppModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
]
},
@ -108,7 +108,7 @@ export function provideRouter(routes: Routes, config: ExtraOptions): any[] {
// Trigger initial navigation
{provide: APP_INITIALIZER, multi: true, useFactory: setupRouterInitializer, deps: [Injector]},
{provide: AppModuleFactoryLoader, useClass: SystemJsAppModuleLoader}
{provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader}
];
}
@ -118,7 +118,7 @@ export function provideRouter(routes: Routes, config: ExtraOptions): any[] {
* ### Example
*
* ```
* @AppModule({providers: [
* @NgModule({providers: [
* provideRoutes([{path: 'home', component: Home}])
* ]})
* class LazyLoadedModule {
@ -141,7 +141,7 @@ export function provideRoutes(routes: Routes): any {
* ### Example
*
* ```
* @AppModule({providers: [
* @NgModule({providers: [
* provideRouterOptions({enableTracing: true})
* ]})
* class LazyLoadedModule {

View File

@ -41,8 +41,7 @@ function resolveNode(
function resolveComponent(
resolver: ComponentResolver, snapshot: ActivatedRouteSnapshot): Promise<any> {
// TODO: vsavkin change to typeof snapshot.component === 'string' in beta2
if (snapshot.component && snapshot._routeConfig) {
if (snapshot.component && snapshot._routeConfig && typeof snapshot.component === 'string') {
return resolver.resolveComponent(<any>snapshot.component);
} else {
return Promise.resolve(null);

View File

@ -13,7 +13,7 @@ import 'rxjs/add/operator/reduce';
import 'rxjs/add/operator/every';
import {Location} from '@angular/common';
import {AppModuleFactoryLoader, ComponentFactoryResolver, ComponentResolver, Injector, ReflectiveInjector, Type} from '@angular/core';
import {ComponentFactoryResolver, ComponentResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription';
@ -146,7 +146,7 @@ export class Router {
constructor(
private rootComponentType: Type, private resolver: ComponentResolver,
private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap,
private location: Location, private injector: Injector, loader: AppModuleFactoryLoader,
private location: Location, private injector: Injector, loader: NgModuleFactoryLoader,
config: Routes) {
this.resetConfig(config);
this.routerEvents = new Subject<Event>();

View File

@ -6,13 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AppModuleFactoryLoader, ComponentFactoryResolver, Injector, OpaqueToken} from '@angular/core';
import {ComponentFactoryResolver, Injector, NgModuleFactoryLoader, OpaqueToken} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {fromPromise} from 'rxjs/observable/fromPromise';
import {Route} from './config';
/**
* @deprecated use Routes
*/
@ -26,7 +27,7 @@ export class LoadedRouterConfig {
}
export class RouterConfigLoader {
constructor(private loader: AppModuleFactoryLoader) {}
constructor(private loader: NgModuleFactoryLoader) {}
load(parentInjector: Injector, path: string): Observable<LoadedRouterConfig> {
return fromPromise(this.loader.load(path).then(r => {
@ -35,4 +36,4 @@ export class RouterConfigLoader {
ref.injector.get(ROUTES), ref.injector, ref.componentFactoryResolver);
}));
}
}
}

View File

@ -7,7 +7,7 @@
*/
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
import {AppModule, AppModuleFactoryLoader, ApplicationRef, ComponentResolver, Injector, OpaqueToken, SystemJsAppModuleLoader} from '@angular/core';
import {ApplicationRef, ComponentResolver, Injector, NgModule, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core';
import {ROUTER_CONFIGURATION, rootRoute, setupRouter} from './common_router_providers';
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
@ -33,14 +33,35 @@ export const ROUTER_PROVIDERS: any[] = [
useFactory: setupRouter,
deps: [
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
AppModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
]
},
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
{provide: AppModuleFactoryLoader, useClass: SystemJsAppModuleLoader},
{provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader},
{provide: ROUTER_CONFIGURATION, useValue: {enableTracing: false}}
];
/**
* Router module to be used for lazy loaded parts.
*
* ### Example
*
* ```
* @NgModule({
* imports: [RouterModuleWithoutProviders]
* })
* class TeamsModule {}
* ```
*
* @experimental We will soon have a way for the `RouterModule` to be imported with and without a
* provider,
* and then this module will be removed.
*/
@NgModule({declarations: ROUTER_DIRECTIVES, exports: ROUTER_DIRECTIVES})
export class RouterModuleWithoutProviders {
}
/**
* Router module.
*
@ -52,7 +73,7 @@ export const ROUTER_PROVIDERS: any[] = [
*
* @experimental
*/
@AppModule({directives: ROUTER_DIRECTIVES, providers: ROUTER_PROVIDERS})
@NgModule({exports: [RouterModuleWithoutProviders], providers: ROUTER_PROVIDERS})
export class RouterModule {
constructor(private injector: Injector) {
setTimeout(() => {
@ -64,4 +85,4 @@ export class RouterModule {
}
}, 0);
}
}
}

View File

@ -9,21 +9,27 @@
import 'rxjs/add/operator/map';
import {Location} from '@angular/common';
import {AppModule, AppModuleFactoryLoader, Component} from '@angular/core';
import {Component, NgModule, NgModuleFactoryLoader} from '@angular/core';
import {ComponentFixture, TestComponentBuilder, addProviders, configureModule, fakeAsync, inject, tick} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
import {Observable} from 'rxjs/Observable';
import {of } from 'rxjs/observable/of';
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Params, ROUTER_DIRECTIVES, Resolve, Router, RouterStateSnapshot, RoutesRecognized, provideRoutes} from '../index';
import {RouterTestingModule, SpyAppModuleFactoryLoader} from '../testing';
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Params, ROUTER_DIRECTIVES, Resolve, Router, RouterModuleWithoutProviders, RouterStateSnapshot, RoutesRecognized, provideRoutes} from '../index';
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
describe('Integration', () => {
beforeEach(() => {
configureModule({
modules: [RouterTestingModule],
imports: [RouterTestingModule],
providers: [provideRoutes(
[{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}])]
[{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}])],
declarations: [
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp,
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp,
QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf,
ComponentRecordingQueryParams, ComponentRecordingRoutePathAndUrl, RouteCmp
]
});
});
@ -254,24 +260,6 @@ describe('Integration', () => {
it('should not push query params into components that will be deactivated',
fakeAsync(
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
@Component({template: ''})
class ComponentRecordingQueryParams {
recordedQueryParams: any[] = [];
subscription: any;
constructor(r: Router) {
this.subscription =
r.routerState.queryParams.subscribe(r => this.recordedQueryParams.push(r));
}
ngOnDestroy() { this.subscription.unsubscribe(); }
}
@Component({
template: '<router-outlet></router-outlet>',
precompile: [SimpleCmp, ComponentRecordingQueryParams]
})
class RootCmp {
}
router.resetConfig([
{path: '', component: ComponentRecordingQueryParams},
@ -524,25 +512,10 @@ describe('Integration', () => {
fakeAsync(inject(
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
@Component({selector: 'cmp', template: ''})
class Cmp {
private path: any;
private url: any;
constructor(router: Router, route: ActivatedRoute) {
this.path = router.routerState.pathFromRoot(route);
this.url = router.url.toString();
}
}
const fixture = createRoot(tcb, router, RootCmp);
@Component(
{selector: 'root', template: '<router-outlet></router-outlet>', precompile: [Cmp]})
class Root {
}
const fixture = createRoot(tcb, router, Root);
router.resetConfig([{path: 'cmp', component: Cmp}]);
router.resetConfig([{path: 'cmp', component: ComponentRecordingRoutePathAndUrl}]);
router.navigateByUrl('/cmp');
advance(fixture);
@ -1330,9 +1303,9 @@ describe('Integration', () => {
describe('lazy loading', () => {
it('works', fakeAsync(inject(
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location,
loader: SpyAppModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
@Component({
selector: 'lazy',
template: 'lazy-loaded-parent [<router-outlet></router-outlet>]',
@ -1345,12 +1318,14 @@ describe('Integration', () => {
class ChildLazyLoadedComponent {
}
@AppModule({
@NgModule({
declarations: [ParentLazyLoadedComponent, ChildLazyLoadedComponent],
providers: [provideRoutes([{
path: 'loaded',
component: ParentLazyLoadedComponent,
children: [{path: 'child', component: ChildLazyLoadedComponent}]
}])],
imports: [RouterModuleWithoutProviders],
precompile: [ParentLazyLoadedComponent, ChildLazyLoadedComponent]
})
class LoadedModule {
@ -1373,9 +1348,9 @@ describe('Integration', () => {
it('should use the injector of the lazily-loaded configuration',
fakeAsync(inject(
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location,
loader: SpyAppModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
class LazyLoadedService {}
@Component({selector: 'lazy', template: 'lazy-loaded', directives: ROUTER_DIRECTIVES})
@ -1383,8 +1358,10 @@ describe('Integration', () => {
constructor(service: LazyLoadedService) {}
}
@AppModule({
@NgModule({
precompile: [LazyLoadedComponent],
declarations: [LazyLoadedComponent],
imports: [RouterModuleWithoutProviders],
providers: [
LazyLoadedService, provideRoutes([{
path: '',
@ -1412,9 +1389,9 @@ describe('Integration', () => {
it('error emit an error when cannot load a config',
fakeAsync(inject(
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location,
loader: SpyAppModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
loader.stubbedModules = {};
const fixture = createRoot(tcb, router, RootCmp);
@ -1609,6 +1586,29 @@ class DummyLinkWithParentCmp {
constructor(route: ActivatedRoute) { this.exact = (<any>route.snapshot.params).exact === 'true'; }
}
@Component({template: ''})
class ComponentRecordingQueryParams {
recordedQueryParams: any[] = [];
subscription: any;
constructor(r: Router) {
this.subscription = r.routerState.queryParams.subscribe(r => this.recordedQueryParams.push(r));
}
ngOnDestroy() { this.subscription.unsubscribe(); }
}
@Component({selector: 'cmp', template: ''})
class ComponentRecordingRoutePathAndUrl {
private path: any;
private url: any;
constructor(router: Router, route: ActivatedRoute) {
this.path = router.routerState.pathFromRoot(route);
this.url = router.url.toString();
}
}
@Component({
selector: 'root-cmp',
template: `<router-outlet></router-outlet>`,
@ -1616,7 +1616,8 @@ class DummyLinkWithParentCmp {
precompile: [
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp,
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp,
QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf
QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf,
ComponentRecordingQueryParams, ComponentRecordingRoutePathAndUrl
]
})
class RootCmp {
@ -1632,6 +1633,7 @@ class RootCmp {
class RootCmpWithTwoOutlets {
}
function advance(fixture: ComponentFixture<any>): void {
tick();
fixture.detectChanges();

View File

@ -8,29 +8,29 @@
import {Location, LocationStrategy} from '@angular/common';
import {MockLocationStrategy, SpyLocation} from '@angular/common/testing';
import {AppModule, AppModuleFactory, AppModuleFactoryLoader, Compiler, ComponentResolver, Injectable, Injector} from '@angular/core';
import {Compiler, ComponentResolver, Injectable, Injector, NgModule, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core';
import {Router, RouterOutletMap, Routes, UrlSerializer} from '../index';
import {ROUTES} from '../src/router_config_loader';
import {ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from '../src/router_module';
import {RouterModule} from '../src/router_module';
/**
* A spy for {@link AppModuleFactoryLoader} that allows tests to simulate the loading of app module
* A spy for {@link NgModuleFactoryLoader} that allows tests to simulate the loading of ng module
* factories.
*
* @experimental
*/
@Injectable()
export class SpyAppModuleFactoryLoader implements AppModuleFactoryLoader {
export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
public stubbedModules: {[path: string]: any} = {};
constructor(private compiler: Compiler) {}
load(path: string): Promise<AppModuleFactory<any>> {
load(path: string): Promise<NgModuleFactory<any>> {
if (this.stubbedModules[path]) {
return this.compiler.compileAppModuleAsync(this.stubbedModules[path]);
return this.compiler.compileModuleAsync(this.stubbedModules[path]);
} else {
return <any>Promise.reject(new Error(`Cannot find module ${path}`));
}
@ -39,20 +39,20 @@ export class SpyAppModuleFactoryLoader implements AppModuleFactoryLoader {
function setupTestingRouter(
resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
location: Location, loader: AppModuleFactoryLoader, injector: Injector, routes: Routes) {
location: Location, loader: NgModuleFactoryLoader, injector: Injector, routes: Routes) {
return new Router(null, resolver, urlSerializer, outletMap, location, injector, loader, routes);
}
/**
* A module setting up the router that should be used for testing.
* It provides spy implementations of Location, LocationStrategy, and AppModuleFactoryLoader.
* It provides spy implementations of Location, LocationStrategy, and NgModuleFactoryLoader.
*
* # Example:
*
* ```
* beforeEach(() => {
* configureModule({
* modules: [RouterTestModule],
* modules: [RouterTestingModule],
* providers: [provideRoutes(
* [{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}])]
* });
@ -61,18 +61,17 @@ function setupTestingRouter(
*
* @experimental
*/
@AppModule({
directives: ROUTER_DIRECTIVES,
@NgModule({
exports: [RouterModule],
providers: [
ROUTER_PROVIDERS,
{provide: Location, useClass: SpyLocation},
{provide: LocationStrategy, useClass: MockLocationStrategy},
{provide: AppModuleFactoryLoader, useClass: SpyAppModuleFactoryLoader},
{provide: NgModuleFactoryLoader, useClass: SpyNgModuleFactoryLoader},
{
provide: Router,
useFactory: setupTestingRouter,
deps: [
ComponentResolver, UrlSerializer, RouterOutletMap, Location, AppModuleFactoryLoader,
ComponentResolver, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader,
Injector, ROUTES
]
},