Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Universal does not wait for api/http request before render

I have a brand new angular universal project that seems to pretender all the HTML (which is good). However, I am trying to make an API call to my .NET server which is a standard API build with the weatherforecast API.

The API calls and it works great, but it only happens after my web app has switched from pre render to csr. see example 1.

Example 1

enter image description here

if I disable javascript on the page this is what I get

enter image description here

and this is the HTML code

  <div style="padding: 5rem">
    <h1>TEST</h1>
    <h2>{{ this.SampleMessage }}</h2>
    <div *ngFor="let product of weather$ | async">
      <p>{{ this.product.summary }}</p>
    </div>
  </div>

app.component.ts

import { AppService } from './app.service';
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'ModernaMediaAngular';
  text:string = "test"

  weather$: Observable<any>;
  SampleMessage="Example of Angular Async Pipe";    
  
  constructor(private as: AppService, ) {}

  async ngOnInit() {
    await this.getWeatherAsyncPipe();
    //non async
    this.as.getWeather().subscribe( res =>
      {
        this.text = res[0].date;
        console.log("got resolution");
        console.log(res);
        console.log(this.text);
      }
    );


  }
  public async getWeatherAsyncPipe() {    
        this.SampleMessage="Example of Angular Async Pipe";    
        this.weather$ = await this.as.getWeatherAsync();    
        console.log(this.weather$);
      }    
}

app.service.ts

import { environment } from './../environments/environment';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AppService {
  public waeatherUrl = environment.url + '/api/weatherForecast/get'
  constructor(private http: HttpClient) { }

  getWeather() : Observable<object> {
    const headers = new HttpHeaders(
      {'Content-Type': 'application/json',
      'Access-Control-Allow-Origin' : 'http://localhost:4200'
    }
      );
    var x = this.http.get(this.waeatherUrl, {headers: headers}).pipe();
    console.log(x);
    return x;
  }

  public getWeatherAsync():Observable<any> {
    return this.http.get<any[]>(this.waeatherUrl);    
  }
}

app.module.ts

import { BrowserStateInterceptor } from './interceptors/browserstate.interceptor';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

import { TransferHttpCacheModule } from '@nguniversal/common'

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
    TransferHttpCacheModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

app.server.module

import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ServerTransferStateModule
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));
  console.log("DOMCONTENTLOADED");
});

I also get what I think is a cors error whenever the prerendering tries to fetch from the API.

chunk {main} main.js, main.js.map (main) 66.3 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 141 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.15 kB [entry] [rendered]
chunk {styles} styles.css, styles.css.map (styles) 118 bytes [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 2.8 MB [initial] [rendered]
Date: 2021-03-15T14:26:38.903Z - Hash: a20337e49cf8941fcbf6 - Time: 12492ms
Hash: af451acd21594faf190e
Time: 19059ms
Built at: 2021-03-15 15:26:41
      Asset      Size  Chunks                          Chunk Names
    main.js  6.44 MiB    main  [emitted]        [big]  main
main.js.map  6.99 MiB    main  [emitted] [dev]         main
Entrypoint main [big] = main.js main.js.map
chunk {main} main.js, main.js.map (main) 6.08 MiB [entry] [rendered]

Compiled successfully.
** Angular Universal Live Development Server is listening on http://localhost:4200, open your browser on http://localhost:4200 **
Observable {
  _isScalar: false,
  source: Observable {
    _isScalar: false,
    source: Observable {
      _isScalar: false,
      source: [Observable],
      operator: [MergeMapOperator]
    },
    operator: FilterOperator { predicate: [Function], thisArg: undefined }
  },
  operator: MapOperator { project: [Function], thisArg: undefined }
}

Observable {
  _isScalar: false,
  source: Observable {
    _isScalar: false,
    source: Observable {
      _isScalar: false,
      source: [Observable],
      operator: [MergeMapOperator]
    },
    operator: FilterOperator { predicate: [Function], thisArg: undefined }
  },
  operator: MapOperator { project: [Function], thisArg: undefined }
}

ERROR HttpErrorResponse {
  headers: HttpHeaders {
    normalizedNames: Map {},
    lazyUpdate: null,
    headers: Map {}
  },
  status: 0,
  statusText: 'Unknown Error',
  url: 'https://localhost:5001/api/weatherForecast/get',
  ok: false,
  name: 'HttpErrorResponse',
  message: 'Http failure response for https://localhost:5001/api/weatherForecast/get: 0 Unknown Error',
  error: ProgressEvent {
    type: 'error',
    target: XMLHttpRequest {
      onloadstart: null,
      onprogress: null,
      onabort: null,
      onerror: null,
      onload: null,
      ontimeout: null,
      onloadend: null,
      _listeners: [Object],
      onreadystatechange: null,
      _anonymous: undefined,
      readyState: 4,
      response: null,
      responseText: '',
      responseType: 'text',
      responseURL: '',
      status: 0,
      statusText: '',
      timeout: 0,
      upload: [XMLHttpRequestUpload],
      _method: 'GET',
      _url: [Url],
      _sync: false,
      _headers: [Object],
      _loweredHeaders: [Object],
      _mimeOverride: null,
      _request: null,
      _response: null,
      _responseParts: null,
      _responseHeaders: null,
      _aborting: null,
      _error: null,
      _loadedBytes: 0,
      _totalBytes: 0,
      _lengthComputable: false
    },
    currentTarget: XMLHttpRequest {
      onloadstart: null,
      onprogress: null,
      onabort: null,
      onerror: null,
      onload: null,
      ontimeout: null,
      onloadend: null,
      _listeners: [Object],
      onreadystatechange: null,
      _anonymous: undefined,
      readyState: 4,
      response: null,
      responseText: '',
      responseType: 'text',
      responseURL: '',
      status: 0,
      statusText: '',
      timeout: 0,
      upload: [XMLHttpRequestUpload],
      _method: 'GET',
      _url: [Url],
      _sync: false,
      _headers: [Object],
      _loweredHeaders: [Object],
      _mimeOverride: null,
      _request: null,
      _response: null,
      _responseParts: null,
      _responseHeaders: null,
      _aborting: null,
      _error: null,
      _loadedBytes: 0,
      _totalBytes: 0,
      _lengthComputable: false
    },
    lengthComputable: false,
    loaded: 0,
    total: 0
  }
}

ERROR HttpErrorResponse {
  headers: HttpHeaders {
    normalizedNames: Map {},
    lazyUpdate: null,
    headers: Map {}
  },
  status: 0,
  statusText: 'Unknown Error',
  url: 'https://localhost:5001/api/weatherForecast/get',
  ok: false,
  name: 'HttpErrorResponse',
  message: 'Http failure response for https://localhost:5001/api/weatherForecast/get: 0 Unknown Error',
  error: ProgressEvent {
    type: 'error',
    target: XMLHttpRequest {
      onloadstart: null,
      onprogress: null,
      onabort: null,
      onerror: null,
      onload: null,
      ontimeout: null,
      onloadend: null,
      _listeners: [Object],
      onreadystatechange: null,
      _anonymous: undefined,
      readyState: 4,
      response: null,
      responseText: '',
      responseType: 'text',
      responseURL: '',
      status: 0,
      statusText: '',
      timeout: 0,
      upload: [XMLHttpRequestUpload],
      _method: 'GET',
      _url: [Url],
      _sync: false,
      _headers: [Object],
      _loweredHeaders: [Object],
      _mimeOverride: null,
      _request: null,
      _response: null,
      _responseParts: null,
      _responseHeaders: null,
      _aborting: null,
      _error: null,
      _loadedBytes: 0,
      _totalBytes: 0,
      _lengthComputable: false
    },
    currentTarget: XMLHttpRequest {
      onloadstart: null,
      onprogress: null,
      onabort: null,
      onerror: null,
      onload: null,
      ontimeout: null,
      onloadend: null,
      _listeners: [Object],
      onreadystatechange: null,
      _anonymous: undefined,
      readyState: 4,
      response: null,
      responseText: '',
      responseType: 'text',
      responseURL: '',
      status: 0,
      statusText: '',
      timeout: 0,
      upload: [XMLHttpRequestUpload],
      _method: 'GET',
      _url: [Url],
      _sync: false,
      _headers: [Object],
      _loweredHeaders: [Object],
      _mimeOverride: null,
      _request: null,
      _response: null,
      _responseParts: null,
      _responseHeaders: null,
      _aborting: null,
      _error: null,
      _loadedBytes: 0,
      _totalBytes: 0,
      _lengthComputable: false
    },
    lengthComputable: false,
    loaded: 0,
    total: 0
  }
}

but no errors in console:

enter image description here

In startup.cs in my .NET 5 project I have configured cors:

public void ConfigureServices(IServiceCollection services) {
    services.AddControllers();
    services.AddSwaggerGen(c => {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "ModernaMediaDotNet", Version = "v1" });
        });
    services.AddCors();
}

and in public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.UseRouting();
app.UseCors(x => x
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader()
            .SetIsOriginAllowed(origin => true) // allow any origin
            );
like image 359
Stanley Avatar asked Mar 15 '21 14:03

Stanley


2 Answers

I once had the same problem on our test environment, and it was due to the fact that we were using a self signed certificate, which node did not like.

We ended up adding the line below in server.ts so that node disables TLS validation.

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

Note This is unsecure (documentation) and you should not use this in prod (where you should have valid certificates).

Another option would be to only make http calls when you are server side, which you could implement using a HttpInterceptor

like image 188
David Avatar answered Nov 15 '22 21:11

David


Your app probably needs to wrap the API call in a zone macro task.

  1. npm install @bespunky/angular-zen
  2. Import RouterXModule (.forRoot() or .forChild() according to your module).
  3. Have your component/service extend the RouteAware class.
  4. Wrap your observable in a call to this.resolveInMacroTask().

You could do it manually, but RouteAware takes care of everything and gives you additional benefits.

More

Docs (See the Angular Universal section)

Live example using RouteAware

like image 24
Shy Agam Avatar answered Nov 15 '22 22:11

Shy Agam