Angular universal FOUC style on server to client transition

I have a problem with my generic app where when a server side app is replaced for a client side app, there is a point where there are no styles for the components that are part of my routable component for that page. This causes the page to load properly and then instantly displays a flash of loose content (FOUC) and looks awful before sorting itself out.

The styling for the website header and footer components looks great all the time, but the components that are loaded inside the element <router-outlet>

are the ones that don't have the correct styling.

I am using Preboot to manage the server> client transition and do nothing outside of the default configuration. I have experimented with using libraries @ngx-universal/state-transfer

and @ngx-cache

, but I don't think this is what I need.

I'm using lazy loaded routes, but I've experimented with removing them and the error is the same. I also tried setting { initialNavigation: 'enabled' }

in my routing config.

I am using webpack to build my server side app and Angular CLI client side mostly based on this project and I am using the AOT compiler. Any ideas would be much appreciated, thanks!

+3


source to share


1 answer


So the solution I came across was to create StyleStateTransferService

one that uses the @ ngx-universal / state-transfer library.

In a server side application, I pull the angular styles out of my head and add them to the state transfer service. In a client side application, I get the styles from the wrapped state and add them to the head. After angular has finished loading, I go to them and delete the ones I added so there are no duplicates.

I don't know if this is the best solution, maybe someone has a better one, originally I was hoping there was only some configuration that I was missing from preboot, but it is not.

Services:



@Injectable()
export class StyleStateTransferService {
  document: any;
  window: Window;
  renderer: Renderer2;
  ngStyleId = 'ng_styles';

  constructor(private stateTransferService: StateTransferService,
              @Inject(DOCUMENT) document: any,
              private winRef: WindowRef,
              private rendererFactory: RendererFactory2) {
    this.window = winRef.nativeWindow;
    this.document = document;
    this.renderer = rendererFactory.createRenderer(this.document, null);
  }

  addStylesToState() {
    const styles: string[] = this.document.head.children
      // elements have a weird structure on the server
      // this filters to style tags with the ng-transition attribute that have content
      .filter(el =>
        el.name === 'style' && el.attribs['ng-transition'] && el.firstChild && el.firstChild.data)
      // extract the css content of the style tags
      .map(el => el.firstChild.data.replace(/\n/g, ' '));

    this.stateTransferService.set(this.ngStyleId, styles);
    this.stateTransferService.inject();
  }

  injectStylesFromState() {
    const styles = _.get(this.window, `${DEFAULT_STATE_ID}.${this.ngStyleId}`, []);
    styles.forEach(content => {
      const styleEl = this.renderer.createElement('style');
      // set this attribute so we can remove them later
      this.renderer.setAttribute(styleEl, 'ng-state-transfer', null);
      this.renderer.setProperty(styleEl, 'innerHTML', content);
      this.renderer.appendChild(this.document.head, styleEl);
    });
  }

  cleanupInjectedStyles() {
    Array.from(<HTMLElement[]>this.document.head.children)
      .filter(htmlEl => htmlEl.tagName === 'STYLE' && htmlEl.hasAttribute('ng-state-transfer'))
      .forEach(styleEl => this.renderer.removeChild(this.document.head, styleEl));
}

      

Using it in server and browser modules (cleaning takes place in the application component, but it didn't seem indicative to him):

export class AppServerModule {
  constructor(private appRef: ApplicationRef,
              private styleStateTransferService: StyleStateTransferService) {
    // waits until the app has fully initialised before adding the styles to the state
    this.appRef.isStable
      .filter(isStable => isStable)
      .first()
      .subscribe(() => {
        this.styleStateTransferService.addStylesToState();
      });
  }
}

export class AppBrowserModule {
  constructor(private styleStateTransferService: StyleStateTransferService) {
    this.styleStateTransferService.injectStylesFromState();
  }
}

      

+1


source







All Articles