Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions projects/sampleBlog/src/app/user/post/post.component.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<h4>Post details</h4>
<section *ngIf="post$ | async as post">
<h4>{{ post.title }}</h4>

Expand Down
6 changes: 2 additions & 4 deletions projects/sampleBlog/src/app/user/post/post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {HttpClient} from '@angular/common/http';
})
export class PostComponent implements OnInit {
postId$: Observable<number> = this.route.params.pipe(
pluck('post'),
pluck('postId'),
filter(val => ![undefined, null].includes(val)),
map(val => parseInt(val, 10)),
shareReplay(1)
Expand Down Expand Up @@ -42,9 +42,7 @@ export class PostComponent implements OnInit {
private route: ActivatedRoute,
private http: HttpClient,
private transferState: TransferStateService
) {
console.log('post inits');
}
) {}

ngOnInit() {}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {Injectable, NgZone} from '@angular/core';
import {Router, NavigationEnd} from '@angular/router';
import {NavigationEnd, Router} from '@angular/router';
import {BehaviorSubject} from 'rxjs';
import {filter, map, tap, startWith, shareReplay, pluck, take} from 'rxjs/operators';
import {filter, pluck, take, tap} from 'rxjs/operators';
import {TransferStateService} from '../transfer-state/transfer-state.service';

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

constructor(private zone: NgZone, private router: Router) {
constructor(private zone: NgZone, private router: Router, private tss: TransferStateService) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm injecting it here because we are starting the idle-service anyway. This will get refactored in an upcomming PR

if (window) {
window.dispatchEvent(this.initApp);
this.router.events
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {DOCUMENT} from '@angular/common';
import {Inject, Injectable} from '@angular/core';
import {NavigationEnd, NavigationStart, Router} from '@angular/router';
import {BehaviorSubject, EMPTY, forkJoin, Observable} from 'rxjs';
import {filter, first, map, pluck, switchMap, tap} from 'rxjs/operators';
import {BehaviorSubject, forkJoin, NEVER, Observable, of} from 'rxjs';
import {catchError, filter, first, map, pluck, switchMap, tap} from 'rxjs/operators';
import {fetchHttp} from '../utils/fetchHttp';
import {isScullyGenerated, isScullyRunning} from '../utils/isScully';

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

constructor(@Inject(DOCUMENT) private document: Document, private router: Router) {
this.setupEnvForTransferState();
Expand All @@ -40,6 +40,9 @@ export class TransferStateService {
this.document.head.appendChild(this.script);
} else if (isScullyGenerated()) {
// On the client AFTER scully rendered it
this.initialUrl = window.location.pathname || '__no_NO_no__';
this.initialUrl = this.initialUrl.endsWith('/') ? this.initialUrl.slice(0, -1) : this.initialUrl;
/** set the initial state */
this.stateBS.next((window && window[SCULLY_SCRIPT_ID]) || {});
}
}
Expand All @@ -50,6 +53,19 @@ export class TransferStateService {
* @param name The name of the state to
*/
getState<T>(name: string): Observable<T> {
/**
* We need the initial state only when the app is booting.
* In this case, the router doesn't fire an event.
* As the boot process is SYNC, putting in anything async will cause flicker in the view.
* we can't use the subject in this case, because it will fire the
* data sync before the component is ready.
*/
// if (this.initial) {
// this.initial = false;
// // this.stateBS.next(this.state);
// return of(this.state[name]);
// }
/** once booted, the router will make sure this event fires after navigation */
return this.state$.pipe(pluck(name));
}

Expand All @@ -75,7 +91,15 @@ export class TransferStateService {
.pipe(
filter(e => e instanceof NavigationStart),
switchMap((e: NavigationStart) => {
this.isNavigatingBS.next(true);
if (this.initialUrl === e.url) {
this.initialUrl = '__done__with__Initial__navigation__';
return NEVER;
}
return of(e);
}),
/** reset the state, so new components will never get stale data */
tap(() => this.stateBS.next(undefined)),
switchMap((e: NavigationStart) => {
return forkJoin([
/** prevent emitting before navigation to _this_ URL is done. */
this.router.events.pipe(
Expand All @@ -95,16 +119,18 @@ export class TransferStateService {
const newStateStr = html.split(SCULLY_STATE_START)[1].split(SCULLY_STATE_END)[0];
return JSON.parse(newStateStr);
} catch {
return null;
/** in case of emergency (no state parsing possible,set state to undefined) */
return {};
}
}),
/** prevent progressing in case anything went sour above */
filter(val => val !== null),
/** activate the new state */
catchError(e => {
// TODO: come up with better error text.
/** the developer needs to know, but its not fatal, so just return an empty state */
console.warn('Error for getState during navigation:', e);
return of({});
}),
tap(newState => {
/** signal to send out update */
this.isNavigatingBS.next(false);
/** replace the state, so we don't leak memory on old state */
/** and activate the state in the components. on any error it will be empty */
this.stateBS.next(newState);
})
)
Expand Down