Search code examples
angulartypescriptrxjsangular-httpclient

Angular HttpClient - request not executed


I cant get the HttpClient request to be fired. Probably it is some importing or providing issue, but can't find where it is. The API does work, but never gets called.

As for the environment/versions:

  • Angular: 15.1.2 Angular
  • CLI: 15.1.3
  • Node: 16.14.2
  • Package Manager: npm 8.5.0
  • OS: win32 x64

The layout is: I have a main landing page, hosting several widgets which is a quick look into the targeted content. Each widgets has its own component. Services, providing the data for the widgets and provided in root and they expose an Observable to which each component subscribes to.

app.module.ts

const pipes = [
  SignUpFormTextPipe,
  StageFormatTextPipe,
  StageTypeTextPipe,
  TeamSizeTextPipe,
  TournamentStatusTextPipe,
];

const providers = [HttpClientModule];

@NgModule({
  declarations: [...components, ...pipes],
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  providers: [...pipes, ...providers],
  bootstrap: [AppComponent],
})

The service

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Tournament } from '../models/tournament/tournament';
import { BehaviorSubject, Observable, take, tap } from 'rxjs';
import { TournamentsResponse } from '../models/api-responses/tournament/tournaments-response';

@Injectable({
  providedIn: 'root',
})
export class TournamentService {
  private baseUrl: 'http://localhost:5077/api/tournamens';

  initialTournaments: Tournament[] = [];
  private tournaments = new BehaviorSubject<Tournament[]>(
    this.initialTournaments
  );

  public tournaments$ = this.tournaments.asObservable();

  constructor(private httpClient: HttpClient) {}

  public getTournaments(): Observable<TournamentsResponse> {
    console.log('test 123'); // this is fired

    return this.httpClient.get<TournamentsResponse>(this.baseUrl).pipe(
      tap((resp) => {
        console.log(resp); // this ain't fired

        this.tournaments.next(resp.tournaments);

        return resp;
      }),
      take(1)
    );
  }
}

One of the components subscribing to it:

import { Component, OnInit } from '@angular/core';
import { Observable, Subject, takeUntil } from 'rxjs';
import { Tournament } from 'src/app/models/tournament/tournament';
import { NotificationService } from 'src/app/services/common/notification.service';
import { TournamentService } from 'src/app/services/tournament.service';

@Component({
  selector: 'app-upcoming-tourneys',
  templateUrl: './upcoming-tourneys.component.html',
  styleUrls: ['./upcoming-tourneys.component.css'],
})
export class UpcomingTourneysComponent implements OnInit {
  private destroy$: Subject<void> = new Subject<void>();

  upcomingTournaments$: Observable<Tournament[]> =
    this.tournamentService.tournaments$;

  constructor(
    private tournamentService: TournamentService,
    private notificationService: NotificationService
  ) {}

  ngOnInit() {
    console.log(this.tournamentService); // this gets fired and logged properly

    this.tournamentService
      .getTournaments()
      .pipe(takeUntil(this.destroy$))
      .subscribe({
        next: (resp) => {
          console.log(resp); // this ain't get fired
        },
        error: (resp) => {
          this.notificationService.showError(resp);
        },
      });
  }
}

and the related components HTML:

test if component is rendered
<div class="container-fluid" *ngIf="upcomingTournaments$ | async as upcomingTournaments">
  <div class="card">
    <div class="card-header" routerLink="/tournaments">
      Upcoming tournaments
    </div>
    <div class="card-body">
       <div *ngFor="let tourney of upcomingTournaments">
         ... content...
       </div>
    </div>
  </div>
</div>

This most probably is a very vague error since I have the same pattern in another working app. I got some initial feedback that it is most probably an error on the imports, but intelisense/linter doesn't give the hint.

enter image description here enter image description here

*** UPDATE 1 *** I have altered the service as suggested below by @flo to use lastValueFrom(...). Unfortunately HTTP call still not fired. :(

@Injectable({ providedIn: 'root' })
export class TournamentService {
  private baseUrl: 'http://localhost:5077/api/tournamens';

  initialTournaments: Tournament[] = [];
  private tournamentsSubject = new BehaviorSubject<Tournament[]>(
    this.initialTournaments
  );

  public tournaments$ = this.tournamentsSubject.asObservable();

  constructor(private httpClient: HttpClient) {}

  public getTournaments() {
    const result = lastValueFrom(
      this.httpClient.get<TournamentsResponse>(this.baseUrl)
    ).then((resp) => {
      this.tournamentsSubject.next(resp.tournaments);
    });
  }
}

In the component typescript file:

export class UpcomingTourneysComponent implements OnInit {
  upcomingTournaments$: Observable<Tournament[]> =
    this.tournamentService.tournaments$;

  constructor(private tournamentService: TournamentService) {}

  ngOnInit() {
    this.tournamentService.getTournaments();
  }
}

In the component template:

test if component is rendered
{{ upcomingTournaments$ | async }}
<div
  class="container-fluid"
  *ngIf="upcomingTournaments$ | async as upcomingTournaments"
>
....

*** UPDATE 2 *** Since it does not seems to work, I tried to remove all observables from the story and I have updated the service call as below.. Yet still no get call fired :*

The service:

  public getTournaments(): Observable<TournamentsResponse> {
    return this.httpClient.get<TournamentsResponse>(this.baseUrl);
  }

Component:

  ngOnInit() {
    const response = this.tournamentService.getTournaments().subscribe({
      next: (resp) => {
        console.log({ what: 'next call within service subscribe', resp }); // logs an ERROR TypeError: Cannot read properties of undefined (reading 'toLowerCase')
      },
    });
    console.log({ what: 'upcomingTourneysInit', response }); // here Response is SafeSubscriber
  }

Solution

  • You're not properly assigning a value to your baseUrl in the service. The service now has:

    private baseUrl: 'http://localhost:5077/api/tournamens';
    

    But that does not assign the value of your API url, rather it's marking it as its type.

    The correct code should be:

    private baseUrl: string = 'http://localhost:5077/api/tournamens';
    

    Also, consider returning an Observable from your service and subscribing in your template using the async pipe to avoid having to handle subscriptions manually.