angularcookiesserver-side-renderingapp-initializer

Angular: Cookie not available in the first execution of APP_INITIALIZER function, component renders with initial value and re-renders with new value


In my Angular v18 project with SSR enabled, I am trying to get hold of a cookie inside the APP_INITIALIZER function. I'm using ngx-cookie-service and set it up with their instructions for SSR.

The app.config.ts

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({eventCoalescing: true}),
    provideRouter(routes),
    provideClientHydration(),
    provideHttpClient(withFetch()),
    {
      provide: APP_INITIALIZER,
      useFactory: () => {
        const cookieService = inject(SsrCookieService);
        const authService = inject(AuthService);

        return () => new Promise((resolve, reject) => {
          const isAuthenticated = cookieService.check("idToken");
          console.log(isAuthenticated);

          if (isAuthenticated) {
            const idToken = cookieService.get("idToken");
            authService.setUserCredentials(idToken);
            console.log(authService.getIsAuthenticated());

          }

          resolve(true);
        });
      },
      multi: true,
    }
  ]
};

In nav-bar.component.ts

private authService = inject(AuthService);
isAuthenticated = this.authService.getIsAuthenticated();

And in the nav-bar.component.ts template

  @if (!isAuthenticated) {
    <button routerLink="login">Login</button>
  } @else {
    <button routerLink="profile">My Account</button>
  }

The result is that in the IDE terminal, the console log in app.config.ts is false, then in the browser console it's true. This causes the first render of the navigation bar to show Login and then immediately re-render and show My Account.

The <app-nav> is in the AppComponent

  <app-nav/>
  <router-outlet/>
  <app-footer/>

I've tried injecting the DOCUMENT and calling document.cookie, but the page loads with the NotYetImplemented error.

I've also tried with a BaseComponent on / route and a Resolve on it and passing a boolean as a required input to <app-nav/>, but the effect is the same: Login on first render and My Account on second.

I am a bit confused as to what's happening and not sure what knowledge I am missing to be able to achieve the value public isAuthenticated: boolean = false; in AuthService to be true before the app's first render.

Any help is appreciated, thank you!

EDIT: Stackblitz

Steps to reproduce:

  1. Load stackblitz (if 404, press enter in the url bar to reload)
  2. Press Inicia Sesión button
  3. Leave fields empty and press Continuar button to set up a cookie with idToken name
  4. Refresh the browser inside stackblitz to see Inicia Sesión button first and then a re-render changing to Mi cuenta button
  5. Optional: the cookie can be removed using Developer tools

Solution

  • The cookie service does not work when you do npm run start(ng serve), but it works great when you run the SSR server.

    So you can ignore these bugs and carry on development, because when you deploy with SSR, it should work fine.


    Steps to run:

    1. Run npm run build first.

    2. Then run npm run serve:ssr:PizzeriaAngularFE

    3. open the application and you should see the service showing that the application is authenticated, after the first login.

    I made a small correction to you code, looking at the latest documentation on their github page. It might also be not needed and you can use the original method on their ngx-cookie-service - npm page

    ngx-cookie-service - github
    import { REQUEST as SSR_REQUEST } from "ngx-cookie-service-ssr";
    ...
    
    ...
    server.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: SSR_REQUEST, useValue: req}, // <- changed here!
            {provide: 'RESPONSE', useValue: res},
          ],
        })
        .then((html) => res.send(html))
        .catch((err) => next(err));
    });
    

    Github Repo