angulardependency-injectionserver-side-renderingangular-ssrangular-dependency-injection

How to access provider value in Angular 19


Using Angular SSR, I would like to access a server-side value in my app.component.

Here is my server-side route:

app.get('**', (req, res, next) => {
  const { protocol, originalUrl, baseUrl, headers } = req;

  commonEngine
    .render({
      bootstrap,
      documentFilePath: indexHtml,
      url: `${protocol}://${headers.host}${originalUrl}`,
      publicPath: browserDistFolder,
      providers: [
        {
          provide: APP_BASE_HREF,
          useValue: baseUrl
        },
        {
          provide: 'message',
          useValue: 'Message provided server-side'
        }
      ],
    })
    .then((html) => res.send(html))
    .catch((err) => next(err));
});

I am providing a value: message

Here's my component code, where I would like to pick up the message value.

@Component({
  imports: [
    RouterOutlet,
  ],
  selector: 'app-root',
  styleUrl: './app.component.scss',
  templateUrl: './app.component.html',
})
export class AppComponent {

  constructor(
    @Optional()
    @Inject('message')
    readonly message: string,

    @Inject(PLATFORM_ID)
    private readonly platformId: Object,

    private transferState: TransferState,
  ) {
    const MESSAGE_KEY = makeStateKey<string>('message');

    if (isPlatformBrowser(this.platformId)) {
      this.message = this.transferState.get(MESSAGE_KEY, 'Message provided client-side');
    }
    else {
      console.log(this.message); // this value is null

      this.transferState.set(MESSAGE_KEY, this.message);
    }
  }
}

message is always null.

Here's my app.config where I'm specifying client hydration:

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideClientHydration(withEventReplay())
  ]
};

Here's the app.config.server

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(),
  ]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);

What is the correct way to access provided values?


Solution

  • You need to add the providers at the app.config.ts, adding two different values for main.ts(app.config.ts) and main.server.ts(app.config.server.ts).

    app.config.ts

    import {
      ApplicationConfig,
      InjectionToken,
      makeEnvironmentProviders,
      provideZoneChangeDetection,
    } from '@angular/core';
    import { provideRouter } from '@angular/router';
    
    import { routes } from './app.routes';
    import { provideClientHydration } from '@angular/platform-browser';
    export const MESSAGE = new InjectionToken<string>('message'); // <- this must be on the app.config.ts (!important)
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideClientHydration(),
        { provide: MESSAGE, useValue: 'message from the browser' },
      ],
    };
    

    app.config.server.ts

    import {
      mergeApplicationConfig,
      ApplicationConfig,
      InjectionToken,
    } from '@angular/core';
    import { provideServerRendering } from '@angular/platform-server';
    import { appConfig, MESSAGE } from './app.config';
    
    const serverConfig: ApplicationConfig = {
      providers: [
        provideServerRendering(),
        { provide: MESSAGE, useValue: 'message from the server' },
      ],
    };
    
    export const config = mergeApplicationConfig(appConfig, serverConfig);
    

    app.component.ts

    import { Component, inject } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    import { MESSAGE } from './app.config';
    import { PLATFORM_ID } from '@angular/core';
    import { isPlatformBrowser } from '@angular/common';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [RouterOutlet],
      templateUrl: './app.component.html',
      styleUrl: './app.component.scss',
    })
    export class AppComponent {
      title = 'test';
      message = inject(MESSAGE, {
        optional: true,
      });
      platformId = PLATFORM_ID;
    
      ngOnInit() {
        if (isPlatformBrowser(this.platformId)) {
          console.log('from browser', this.message);
        } else {
          console.log('from server', this.message);
        }
      }
    }
    

    Stackblitz Demo -> cd test -> npm i -> npm run start