Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular universal 7 not making http calls

I have recently begun converting my Angular 7.2.0 app to an Angular universal app. The app is successfully rendering server-side, and transfers state fairly quickly on fast PCs and internet connections, but unfortunately until the hand-off, the rendered page is missing any data normally fetched from the API.

I have added the serverURL to a provided token from the server, and while rendering server-side, all http requests are made using the fully qualified url. However, it appears that does not matter, since all http calls are cancelled before they complete! My server shows no calls to the api endpoints, and the client logs an error of {} (which shows up as [Error] by default).

I am using the TransferStateModule, as well as the TransferHttpCacheModule, but nothing has worked so far. It seems like the express-engine render just will not wait for async http calls. It affects the http calls in ngOnInit of components, the constructors of services, and any in resolvers, so I don't know how else to get the data.

For things like config data, I am injecting the necessary fields as a token, and checking that token on server-side renders, but I cannot do that for all data in the app.

Below are the relevant sections of my server and app modules

app.ts (node express server)

 app.get('*.*', express.static(APP_CONFIG.client_root, {maxAge: 0}));

    // Render Angular Universal
    if (APP_CONFIG.universal) {
        // Our index.html we'll use as our template
        const templateFile = HelpersService.tryLoad(join(APP_CONFIG.client_root, './index.html'));
            const config = configService.getConfig();
            const template = templateFile.toString();
            const win = domino.createWindow(template);
            const fakeanimation = {
                value: () => {
                    return {
                        enumerable: true,
                        configurable: true
                    };
                },
            };
            global['window'] = win;
            global['document'] = {...win.document, 
                createElement: () => {},
                body: {
                    ...win.document.body,
                    style: { 
                        ...win.document.body.style,
                        transform: fakeanimation,
                        opacity: fakeanimation,    
                        bottom: fakeanimation,    
                        left: fakeanimation,    
                    }
                }
            };
            global['navigator'] = win.navigator;
            global['location'] = win.location;

            const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('../ssr/ssr.js');

            app.engine('html', ngExpressEngine({
                bootstrap: AppServerModuleNgFactory,
                providers: [
                    provideModuleMap(LAZY_MODULE_MAP),
                ]
            }));

            app.set('view engine', 'html');
            app.set('views', APP_CONFIG.client_root);


            app.get('*', (req, res, next) => {
                const protocol = res.locals.protocol || req.protocol;
                const showPort = ((APP_CONFIG.port === 80 && protocol === 'http') || (protocol === 'https')) ? false : true;
                const serverUrl = `${protocol}://${req.hostname}${showPort ? ':'+APP_CONFIG.port : ''}`;
                res.render('index', {
                    req,
                    document: template,
                    url: req.url,
                    providers: [
                        {
                            provide: 'serverURL',
                            useValue: serverUrl
                        },
                        {
                            provide: 'SERVER_CONFIG',
                            useValue: config || {}
                        },
                        {
                            provide: 'SERVER_AUTH',
                            useValue: {signedIn: !!res.locals.auth}
                        },
                    ]
                });
            });
        }

app.module.ts

@NgModule({
    bootstrap: [
        AppComponent
    ],
    imports: [
        BrowserModule.withServerTransition({appId: 'myapp'}),
        BrowserAnimationsModule,
        SharedModule,
        TransferHttpCacheModule,
        BrowserTransferStateModule,
        RouterModule.forRoot(
    ...

app.server.module.ts

@NgModule({
  imports: [
    // The AppServerModule should import your AppModule followed
    // by the ServerModule from @angular/platform-server.
    AppModule,
    ServerModule,
    ServerTransferStateModule,
    ModuleMapLoaderModule // <-- *Important* to have lazy-loaded routes work
  ],
  // Since the bootstrapped component is not inherited from your
  // imported AppModule, it needs to be repeated here.
  bootstrap: [AppComponent],
  providers: [
    {
        provide: HTTP_INTERCEPTORS,
        useClass: ServerSideRequestInterceptor,
        multi: true,
    },
  ]
})
export class AppServerModule {}

The ServerSideRequestInterceptor reads the serverURL injection token, and prepends it to all requests starting with /.

I feel like I am very close to cracking this, but I cannot for the life of me tell how to make it wait for the http calls in my app.

EDIT: Here's the Repo I made with working universal and angular 7: https://github.com/swimmadude66/AngularPWASeed/tree/universal7

like image 943
Adam Yost Avatar asked Jan 17 '19 23:01

Adam Yost


1 Answers

Thanks to @CaerusKaru for solving this one in the @nguniversal issues page! https://github.com/angular/universal/issues/1046#issuecomment-455408250

Essentially, the problem was the interceptor I use to append the full server path to api calls. It was using Object.assign(request, {url: fullServerUrl}); to set the new url for all http requests beginning with /. The appropriate method is apparently request.clone({url: fullServerUrl}); and changing that one snippet caused the data to flow like a rushing stream.

Hope this helps someone else, and I will be turning the repro-repo (hard to say and type) in to boilerplate of Angular7 and Universal as a working starter point.

like image 67
Adam Yost Avatar answered Sep 22 '22 10:09

Adam Yost