Skip to content

Commit 2da6614

Browse files
authored
fix(transferstate): make sure it fires even on first load in lazy-loaded module (#268)
1 parent 1a452d1 commit 2da6614

File tree

4 files changed

+47
-21
lines changed

4 files changed

+47
-21
lines changed

projects/sampleBlog/src/app/user/post/post.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<h4>Post details</h4>
12
<section *ngIf="post$ | async as post">
23
<h4>{{ post.title }}</h4>
34

projects/sampleBlog/src/app/user/post/post.component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {HttpClient} from '@angular/common/http';
1313
})
1414
export class PostComponent implements OnInit {
1515
postId$: Observable<number> = this.route.params.pipe(
16-
pluck('post'),
16+
pluck('postId'),
1717
filter(val => ![undefined, null].includes(val)),
1818
map(val => parseInt(val, 10)),
1919
shareReplay(1)
@@ -42,9 +42,7 @@ export class PostComponent implements OnInit {
4242
private route: ActivatedRoute,
4343
private http: HttpClient,
4444
private transferState: TransferStateService
45-
) {
46-
console.log('post inits');
47-
}
45+
) {}
4846

4947
ngOnInit() {}
5048
}

projects/scullyio/ng-lib/src/lib/idleMonitor/idle-monitor.service.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {Injectable, NgZone} from '@angular/core';
2-
import {Router, NavigationEnd} from '@angular/router';
2+
import {NavigationEnd, Router} from '@angular/router';
33
import {BehaviorSubject} from 'rxjs';
4-
import {filter, map, tap, startWith, shareReplay, pluck, take} from 'rxjs/operators';
4+
import {filter, pluck, take, tap} from 'rxjs/operators';
5+
import {TransferStateService} from '../transfer-state/transfer-state.service';
56

67
// tslint:disable-next-line: no-any
78
// tslint:disable: no-string-literal
@@ -26,7 +27,7 @@ export class IdleMonitorService {
2627
private appReady = new Event('AngularReady', {bubbles: true, cancelable: false});
2728
private appTimeout = new Event('AngularTimeout', {bubbles: true, cancelable: false});
2829

29-
constructor(private zone: NgZone, private router: Router) {
30+
constructor(private zone: NgZone, private router: Router, private tss: TransferStateService) {
3031
if (window) {
3132
window.dispatchEvent(this.initApp);
3233
this.router.events

projects/scullyio/ng-lib/src/lib/transfer-state/transfer-state.service.ts

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {DOCUMENT} from '@angular/common';
22
import {Inject, Injectable} from '@angular/core';
33
import {NavigationEnd, NavigationStart, Router} from '@angular/router';
4-
import {BehaviorSubject, EMPTY, forkJoin, Observable} from 'rxjs';
5-
import {filter, first, map, pluck, switchMap, tap} from 'rxjs/operators';
4+
import {BehaviorSubject, forkJoin, NEVER, Observable, of} from 'rxjs';
5+
import {catchError, filter, first, map, pluck, switchMap, tap} from 'rxjs/operators';
66
import {fetchHttp} from '../utils/fetchHttp';
77
import {isScullyGenerated, isScullyRunning} from '../utils/isScully';
88

@@ -21,11 +21,11 @@ interface State {
2121
})
2222
export class TransferStateService {
2323
private script: HTMLScriptElement;
24-
private isNavigatingBS = new BehaviorSubject<boolean>(false);
24+
// private stateBS = new BehaviorSubject<State>({});
25+
/** subject to fire off incomming states */
26+
private initialUrl: string;
2527
private stateBS = new BehaviorSubject<State>({});
26-
private state$ = this.isNavigatingBS.pipe(
27-
switchMap(isNav => (isNav ? EMPTY : this.stateBS.asObservable()))
28-
);
28+
private state$ = this.stateBS.pipe(filter(state => state !== undefined));
2929

3030
constructor(@Inject(DOCUMENT) private document: Document, private router: Router) {
3131
this.setupEnvForTransferState();
@@ -40,6 +40,9 @@ export class TransferStateService {
4040
this.document.head.appendChild(this.script);
4141
} else if (isScullyGenerated()) {
4242
// On the client AFTER scully rendered it
43+
this.initialUrl = window.location.pathname || '__no_NO_no__';
44+
this.initialUrl = this.initialUrl.endsWith('/') ? this.initialUrl.slice(0, -1) : this.initialUrl;
45+
/** set the initial state */
4346
this.stateBS.next((window && window[SCULLY_SCRIPT_ID]) || {});
4447
}
4548
}
@@ -50,6 +53,19 @@ export class TransferStateService {
5053
* @param name The name of the state to
5154
*/
5255
getState<T>(name: string): Observable<T> {
56+
/**
57+
* We need the initial state only when the app is booting.
58+
* In this case, the router doesn't fire an event.
59+
* As the boot process is SYNC, putting in anything async will cause flicker in the view.
60+
* we can't use the subject in this case, because it will fire the
61+
* data sync before the component is ready.
62+
*/
63+
// if (this.initial) {
64+
// this.initial = false;
65+
// // this.stateBS.next(this.state);
66+
// return of(this.state[name]);
67+
// }
68+
/** once booted, the router will make sure this event fires after navigation */
5369
return this.state$.pipe(pluck(name));
5470
}
5571

@@ -75,7 +91,15 @@ export class TransferStateService {
7591
.pipe(
7692
filter(e => e instanceof NavigationStart),
7793
switchMap((e: NavigationStart) => {
78-
this.isNavigatingBS.next(true);
94+
if (this.initialUrl === e.url) {
95+
this.initialUrl = '__done__with__Initial__navigation__';
96+
return NEVER;
97+
}
98+
return of(e);
99+
}),
100+
/** reset the state, so new components will never get stale data */
101+
tap(() => this.stateBS.next(undefined)),
102+
switchMap((e: NavigationStart) => {
79103
return forkJoin([
80104
/** prevent emitting before navigation to _this_ URL is done. */
81105
this.router.events.pipe(
@@ -95,16 +119,18 @@ export class TransferStateService {
95119
const newStateStr = html.split(SCULLY_STATE_START)[1].split(SCULLY_STATE_END)[0];
96120
return JSON.parse(newStateStr);
97121
} catch {
98-
return null;
122+
/** in case of emergency (no state parsing possible,set state to undefined) */
123+
return {};
99124
}
100125
}),
101-
/** prevent progressing in case anything went sour above */
102-
filter(val => val !== null),
103-
/** activate the new state */
126+
catchError(e => {
127+
// TODO: come up with better error text.
128+
/** the developer needs to know, but its not fatal, so just return an empty state */
129+
console.warn('Error for getState during navigation:', e);
130+
return of({});
131+
}),
104132
tap(newState => {
105-
/** signal to send out update */
106-
this.isNavigatingBS.next(false);
107-
/** replace the state, so we don't leak memory on old state */
133+
/** and activate the state in the components. on any error it will be empty */
108134
this.stateBS.next(newState);
109135
})
110136
)

0 commit comments

Comments
 (0)