Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SSR flickering #28

Closed
rafa-suagu opened this issue Aug 14, 2019 · 20 comments
Closed

Fix SSR flickering #28

rafa-suagu opened this issue Aug 14, 2019 · 20 comments

Comments

@rafa-suagu
Copy link
Contributor

The AppInitializer should be adapted to make work properly with Angular Universal + Lazy loaded modules.

I've tested a lot and this https://medium.com/@fessbespalov/enabling-initialnavigation-with-localizerouter-and-angular-universal-8d514f1faad9 solution works like a charm.

When this issue #26 is done we can start to make ngx-translate-router compatible 100% with Angular Universal and lazy loaded modules.

@gilsdav
Copy link
Owner

gilsdav commented Aug 14, 2019

I don't understand how it can help for the "flickering".
And we already have the appInitializer:

appInitializer(): Promise<any> {
    const res = this.parser.load(this.routes);
    res.then(() => {
      const localize: LocalizeRouterService = this.injector.get(LocalizeRouterService);
      localize.init();
    });

    return res;
  }

Do you think we have a problem because we return res and not res.then(... ?

@rafa-suagu
Copy link
Contributor Author

To avoid the flickering using Angular Universal we should add the router param InitialNavigation: 'enabled'. When you add this flag using this library or localize-router, you get the routing broken.

You can see why this happens in the medium post

https://medium.com/@fessbespalov/enabling-initialnavigation-with-localizerouter-and-angular-universal-8d514f1faad9

This happened because LocalizeRouter wasn’t initialized yet and the initial segment of the url (language shortening — en) was perceived as part of the route.

Also you can get more info in the issue of localize-router:

Greentube/localize-router#157

If you don't add the InitialNavigation the SSR shows the rendered page, next you can see a blank page and finally you get back the rendered page.

I don't know exactly why and where is the problem but I'm agree with you, seems to be something about the return, something like the function returned or if need a promise or a pure function, because several differences are between your implementation of AppInitialized and the described in the medium post.

I'll continue investigating about it.

@gilsdav
Copy link
Owner

gilsdav commented Aug 17, 2019

@rafa-as
My idea is that if we return res and not res.then(...), the second "subscriber" probably doesn't way the end of the first "subscription". Because it's not chained.

@rafa-suagu
Copy link
Contributor Author

I think that the point is mark as resolved the promise when the routes has been initialized calling the init method.

export function initLanguage(injector: Injector): () => void {
  return (): Promise<any> =>
    new Promise((resolve) => {
        injector.get(LocalizeRouterService).init();
        resolve();
    });
}

@gilsdav
Copy link
Owner

gilsdav commented Aug 17, 2019

Is it possible for you to try to replace appInitializer to

appInitializer(): Promise<any> {
    const res = this.parser.load(this.routes);
    return res.then(() => {
      const localize: LocalizeRouterService = this.injector.get(LocalizeRouterService);
      localize.init();
    });
  }

into localize-router.module.ts
?

@rafa-suagu
Copy link
Contributor Author

@gilsdav I just tried it but doesn't works. The routes tree is not generated with the language prefix and the translations. The routes are accessible without /{lang} and with the translation key not the value.

@gilsdav
Copy link
Owner

gilsdav commented Aug 17, 2019

@rafa-as I've just tested the post and it doesn't work properly.

For exemple this configuration will give an exception:
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' }

If I understand, Angular use APP_INITIALIZER to load routes and this is executed before ngx-translate-router.

If we put one directly on app.module it will be called first and mutation will do the rest.
I don't like that and we can see it doesn't work in all cases.

@rafa-suagu
Copy link
Contributor Author

For exemple this configuration will give an exception:
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' }

What exception? Can you provide more info?

I will test your route but we have equal routes in our three apps and everything works.

@giacomo
Copy link
Contributor

giacomo commented Aug 25, 2019

more information on that topic
angular/universal#1184

@hymenoby
Copy link

@rafa-as is there a workaround on this issue?

@rafa-suagu
Copy link
Contributor Author

rafa-suagu commented Jan 23, 2020

@hymenoby we have solved it with the following code:

app.module.ts -> providers

{
    provide: LOCATION_INITIALIZED,
    useFactory: initLanguageRoutes,
    deps: [TranslateService, LocalizeParser, Router]
}

app-routing.module.ts

RouterModule.forRoot(
    appRoutes,
    {
        scrollPositionRestoration: 'disabled',
        anchorScrolling: 'enabled',
        scrollOffset: [0, APP_CONSTANTS.contentTopOffset],
        preloadingStrategy: SelectivePreloadingStrategy,
        initialNavigation: 'enabled' // <---------- The important part
    }
)

language.service.ts

export function initLanguageRoutes(translateService: TranslateService, localizeParser: LocalizeParser, router: Router): Promise<void> {
  return new Promise((resolve: any) => {
    // TODO: use constant default language
    const lang = localizeParser.getLocationLang() || 'en';
    concat(
      translateService.use(lang).pipe(
        filter(translations => !!translations),
        first()
      ),
      localizeParser.translateRoutes(translateService.currentLang).pipe(
        first()
      )
    ).subscribe(() => {
      router.resetConfig(localizeParser.routes);
      resolve();
    });
  });
}

@gilsdav what do you think? we can add this logic inside the library?

@hymenoby
Copy link

hymenoby commented Jan 23, 2020

@rafa-as I tried to add the code but now the routes aren't translating any more

here is my app.module.ts


export function createTranslateLoader(http: HttpClient) {
  return new TranslateHttpLoader(http, environment.BASE_API_URL + '/applications/app/views/i18n/', '.json');
}

export function initLanguage(injector: Injector): () => void {
  return (): Promise<any> =>
    new Promise((resolve) => {
      const localizeRouterService = injector.get(LocalizeRouterService);
      localizeRouterService.init();
      resolve();
    });
}

export function initLanguageRoutes(translateService: TranslateService, localizeParser: LocalizeParser, router: Router): Promise<void> {
  return new Promise((resolve: any) => {
    // TODO: use constant default language
    console.log(localizeParser.getLocationLang());
    const lang = localizeParser.getLocationLang() || 'en';
    concat(
      translateService.use(lang).pipe(
        filter(translations => !!translations),
        first()
      ),
      localizeParser.translateRoutes(translateService.currentLang).pipe(
        first()
      )
    ).subscribe(() => {
      router.resetConfig(localizeParser.routes);
      resolve();
    });
  });
}


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({appId: 'ng-universal-app'}),
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient]
      }
    }),
    HttpClientModule,
    NgbModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    LayoutModule,
    ScrollToModule.forRoot(),
    NgxGalleryModule,
    AgmCoreModule.forRoot({
      apiKey: environment.GOOGLE_MAPS_API_KEY
    }),
    ShareButtonModule,
  ],
  providers: [
    /*{
      provide: APP_INITIALIZER,
      useFactory: initLanguage,
      multi: true,
      deps: [Injector]
    },*/
    {
      provide: LOCATION_INITIALIZED,
      useFactory: initLanguageRoutes,
      deps: [TranslateService, LocalizeParser, Router]
    },
    {
      provide: LOCALE_ID,
      useValue: 'fr'
    },
    {
      provide: RouteReuseStrategy,
      useClass: CustomRouteReuseStrategyService
    },
    {
      provide: HAMMER_GESTURE_CONFIG,
      useClass: CustomHammerConfigService
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpInterceptorService,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptorService,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ErrorInterceptorService,
      multi: true
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {
  private $ = $;

  constructor() {
  }
}

and here is my routing.module.ts

export const routes: Routes = [
  {
    path: '',
    loadChildren: () => import('./main/homepage/homepage.module').then((m) => m.HomepageModule),
  },
  {
    path: '',
    loadChildren: () => import('./main/pages/pages.module').then((m) => m.PagesModule),
  },
  {
    path: '',
    loadChildren: () => import('./main/listings/listings.module').then((m) => m.ListingsModule),
  },
  {
    path: '',
    loadChildren: () => import('./main/checkout/checkout.module').then((m) => m.CheckoutModule),
  },
  {
    path: '',
    loadChildren: () =>  import('./main/user/user.module').then((m) => m.UserModule),
  },
  {
    path: '**',
    redirectTo: '/NOT_FOUND',
    pathMatch: 'full'
  }
];


export function localizeRouterParserFactory(translate, location, settings) {
  // return new CustomLocalizeParser(translate, location, settings);
  return new ManualParserLoader(translate, location, settings, environment.LANGUAGES, 'ROUTES.');
}

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      // preloadingStrategy: PreloadAllModules,
      initialNavigation: 'enabled',
      // scrollPositionRestoration: 'top',
      // anchorScrolling: 'enabled',
    }),
    LocalizeRouterModule.forRoot(routes, {
      parser: {
        provide: LocalizeParser,
        useFactory: localizeRouterParserFactory,
        deps: [TranslateService, Location, LocalizeRouterSettings]
      }
    }),
  ],
  exports: [
    RouterModule,
  ]
})
export class AppRoutingModule {
}

After updating like this the routes aren't translated any more

@rafa-suagu
Copy link
Contributor Author

@hymenoby you should export LocalizeRouterModule in your routing file and I think that the APP_INITIALIZER provider should be uncommented.

Normally is a good practice to import the AppRoutingModule the last one

@hymenoby
Copy link

@rafa-as the problem is still not solved in my project, any page where i use a resolver to load data is no more working .

@rafa-suagu
Copy link
Contributor Author

@hymenoby you pages without a resolver works properly?

@hymenoby
Copy link

hymenoby commented Jan 28, 2020

@rafa-as , i tried the changes you were talking about and they didn't work, when i use them, the url without pages without resolver are not translated and the pages with a resolver have an error.

i tried to make it work and i found a workaround for the pages without resolver, i found out that the problems occurs, when the parser does not find the translations to the routes (I think the language service is not loaded at that time or haven't loaded the language file yet). So i extended the default parser to have a parser with fallback translations in a constant (I've done it this way to not override the default process of the modules).

so here is my new parser :


export const fallBackRoutesTranslations = {
  fr: {
    ABOUT_US: 'a-propos',
    USER: 'utilisateur',
    PROFILE: 'profil',
  },
  en: {
    ABOUT_US: 'about-us',
    USER: 'user',
    PROFILE: 'profile',
  }
};

export class CustomLocalizeParser extends LocalizeParser {
  locales: string[];
  currentLang: string;
  routes: Routes;
  defaultLang: string;
  protected prefix: string;
  urlPrefix: string;

  constructor(
    protected _translate: TranslateService,
    protected _location: Location,
    protected _settings: LocalizeRouterSettings
  ) {
    super(_translate, _location, _settings);
  }

  translateRoute(path: string): string {
    let translatedRoute = super.translateRoute(path);
    if (translatedRoute === path) {
      let lang = this.getLocationLang(this._location.path());
      if (this.currentLang) {
        lang = this.currentLang;
      }
      if (this.locales.includes(lang)) {
        if (fallBackRoutesTranslations[lang][path]) {
          // console.log(`NOT : path : "${path}", translated "${translatedRoute}",  new route: ${fallBackRoutesTranslations[lang][path]}`);
          translatedRoute = fallBackRoutesTranslations[lang][path];
        }
      }
    }
    return translatedRoute;
  }

  load(lroutes: Routes): Promise<any> {
    return new Promise((resolve) => {
      this.locales = environment.LANGUAGES,
        this.defaultLang = environment.DEFAULT_LANGUAGE;
      this.prefix = 'ROUTES.';
      this.init(lroutes).then(() => { resolve(); });
    });
  }

}

and in my app routing module :

...
export function localizeRouterParserFactory(translate, location, settings) {
  return new CustomLocalizeParser(translate, location, settings);
}
...

... 
LocalizeRouterModule.forRoot(routes, {
      parser: {
        provide: LocalizeParser,
        useFactory: localizeRouterParserFactory,
        deps: [TranslateService, Location, LocalizeRouterSettings]
      }
    }),
... 

with this parser with fallback translations, the routes without resolver are working but when i have a resolver on one route , it still gives me errors .

Thank you for your support.

@Ismaestro
Copy link

any updates?? I'm stuck here, as lots of people, I think...

@gilsdav
Copy link
Owner

gilsdav commented Jul 8, 2020

Fixed by #70
@Ismaestro Can you test with de described way on this related issue ?

@Ismaestro
Copy link

@gilsdav hellooo I think it's working now!! I don't see any flirk and finally I can enable the 'initialNavigation' flag. I would like someone else to confirm it. Thanks for the fix David!

@gilsdav
Copy link
Owner

gilsdav commented Jul 13, 2020

Deployed in version 3.1.0

@gilsdav gilsdav closed this as completed Jul 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants