Blog>
Snippets

Preloading Data with Angular Universal

Explain how to preload server-side data before rendering the application, to be available immediately on the client side.
// app.server.module.ts
import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';

@NgModule({
  imports: [
    // The ServerModule includes providers for using Angular on the server.
    ServerModule,

    // Add ServerTransferStateModule to enable transferring state from server to client
    ServerTransferStateModule
  ],
  // Other module properties...
})
export class AppServerModule {}
This code snippet is part of the server-side Angular module definition, where you include ServerTransferStateModule in order to enable the transfer of state from server to client.
// some.service.ts
import { Injectable } from '@angular/core';
import { makeStateKey, TransferState } from '@angular/platform-browser';

const SOME_STATE_KEY = makeStateKey('someDataKey');

@Injectable({
  providedIn: 'root'
})
export class SomeService {
  constructor(private transferState: TransferState) {}

  getSomeData() {
    // Logic to fetch data that will be preloaded
    // Example: HTTP request to get data

    // Once the data is retrieved, we store it in the transfer state
    // so it is available on the client side immediately
    this.transferState.set(SOME_STATE_KEY, data);
  }
}
This service contains the function responsible for fetching data. It uses Angular's TransferState to store the data with a specific key so it can be transferred to the client.
// app.server.ts
import { enableProdMode } from '@angular/core';
import { ngExpressEngine } from '@nguniversal/express-engine';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

// The AppServerModuleNgFactory will be generated with the Angular AOT compiler.
import { AppServerModuleNgFactory } from './app/app.server.module.ngfactory';

// Express server
import * as express from 'express';
import { join } from 'path';

// Other imports...

const app = express();

app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

// More app setup...

app.listen(PORT, () => {
  console.log(`Running on http://localhost:${PORT}`);
});
This is the server setup file where you configure the ngExpressEngine with your AppServerModuleNgFactory. This enables Angular Universal to render the application on the server.
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { TransferState, makeStateKey } from '@angular/platform-browser';

const SOME_STATE_KEY = makeStateKey('someDataKey');

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  data: any;

  constructor(private transferState: TransferState) {}

  ngOnInit() {
    // We check if the data is already loaded by the server
    if (this.transferState.hasKey(SOME_STATE_KEY)) {
      // If it is, use the transferred state
      this.data = this.transferState.get(SOME_STATE_KEY, null);
      // Remove the state to avoid memory leaks
      this.transferState.remove(SOME_STATE_KEY);
    } else {
      // If not, initiate the request for the data.
      // This branch is for the client side.
    }
  }
}
In the app component, we check whether the data we need is available in the TransferState. If it is, we use it directly to prevent additional requests on the client side. Otherwise, we fall back to the usual way of fetching the data.