Skip to content

Commit

Permalink
feat: add support for lazy loading NgModules
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The input for lazy loading has change from [loadComponent] to [load] to support
components and NgModules

BEFORE:

<route path="/path" [loadComponent]=""></route>

AFTER:

<route path="/path" [load]=""></route>
  • Loading branch information
brandonroberts committed Jun 30, 2020
1 parent 5c0b345 commit 0d2aa76
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 43 deletions.
21 changes: 0 additions & 21 deletions apps/example-app/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ import { CoreModule } from '@example-app/core';
import { UserEffects, RouterEffects } from '@example-app/core/effects';
import { AppComponent } from '@example-app/core/containers';
import * as fromAuth from '@example-app/auth/reducers';
import { BookEffects, CollectionEffects } from '@example-app/books/effects';

import * as fromBooks from '@example-app/books/reducers';

@NgModule({
imports: [
Expand Down Expand Up @@ -78,24 +75,6 @@ import * as fromBooks from '@example-app/books/reducers';
* See: https://ngrx.io/guide/effects#registering-root-effects
*/
EffectsModule.forRoot([UserEffects]),

/**
* StoreModule.forFeature is used for composing state
* from feature modules. These modules can be loaded
* eagerly or lazily and will be dynamically added to
* the existing state.
*/
StoreModule.forFeature(fromBooks.booksFeatureKey, fromBooks.reducers),

/**
* Effects.forFeature is used to register effects
* from feature modules. Effects can be loaded
* eagerly or lazily and will be started immediately.
*
* All Effects will only be instantiated once regardless of
* whether they are registered once or multiple times.
*/
EffectsModule.forFeature([BookEffects, CollectionEffects]),
CoreModule,
],
bootstrap: [AppComponent],
Expand Down
27 changes: 24 additions & 3 deletions apps/example-app/src/app/books/books.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ import {

import { MaterialModule } from '@example-app/material';
import { PipesModule } from '@example-app/shared/pipes';
import { RoutingModule } from 'angular-routing';
import { RoutingModule, ModuleWithRoute } from 'angular-routing';
import { AuthGuard } from '@example-app/auth/services';
import { BookEffects, CollectionEffects } from '@example-app/books/effects';

import * as fromBooks from '@example-app/books/reducers';

@Component({
selector: 'app-books',
selector: 'bc-books',
template: `
<router *ngIf="loggedIn$ | async">
<route path="/find">
Expand Down Expand Up @@ -69,8 +71,27 @@ export const CONTAINERS = [
MaterialModule,
RoutingModule,
PipesModule,
/**
* StoreModule.forFeature is used for composing state
* from feature modules. These modules can be loaded
* eagerly or lazily and will be dynamically added to
* the existing state.
*/
StoreModule.forFeature(fromBooks.booksFeatureKey, fromBooks.reducers),

/**
* Effects.forFeature is used to register effects
* from feature modules. Effects can be loaded
* eagerly or lazily and will be started immediately.
*
* All Effects will only be instantiated once regardless of
* whether they are registered once or multiple times.
*/
EffectsModule.forFeature([BookEffects, CollectionEffects]),
],
declarations: [COMPONENTS, CONTAINERS],
entryComponents: [BooksComponent]
})
export class BooksModule {}
export class BooksModule implements ModuleWithRoute {
routeComponent = BooksComponent;
}
4 changes: 2 additions & 2 deletions apps/example-app/src/app/core/containers/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { LayoutActions } from '@example-app/core/actions';
</bc-toolbar>
<router>
<route path="/books/**" [loadComponent]="components.books"></route>
<route path="/books/**" [load]="components.books"></route>
<route path="/login">
<bc-login-page *routeComponent></bc-login-page>
</route>
Expand All @@ -58,7 +58,7 @@ import { LayoutActions } from '@example-app/core/actions';
})
export class AppComponent {
components = {
books: () => import('../../books/books.module').then(m => m.BooksComponent)
books: () => import('../../books/books.module').then(m => m.BooksModule)
};
showSidenav$: Observable<boolean>;
loggedIn$: Observable<boolean>;
Expand Down
2 changes: 1 addition & 1 deletion libs/angular-routing/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "angular-routing",
"version": "0.0.1",
"version": "0.1.0",
"description": "A declarative router for Angular applications",
"repository": {
"type": "git",
Expand Down
47 changes: 34 additions & 13 deletions libs/angular-routing/src/lib/route.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import {
TemplateRef,
ChangeDetectionStrategy,
Self,
NgModuleFactory,
Compiler,
} from '@angular/core';

import { Subject, BehaviorSubject, merge, of } from 'rxjs';
import { Subject, BehaviorSubject, merge, of, from } from 'rxjs';
import {
distinctUntilChanged,
filter,
Expand All @@ -20,7 +22,7 @@ import {
withLatestFrom,
} from 'rxjs/operators';

import { LoadComponent, Route } from './route';
import { Load, Route } from './route';
import { Params, RouteParams } from './route-params.service';
import { RouterComponent } from './router.component';
import { Router } from './router.service';
Expand Down Expand Up @@ -49,7 +51,7 @@ export class RouteComponent implements OnInit {
@ContentChild(TemplateRef) template: TemplateRef<any> | null;
@Input() path: string;
@Input() component: Type<any>;
@Input() loadComponent: LoadComponent;
@Input() load: Load;
@Input() reuse = true;
@Input() redirectTo!: string;

Expand All @@ -65,7 +67,8 @@ export class RouteComponent implements OnInit {
private router: Router,
private routerComponent: RouterComponent,
private resolver: ComponentFactoryResolver,
private viewContainerRef: ViewContainerRef
private viewContainerRef: ViewContainerRef,
private compiler: Compiler
) {}

ngOnInit(): void {
Expand All @@ -76,7 +79,7 @@ export class RouteComponent implements OnInit {

this.route = this.routerComponent.registerRoute({
path,
loadComponent: this.loadComponent,
load: this.load,
});

const activeRoute$ = this.routerComponent.activeRoute$.pipe(
Expand All @@ -99,6 +102,8 @@ export class RouteComponent implements OnInit {

return this.loadAndRender(current.route);
}

return of(null);
} else if (rendered) {
return of(this.clearView());
}
Expand All @@ -115,12 +120,30 @@ export class RouteComponent implements OnInit {
}

private loadAndRender(route: Route) {
if (route.loadComponent) {
return route.loadComponent().then((component) => {
return this.renderComponent(component);
});
if (route.load) {
return from(route.load().then(componentOrModule => {
if (componentOrModule instanceof NgModuleFactory) {
const moduleRef = componentOrModule.create(this.viewContainerRef.injector);
const component = moduleRef.instance.routeComponent;

this.renderComponent(component);
} else if (componentOrModule.ɵmod) {
return this.compiler.compileModuleAsync(componentOrModule as Type<any>).then(moduleFactory => {
const moduleRef = moduleFactory.create(this.viewContainerRef.injector);
const component = moduleRef.instance.routeComponent;
this.renderComponent(component);

return true;
});
} else {
this.renderComponent(componentOrModule);
}

return true;
}));
} else {
return of(this.showTemplate());
this.showTemplate();
return of(true);
}
}

Expand All @@ -133,8 +156,6 @@ export class RouteComponent implements OnInit {
this.viewContainerRef.length,
this.viewContainerRef.injector
);

return of(true);
}

private clearComponent() {
Expand All @@ -153,7 +174,7 @@ export class RouteComponent implements OnInit {
}

private clearView() {
if (this.loadComponent) {
if (this.load) {
this.clearComponent();
} else {
this.hideTemplate();
Expand Down
10 changes: 7 additions & 3 deletions libs/angular-routing/src/lib/route.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Type } from '@angular/core';
import { Type, NgModuleFactory } from '@angular/core';

import { Params } from './route-params.service';

export type LoadComponent = () => Promise<Type<any>>;
export type Load = () => Promise<NgModuleFactory<any>|Type<any>|any>;

export interface Route {
path: string;
// component?: Type<any>;
loadComponent?: LoadComponent;
load?: Load;
matcher?: RegExp;
}

export interface ActiveRoute {
route: Route;
params: Params;
}

export interface ModuleWithRoute {
routeComponent: Type<any>;
}

0 comments on commit 0d2aa76

Please sign in to comment.