1
1
import { DOCUMENT } from '@angular/common' ;
2
2
import { Inject , Injectable } from '@angular/core' ;
3
3
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' ;
6
6
import { fetchHttp } from '../utils/fetchHttp' ;
7
7
import { isScullyGenerated , isScullyRunning } from '../utils/isScully' ;
8
8
@@ -21,11 +21,11 @@ interface State {
21
21
} )
22
22
export class TransferStateService {
23
23
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 ;
25
27
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 ) ) ;
29
29
30
30
constructor ( @Inject ( DOCUMENT ) private document : Document , private router : Router ) {
31
31
this . setupEnvForTransferState ( ) ;
@@ -40,6 +40,9 @@ export class TransferStateService {
40
40
this . document . head . appendChild ( this . script ) ;
41
41
} else if ( isScullyGenerated ( ) ) {
42
42
// 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 */
43
46
this . stateBS . next ( ( window && window [ SCULLY_SCRIPT_ID ] ) || { } ) ;
44
47
}
45
48
}
@@ -50,6 +53,19 @@ export class TransferStateService {
50
53
* @param name The name of the state to
51
54
*/
52
55
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 */
53
69
return this . state$ . pipe ( pluck ( name ) ) ;
54
70
}
55
71
@@ -75,7 +91,15 @@ export class TransferStateService {
75
91
. pipe (
76
92
filter ( e => e instanceof NavigationStart ) ,
77
93
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 ) => {
79
103
return forkJoin ( [
80
104
/** prevent emitting before navigation to _this_ URL is done. */
81
105
this . router . events . pipe (
@@ -95,16 +119,18 @@ export class TransferStateService {
95
119
const newStateStr = html . split ( SCULLY_STATE_START ) [ 1 ] . split ( SCULLY_STATE_END ) [ 0 ] ;
96
120
return JSON . parse ( newStateStr ) ;
97
121
} catch {
98
- return null ;
122
+ /** in case of emergency (no state parsing possible,set state to undefined) */
123
+ return { } ;
99
124
}
100
125
} ) ,
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
+ } ) ,
104
132
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 */
108
134
this . stateBS . next ( newState ) ;
109
135
} )
110
136
)
0 commit comments