paulsm4
paulsm4

Reputation: 121809

Debugging Angular HttpClient observables: where can I set a breakpoint to see what's returned from the server?

I'm new to Angular and "reactive programming".

I have an Angular 8 component (test-api.component.*) that uses a service (contacts.service.ts) to talk to a REST back-end. It looks like this:

test-api.component.ts:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Contact } from '../models/contact';
import { ContactsService } from '../services/contacts.service';

@Component({
  selector: 'app-test-api',
  templateUrl: './test-api.component.html',
  styleUrls: ['./test-api.component.css']
})
export class TestAPIComponent implements OnInit {

  // "$" convention denotes this as an "Observable"
  contacts$: Observable<Contact[]>;

  constructor(private contactsService: ContactsService) { }

  ngOnInit() {
    this.loadContacts();
  }

  loadContacts() {
    this.contacts$ = this.contactsService.getContacts();
  }
}

test-api-component.html:

<h1>Contacts</h1>
<p *ngIf="!(contacts$ | async)"><em>Loading...</em></p>
<table class="table table-sm table-hover" *ngIf="(contacts$ | async)?.length > 0">
  <thead>
    <tr>
      <th>ContactId</th>
      <th>Name</th>
      <th>EMail</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let contact of (contacts$ | async)">
      <td>{{ contact.ContactId }}</td>
      <td>{{ contact.Name }}</td>
      <td>{{ contact.EMail }}</td>
    </tr>
  </tbody>
</table>

I'm developing with Visual Studio code.

How/where can I set a breakpoint in my code so I can see my contacts[] array in the debugger after the async operation has completed? I just want to be able to look at individual "contact" elements in the debugger.

Update

I know how to set breakpoints and use JS debuggers (both VSCode/Chrome extensions and Chrome Developer Tools), but I didn't know how best to modify the code so that I could examine the data that was otherwise going straight into the HTML template.

Andrei Gătej gave the most detailed answer to my implicit questions (what exactly is happening "under the covers" when I declare an "Observable" or pipe "async"?)

Both Mustafa Kunwa and Himanshu Singh gave me effectively the SAME answer - the one that I went with.

Solution

Anyway, my final code looks like this:

test-api.component.html

 <tr *ngFor="let contact of (contactsList)">
      <td>{{ contact.contactId }}</td>
      <td>{{ contact.name }}</td>
      <td>{{ contact.eMail }}</td>
 ...

test-api.component.ts

export class TestAPIComponent implements OnInit, OnDestroy {

  contactsList: Contact[];
  private contactsSubscription$: Subscription;
  ...
  ngOnInit() {
    this.contactsSubscription$ =
      this.contactsService.getContacts().subscribe(
        data => {
          this.contactsList = data;
          console.log('loadContacts', data);
        },
        err => {
          console.error('loadContacts', err);
        });
  ...
  ngOnDestroy() {
    this.contactsSubscription$.unsubscribe();
  }

Upvotes: 3

Views: 3213

Answers (3)

Andrei Gătej
Andrei Gătej

Reputation: 11979

Note that in your template you are applying the async pipe twice to your observable, which will create 2 subscribers.

Although the async pipe takes care of unsubscribing from your observable, having multiple subscriptions might be redundant.

To solve this, you can wrap your part of the template which will make use of that observable(contacts$) with an ng-container, so that only one subscription will be created

test.component.html

<ng-container *ngIf="(contacts$ | async) as contacts; else loading">
    <h1>Contacts</h1>

    <table class="table table-sm table-hover" *ngIf="contacts.length > 0">
        <thead>
            <tr>
                <th>ContactId</th>
                <th>Name</th>
                <th>EMail</th>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let contact of (contacts$ | async)">
            <td>{{ contact.ContactId }}</td>
            <td>{{ contact.Name }}</td>
            <td>{{ contact.EMail }}</td>
            </tr>
        </tbody>
    </table>

    <p *ngIf="contacts.length === 0">No contacts!</p>
</ng-container>

<ng-template #loading>
    <p>
        <em>Loading...</em>
    </p>
</ng-template>

Regarding your question, I find Nicholas' first comment helpful.

Besides the debugger, here is my preferred approach:

loadContacts() {
 this.contacts$ = this.contactsService.getContacts().pipe(tap(console.log));
}

Upvotes: 1

Mustafa Kunwa
Mustafa Kunwa

Reputation: 867

try this

 this.contactsService.getContacts().subscribe(
     res=>{
      console.log(res);
     },
     err=>{
       console.log(err);
     });

Upvotes: 2

Himanshu Singh
Himanshu Singh

Reputation: 2165

Instead of calling loadContacts() from ngOnInit(). You can directly fetch data from API in ngOnInit() only. Then on the mentioned line in code you can add debug point to see the data received from the API

ngOnInit() {
   this.contactsService.getContacts().subscribe((data)=>{
       console.log(data);       // <---- here you can add debug point
   })
}

Upvotes: 2

Related Questions