angularangular-scullyscullyscullyio

How to prevent Angular from loading content in runtime when it was already prerendered by Scully?


I have a service that fetches data from headless CMS Cockpit in my Angular app. I am using Scully to prerender my pages. It works great in doing the prerender, it fetches content build time and creates a static page, however when opening page, Angular loads that data again, although it should come from some kind of scully context.


Solution

  • After looking at the source code of scully I managed to find a useful service there: TransferStateService. So I modified my service to use to my advantage in my service that fetches data from Cockpit:

    private getCockpitData<T>(path: string): Observable<T> {
        const url = `${this.baseApiUrl}${path}`;
        const urlHash = btoa(url);
        if (isScullyRunning()) {
          const headers = new HttpHeaders({ 'Cockpit-Token': environment.cockpit.apiccessToken });
          const queryParams = new HttpParams({ fromObject: { lang: 'uk' } });
          return this.http
            .get<T>(url, { headers, params: queryParams })
            .pipe(
              tap(data => this.transferStateService.setState<T>(urlHash, data)),
              shareReplay(1)
            );
        }
        return this.transferStateService.getState<T>(urlHash).pipe(shareReplay(1));
    }
    

    So when scully is running we fetch data and with a side effect use TransferStateService to set data on the page. Otherwise when it's already a generated page we just get that data back from page instead of doing another request. Then any endpoint can just call this method like:

    getHeaderContentData(): Observable<HeaderContent> {
      return this.getCockpitData<HeaderContent>('singletons/get/header');
    }
    

    So when loading data using service all my components will receive correct data with the same interface and internally it handles the source of where data comes from.

    Important note here: make sure you also have IdleMonitorService injected somewhere, e.g. in AppModule like so:

    export class AppModule {
      // @ts-ignore Property 'idle' is declared but its value is never read.
      // IdleMonitorService has to be injected so that the instance of it is created
      // which allows scully to track wether angular page became idle in build time
      constructor(private readonly idle: IdleMonitorService) {}
    }
    

    If IdleMonitorService is not injected anywhere Angular never creates its instance and scully is not able to track when angular becomes idle during prerender, which results in much longer build time and also it will miss window['ScullyIO'] being set to 'generated', so isScullyGenerated() will always return false in runtime.

    EDIT: as of version 0.0.15 of @scullyio/ng-lib IdleMonitorService no longer needs to be injected in AppModule, now it only requires to import ScullyLibModule instead.