Skip to content

Commit 5ae9051

Browse files
feat(UIRouterModule): Add deferInitialRender toggle to forRoot. Waits for initial router transition before bootstrapping the app the initial render.
1 parent c688f5b commit 5ae9051

File tree

2 files changed

+138
-11
lines changed

2 files changed

+138
-11
lines changed

src/uiRouterNgModule.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +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 onTransitionReady(transitionService: TransitionService, plateformId) {
20-
if (isPlatformServer(plateformId)) {
21-
return () => Promise.resolve();
22-
}
23-
24-
return () => new Promise(resolve => {
25-
transitionService.onSuccess({}, resolve);
26-
transitionService.onError({}, resolve);
27-
});
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+
});
2831
}
2932

30-
export function makeRootProviders(module: StatesModule): Provider[] {
33+
export function makeRootProviders(module: RootModule): Provider[] {
3134
return [
3235
{ provide: UIROUTER_ROOT_MODULE, useValue: module, multi: true},
3336
{ provide: UIROUTER_MODULE_TOKEN, useValue: module, multi: true },
3437
{ provide: ROUTES, useValue: module.states || [], multi: true },
3538
{ provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: module.states || [], multi: true },
36-
{ provide: APP_INITIALIZER, useFactory: onTransitionReady, deps: [TransitionService, PLATFORM_ID], multi: true },
39+
{ provide: APP_INITIALIZER, useFactory: onTransitionReady, deps: [TransitionService, UIROUTER_ROOT_MODULE], multi: true },
3740
];
3841
}
3942

@@ -184,6 +187,17 @@ export interface RootModule extends StatesModule {
184187
* Sets [[UrlRouterProvider.deferIntercept]]
185188
*/
186189
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;
187201
}
188202

189203
/**
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)