Transfer State with Angular 14
What are we going to do?
We will add in our project two modules ServerTransferStateModule and BrowserTransferStateModule.
We will use the Angular version 13.1.1 javascript framework.
This is Step 7 of our Angular guide which will allow us to obtain a PWA Web Application.
We will use an existing project whose characteristics are
- Generated with Angular CLI
- Routing
- Lazy Loading
- Framework CSS Bootstrap
- Server Side Rendering
- HttpClient
All created sources are indicated at the end of the tutorial.
The application is at the following address
Practice
The httpclient module associated with the SSR (side rendering server) generates two requests during an API call.
Doubling the workload on a backend
The goal of this tutorial is to improve the server side rendering (SSR) of our Angular application.
Vérification
An API call in our code starts two requests on the API server.
We will first check the theory.
The code we are going to parse is in the file items.component.ts
For that we will make some modifications in this file.
import { Component, OnInit } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { PLATFORM_ID, APP_ID, Inject } from '@angular/core';
import { ItemsService } from './items.service';
@Component({
selector: 'app-items',
templateUrl: './items.component.html',
styleUrls: ['./items.component.css']
})
export class ItemsComponent implements OnInit {
items: any;
loaded: boolean;
constructor(
private itemsService: ItemsService,
@Inject(PLATFORM_ID) private platformId: Object,
@Inject(APP_ID) private appId: string) {
this.loaded = false;
}
ngOnInit(): void {
this.getUsers();
}
getUsers(): void {
this.itemsService.getItems('https://jsonplaceholder.typicode.com/users')
.subscribe(
items => {
const platform = isPlatformBrowser(this.platformId) ?
'in the browser' : 'on the server';
console.log(`getUsers : Running ${platform} with appId=${this.appId}`);
this.loaded = true;
this.items = items;
});
}
resetUsers(): void {
this.items = null;
this.loaded = true;
}
}
We will now verify this theory.
Case 1 (without SSR)
- npm run start
- Launch Chrome
- Enable development tools with Ctrl + Maj + J
- Launch http://localhost:4200/items
- in the chrome console check a query call in the browser
- getUsers : Running in the browser with appId=angular-starter
Case 2 (with SSR)
- npm run build:ssr
- npm run serve:ssr
- Launch Chrome
- Enable development tools with Ctrl + Maj + J
- Lancer http://localhost:4000/items
- in the chrome console check a query call in the browser
- getUsers : Running in the browser with appId=angular-starter
- In the launch console of the prompt check a query call in the server
- Node server listening on http://localhost:4000
- getUsers : Running on the server with appId=angular-starter
Modification
The solution is to use two angular modules.
ServerTransferStateModule et BrowserTransferStateModule.
For this we need to modify some of our files.
The steps are as follows.
- Edit main.ts
- Create src/app/app.browser.module.ts
- Edit app.server.module.ts
- Edit app.module.ts(rajout de CommonModule)
- Edit items.component.ts
- Edit items.component.spec.ts
- Edit tslint.json
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppBrowserModule } from './app/app.browser.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
function bootstrap() {
platformBrowserDynamic().bootstrapModule(AppBrowserModule)
.catch(err => console.error(err));
};
if (document.readyState === 'complete') {
bootstrap();
} else {
document.addEventListener('DOMContentLoaded', bootstrap);
}
import { NgModule } from '@angular/core';
import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
AppModule,
BrowserModule.withServerTransition({ appId: 'angular-starter' }),
BrowserTransferStateModule
],
bootstrap: [AppComponent],
})
export class AppBrowserModule { }
import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { BrowserModule } from '@angular/platform-browser';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
AppModule,
ServerModule,
ServerTransferStateModule,
BrowserModule.withServerTransition({ appId: 'angular-starter' }),
],
bootstrap: [AppComponent],
})
export class AppServerModule { }
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './modules/general/home/home.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Component, OnInit } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { PLATFORM_ID, APP_ID, Inject } from '@angular/core';
import { ItemsService } from './items.service';
const STATE_KEY_ITEMS = makeStateKey('items');
@Component({
selector: 'app-items',
templateUrl: './items.component.html',
styleUrls: ['./items.component.css']
})
export class ItemsComponent implements OnInit {
// items: any;
items: any = [];
loaded: boolean;
constructor(
private state: TransferState,
private itemsService: ItemsService,
@Inject(PLATFORM_ID) private platformId: object,
@Inject(APP_ID) private appId: string) {
this.loaded = false;
}
ngOnInit(): void {
this.getUsers();
}
getUsers(): void {
this.loaded = false;
this.items = this.state.get(STATE_KEY_ITEMS, <any> []);
if (this.items.length === 0) {
this.itemsService.getItems('https://jsonplaceholder.typicode.com/users')
.subscribe(
items => {
const platform = isPlatformBrowser(this.platformId) ?
'in the browser' : 'on the server';
console.log(`getUsers : Running ${platform} with appId=${this.appId}`);
this.items = items;
this.loaded = true;
this.state.set(STATE_KEY_ITEMS, <any> items);
});
} else {
this.loaded = true;
}
}
resetUsers(): void {
this.items = null;
this.loaded = true;
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientModule } from '@angular/common/http';
import { ItemsComponent } from './items.component';
import { BrowserTransferStateModule } from '@angular/platform-browser';
describe('ItemsComponent', () => {
let component: ItemsComponent;
let fixture: ComponentFixture<ItemsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
HttpClientModule,
BrowserTransferStateModule
],
declarations: [ItemsComponent]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ItemsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Conclusion
Perform the previous test to find that there is only one API call on the server.
- npm run build:ssr
- npm run server:ssr
- localhost:4000/items
It only remains to test the different Angular scripts to finalize the application.
# Development
npm run start
http://localhost:4200/
# Tests
npm run lint
npm run test
npm run e2e
# AOT compilation
npm run build
# SSR compilation
npm run build:ssr
npm run serve:ssr
http://localhost:4000/
Source code
The source code used at the beginning of the tutorial is available on github
https://github.com/ganatan/angular-httpclient
The source code obtained at the end of this tutorial is available on github
https://github.com/ganatan/angular-transferstate
The tutorials for obtaining the application are as follows
- Step 1 : Getting started with Angular
- Step 2 : Routing with Angular
- Step 3 : Lazy loading with Angular
- Step 4 : Bootstrap with Angular
- Step 5 : Server Side Rendering with Angular
- Step 6 : HttpClient with Angular
- Step 7 : Transfer State with Angular
- Step 8 : Progressive Web App with Angular
- Step 9 : Search Engine Optimization with Angular
The source code of the application is available on github
https://github.com/ganatan/angular-app