If you keep data about the user in session of the express server that Angular Universal uses, you need to pass this data when the user first loads the page.
For a smooth data flow, you need to do the following:
- Create an injection token for the data you want to pass. I'm using a separate file for the tokens;
import {InjectionToken} from '@angular/core';
export const USER_PROFILE = new InjectionToken<object>('user data');
- In your
server.ts
file, provide this token with the data in theres.render
call
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render(indexHtml, {
req,
providers: [
{provide: APP_BASE_HREF, useValue: req.baseUrl},
{provide: USER_PROFILE, useValue: req.session.user}
]
});
});
Make sure you have
BrowserTransferStateModule
inapp.module.ts
imports
andServerTransferStateModule
inapp.server.module.ts
imports
Create a TransferState service for easier use of the transferstate keys creation and setting and getting values. I'm using code from this tutorial found here.
import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
import {isPlatformServer} from '@angular/common';
@Injectable({
providedIn: 'root'
})
export class TransferStateService {
private keys = new Map<string, StateKey<string>>();
constructor(
@Inject(PLATFORM_ID) private readonly platformId: string,
private readonly transferState: TransferState
) {}
get<T>(
key: string,
defaultValue: T | null = null
): T | null {
if (!this.has(key)) {
return defaultValue || null;
}
return this.transferState.get<T>(
this.getStateKey(key),
<T>defaultValue
);
}
has(key: string): boolean {
return this.transferState.hasKey(this.getStateKey(key));
}
set(key: string, value: any): void {
if (isPlatformServer(this.platformId)) {
if (this.has(key)) {
console.warn(
`Setting existing value into TransferState using key: '${key}'`
);
}
this.transferState.set(
this.getStateKey(key),
value
);
}
}
private getStateKey(key: string): StateKey<string> {
if (this.keys.has(key)) {
return <StateKey<string>> this.keys.get(key);
}
this.keys.set(key, makeStateKey(key));
return <StateKey<string>> this.keys.get(key);
}
}
- Create an
APP_INITIALIZER
factory to use inapp.module.ts
providers
import {PLATFORM_ID, Injector} from '@angular/core';
import {isPlatformServer} from '@angular/common';
import {Store} from '@ngrx/store';
import {TransferStateService} from '@app/services/transfer-state.service';
export function StateTransferFactory(
transferStateService: TransferStateService,
injector: Injector,
store: Store
): any {
return () => {
const platformId = injector.get(PLATFORM_ID);
const isServer = isPlatformServer(platformId);
let user: any;
if (isServer) {
user = injector.get(USER_PROFILE) || null;
transferStateService.set('USER_PROFILE_KEY', user);
} else {
user = transferStateService.get('USER_PROFILE_KEY');
}
store.dispatch(setUser({user})) // I'm using ngrx store to save data in the app
};
}
- Attach this factory to the
APP_INITIALIZER
provider inapp.module.ts
NOTE:app.module.ts
is imported inapp.server.module.ts
and runs both on the server and on the client
{
provide: APP_INITIALIZER,
useFactory: StateTransferFactory,
deps: [TransferStateService, Injector, Store],
multi: true
}
Notice the injector.get(USER_PROFILE)
, which is used to get the value from the injectionToken. You use it only on the server, for the user token, because there is where you provide the value. On the client it would be undefined
.
This way, whenever the user refreshes the app, you already have the store initialized with the user, if the user is on your req.session
on the server.