简体   繁体   中英

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

在此处输入图像描述

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

在此处输入图像描述

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:

在此处输入图像描述

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
            );

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

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

I was facing the same problem, use a resolver in your route module, that will fix it, happy coding!

  constructor(
    private activatedRoute: ActivatedRoute,
  ) {
    this.activatedRoute.data.subscribe(({ animes }) => {
      this.animeList = animes;
    });
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM