Skip to content

Commit 3c6963a

Browse files
Merge pull request #160 from ui-router/aitboudad-ssr-lazyload
Aitboudad ssr lazyload
2 parents eadb582 + 5ae9051 commit 3c6963a

File tree

2 files changed

+143
-4
lines changed

2 files changed

+143
-4
lines changed

src/uiRouterNgModule.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
/** */
33
import { Ng2StateDeclaration } from "./interface";
44
import {
5-
NgModule, ModuleWithProviders, ANALYZE_FOR_ENTRY_COMPONENTS, Provider, Injector, InjectionToken
5+
NgModule, ModuleWithProviders, ANALYZE_FOR_ENTRY_COMPONENTS, Provider, Injector, InjectionToken, APP_INITIALIZER, PLATFORM_ID,
66
} from "@angular/core";
7-
import { CommonModule, LocationStrategy, HashLocationStrategy, PathLocationStrategy } from "@angular/common";
7+
import { CommonModule, LocationStrategy, HashLocationStrategy, PathLocationStrategy, isPlatformServer } from "@angular/common";
88
import { _UIROUTER_DIRECTIVES } from "./directives/directives";
99
import { UIView } from "./directives/uiView";
10-
import { UrlRuleHandlerFn, TargetState, TargetStateDef, UIRouter } from "@uirouter/core";
10+
import { UrlRuleHandlerFn, TargetState, TargetStateDef, UIRouter, TransitionService } from "@uirouter/core";
1111
import { _UIROUTER_INSTANCE_PROVIDERS, _UIROUTER_SERVICE_PROVIDERS } from "./providers";
1212

1313
import { ROUTES } from "@angular/router";
@@ -16,12 +16,27 @@ import { ROUTES } from "@angular/router";
1616
/** @hidden */ export const UIROUTER_STATES = new InjectionToken("UIRouter States");
1717
// /** @hidden */ export const ROUTES = UIROUTER_STATES;
1818

19-
export function makeRootProviders(module: StatesModule): Provider[] {
19+
// Delay angular bootstrap until first transition is successful, for SSR.
20+
// See https://github.com/ui-router/angular/pull/127
21+
export function onTransitionReady(transitionService: TransitionService, root: RootModule[]) {
22+
let mod = root[0];
23+
if (!mod || !mod.deferInitialRender) {
24+
return () => Promise.resolve();
25+
}
26+
27+
return () => new Promise(resolve => {
28+
const hook = trans => { trans.promise.then(resolve, resolve); };
29+
transitionService.onStart({}, hook, { invokeLimit: 1 });
30+
});
31+
}
32+
33+
export function makeRootProviders(module: RootModule): Provider[] {
2034
return [
2135
{ provide: UIROUTER_ROOT_MODULE, useValue: module, multi: true},
2236
{ provide: UIROUTER_MODULE_TOKEN, useValue: module, multi: true },
2337
{ provide: ROUTES, useValue: module.states || [], multi: true },
2438
{ provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: module.states || [], multi: true },
39+
{ provide: APP_INITIALIZER, useFactory: onTransitionReady, deps: [TransitionService, UIROUTER_ROOT_MODULE], multi: true },
2540
];
2641
}
2742

@@ -172,6 +187,17 @@ export interface RootModule extends StatesModule {
172187
* Sets [[UrlRouterProvider.deferIntercept]]
173188
*/
174189
deferIntercept?: boolean;
190+
191+
/**
192+
* Tells Angular to defer the first render until after the initial transition is complete.
193+
*
194+
* When `true`, adds an async `APP_INITIALIZER` which is resolved after any `onSuccess` or `onError`.
195+
* The initializer stops angular from rendering the root component until after the first transition completes.
196+
* This may prevent initial page flicker while the state is being loaded.
197+
*
198+
* Defaults to `false`
199+
*/
200+
deferInitialRender?: boolean;
175201
}
176202

177203
/**
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { async, inject, TestBed } from '@angular/core/testing';
2+
import { UIRouterModule } from '../../src/uiRouterNgModule';
3+
import { UIView } from '../../src/directives/uiView';
4+
import { memoryLocationPlugin, UIRouter } from '@uirouter/core';
5+
import { ApplicationInitStatus, Component, NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core';
6+
import { Ng2StateDeclaration } from '../../src/interface';
7+
8+
const timeout = (delay?: number) => new Promise(resolve => setTimeout(resolve, delay));
9+
const configFn = (router: UIRouter) => router.plugin(memoryLocationPlugin);
10+
11+
@Component({ selector: 'home', template: 'HOME' })
12+
export class HomeComponent { }
13+
14+
@Component({ selector: 'home', template: '<h1>APP</h1><ui-view></ui-view>' })
15+
export class AppComponent { }
16+
17+
const setupTests = (deferInitialRender: boolean) => {
18+
let resolve;
19+
const promise = new Promise<any>(_resolve => resolve = _resolve);
20+
21+
const homeState: Ng2StateDeclaration = {
22+
name: 'home',
23+
component: HomeComponent,
24+
url: '/home',
25+
resolve: [
26+
{ token: 'data', resolveFn: () => promise },
27+
]
28+
};
29+
30+
const routerModule = UIRouterModule.forRoot({
31+
useHash: true,
32+
states: [homeState],
33+
deferInitialRender: deferInitialRender,
34+
config: configFn,
35+
});
36+
37+
TestBed.configureTestingModule({
38+
declarations: [HomeComponent, AppComponent],
39+
imports: [routerModule],
40+
providers: [
41+
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
42+
]
43+
});
44+
45+
return resolve;
46+
};
47+
48+
describe('deferInitialRender == false', () => {
49+
let resolve, router: UIRouter, status: ApplicationInitStatus;
50+
beforeEach(() => {
51+
resolve = setupTests(false)
52+
});
53+
54+
beforeEach(inject([UIRouter, ApplicationInitStatus], (_router, _status) => {
55+
router = _router;
56+
status = _status;
57+
}));
58+
59+
it('should not wait for initial transition', async done => {
60+
const { stateService } = router;
61+
const fixture = TestBed.createComponent(AppComponent);
62+
63+
expect(status.done).toBe(false);
64+
const goPromise = stateService.go('home');
65+
66+
fixture.detectChanges();
67+
await fixture.whenStable();
68+
expect(status.done).toBe(false);
69+
70+
await timeout();
71+
expect(status.done).toBe(true);
72+
73+
resolve();
74+
await goPromise;
75+
done();
76+
});
77+
});
78+
79+
describe('deferInitialRender == true', () => {
80+
let resolve, router: UIRouter, status: ApplicationInitStatus;
81+
beforeEach(() => {
82+
resolve = setupTests(true)
83+
});
84+
85+
beforeEach(inject([UIRouter, ApplicationInitStatus], (_router, _status) => {
86+
router = _router;
87+
status = _status;
88+
}));
89+
90+
it('should wait for initial transition', async done => {
91+
const { stateService } = router;
92+
const fixture = TestBed.createComponent(AppComponent);
93+
94+
expect(status.done).toBe(false);
95+
const goPromise = stateService.go('home');
96+
97+
fixture.detectChanges();
98+
await fixture.whenStable();
99+
expect(status.done).toBe(false);
100+
101+
await timeout();
102+
resolve();
103+
104+
await goPromise;
105+
expect(status.done).toBe(false);
106+
107+
await timeout();
108+
expect(status.done).toBe(true);
109+
110+
done();
111+
});
112+
});
113+

0 commit comments

Comments
 (0)