10

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
            );
9
  • Could it be that the rendering server cannot access the API? Commented Mar 18, 2021 at 23:13
  • @RobinDijkhof That seems like the problem, but it's weird as the're is literally no cors policy and the car part of the application are able to access the api Commented Mar 19, 2021 at 10:26
  • Does that help? stackoverflow.com/a/50051943/1160794 Commented Mar 21, 2021 at 7:46
  • @David Thank you! I have tried completely disabling cors and allowing anything, yet it doesn't seem to accept the request from SSR engine, only the CSR. You can see my policy at the end of the question Commented Mar 21, 2021 at 11:57
  • 1
    I understand, but it would have just given more info as to 'why'. I'll write an answer Commented Mar 22, 2021 at 11:46

3 Answers 3

13
+50

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

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you, that was really helpful. Can you give more details about how set valid certificates?
Thank you. Helped me a lot. I was all day long front this issue. Now, I need to publish angular app using Angular universal.
"nother option would be to only make http calls when you are server side, which you could implement using a HttpInterceptor" How to do this?
so thank you. its worked.
2

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

3 Comments

Thank you for the suggestion, however the approach taken should work fluidly even without another dependency.
Then I suggest you take a look at the implementation of resolveInMacroTask(). You could implement it yourself: github.com/BeSpunky/angular-zen/blob/…
Thank you! I will take a look!
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.