Transfer State avec Angular 8

14/11/19 dannyEnglish Version

Qu’allons nous faire ?

Nous allons rajouter dans notre projet deux modules ServerTransferStateModule et BrowserTransferStateModule.
Nous utiliserons le framework javascript Angular version 8.2.14

Il s'agit de l'étape 7 de notre guide Angular qui nous permettra d'obtenir une Application Web de type PWA.

Le projet Angular de base que nous utiliserons dispose déjà des caractéristiques suivantes

  • Généré avec Angular CLI
  • Le Routing
  • Le Lazy Loading
  • Le framework CSS Bootstrap
  • Server Side Rendering
  • HttpClient

Tous les sources créés sont indiqués en fin de tutoriel.

L' application est à l'adresse suivante 


Pratique

Le module httpclient associé au SSR (serveur side rendering) génèrent deux requêtes lors d'un appel d'api.
Ce qui double la charge de travail sur un backend

L’objectif de ce tutoriel est d’améliorer le server side rendering (SSR) de notre application Angular.


Vérification

Un appel d'api dans notre code lance à priori deux requêtes sur le serveur d'api.
Nous allons tout d'abord vérifier la théorie.

Le code que nous allons analyser se trouve dans le fichier items.component.ts
Pour cela nous allons procéder à quelques modifications dans ce fichier.

src/app/modules/application/items/items.component.ts
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;
  constructor(
    private itemsService: ItemsService,
    @Inject(PLATFORM_ID) private platformId: Object,
    @Inject(APP_ID) private appId: string) {
  }

  ngOnInit() {
    this.getUsers();
  }

  getUsers() {
    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;
        });
  }

}

Nous allons maintenant vérifier cette théorie.

Cas n° 1 (sans SSR)

  • npm run start
  • Lancer Chrome
  • Activer les outils de développement avec Ctrl + Maj + J
  • Lancer http://localhost:4200/items
  • dans la console de chrome vérifier un appel de requête dans le browser
  • getUsers : Running in the browser with appId=angular-starter


Cas n° 2 (avec SSR)

  • npm run build:ssr
  • npm run serve:ssr
  • Lancer Chrome
  • Activer les outils de développement avec Ctrl + Maj + J
  • Lancer http://localhost:4000/items
  • dans la console de chrome vérifier un appel de requête dans le browser
  • getUsers : Running in the browser with appId=angular-starter
  • Dans la console de lancement du prompt  vérifier un appel de requête dans le server
  • Node server listening on http://localhost:4000
  • getUsers : Running on the server with appId=angular-starter

Modification

La solution consiste à utiliser deux modules d'angular. 
ServerTransferStateModule et BrowserTransferStateModule.

Pour cela il nous faut modifier quelques uns de nos fichiers.
Les étapes sont les suivantes.

  • Modifier main.ts
  • Créer src/app/app.browser.module.ts
  • Modifier app.server.module.ts
  • Modifier app.module.ts
  • Modifier items.component.ts
  • Modifier items.component.spec.ts
  • Modifier tslint.json
src/main.ts
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();
}

document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic().bootstrapModule(AppBrowserModule)
    .catch(err => console.log(err));
});
src/app/app.browser.module.ts
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 { }
src/app/app.server.module.ts
import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { BrowserModule } from '@angular/platform-browser';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule,
    ServerTransferStateModule,
    BrowserModule.withServerTransition({ appId: 'angular-starter' }),
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule { }
src/app/app.module.ts
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: [
    AppRoutingModule,
    HttpClientModule
],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
src/app/modules/items/items.component.ts
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 = [];

  constructor(
    private state: TransferState,
    private itemsService: ItemsService,
    @Inject(PLATFORM_ID) private platformId: object,
    @Inject(APP_ID) private appId: string) {
  }

  ngOnInit() {
    this.getUsers();
  }

  getUsers() {

    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.state.set(STATE_KEY_ITEMS, <any> items);
          });
    }
  }

}
src/app/modules/items/items.component.spec.ts
import { async, 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(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientModule,
        BrowserTransferStateModule
      ],
      declarations: [ ItemsComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ItemsComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

L'analyse de code avec la commande npm run lint génère des erreurs.
Nous allons rajouter une option dans tslint.json

  • no-angle-bracket-type-assertion
tslint.json
    "template-no-negated-async": true,
    "use-lifecycle-interface": true,
    "use-pipe-transform-interface": true,
    "no-angle-bracket-type-assertion": false  

Conclusion

Effectuer le test précédent pour constater qu'il n'y a plus qu'un appel d'api sur le serveur.

  • npm run build:ssr
  • npm run server:ssr
  • localhost:4000/items


Il ne reste plus qu'à tester les différents scripts Angular pour finaliser l'application.

# Développement
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/

Laissez un commentaire

Votre avis
Cette adresse ne sera pas publiée