Angular 8 using ngIf with async pipe to show and hide the HTML element

Viewed 7023

I am trying to show and hide HTML elements according to the service result. I am using *ngIf="messageService.getData() | async" but it isn't working to show or hide the element. I am using async, otherwise "Failure Message" is shown for a short time and then "Successful Message" is shown.

I have 2 tags like these:

<div *ngIf="messageService.getData() | async">Successful Message</div>
<div *ngIf="!messageService.getData() | async">Failure Message</div>

In the service I have a code like this:

export class MessageService {
  constructor(private http: HttpClient) { }

  public getData() {
    return this.http.get("https://jsonplaceholder.typicode.com/todos/1")
    .pipe(
      map((response) => {
        console.log("success");
      }),
      catchError(this.handleError)
    )
  }

  private handleError(error: Response) {
    console.log("handleError")
    let errMsg: string;
    errMsg = "error"
    return Observable.throw(errMsg);
  }
}

Here is the source code: https://stackblitz.com/edit/angular-iqr6az

3 Answers

in your service:

public getData() {
    return this.http.get("https://jsonplaceholder.typicode.com/todos/1")
    .pipe(
      map((response) => {
        return response; // return res
      }),
      catchError(this.handleError)
    )
  }

in your component:

export class MessageComponent implements OnInit {
  isServiceAPIWorking: boolean;
  todos;
  loadingError$ = new Subject<boolean>();
  constructor(private messageService: MessageService) { }

  ngOnInit() {
    this.todos = this.messageService.getData().pipe(
      catchError((error) => {
        // it's important that we log an error here.
        // Otherwise you won't see an error in the console.
        console.error('error loading the list of users', error);
        this.loadingError$.next(true);
        return of();
      })
    );
  }
}

in your html:

<div>Show Message:</div>
<div *ngIf="todos | async">Successfull Message</div>
<div *ngIf="loadingError$ | async">Failure Message</div>

DEMO .

Why bother with async pipes when you can just assign the data in your component?

// message.component.ts

class MessageComponent implements OnInit {
  isServiceAPIWorking: boolean;
  data: any;
  constructor(private messageService: MessageService) { }

  ngOnInit() {
    this.messageService.getData()
      .subscribe(response => {
        this.isServiceAPIWorking = true;
        // Assign the data
        this.data = response;
      },
        error => {
          this.isServiceAPIWorking = false;
        })
  }
}
// message.component.html

<div>Show Message:</div>
<div *ngIf="data">Successfull Message</div>
<div *ngIf="!data">Failure Message</div>

And there is a mistake in your service. If you use map without returning anything, you won't get any data in the end. Use tap if you want to do logging:

// message.service.ts

public getData() {
  return this.http.get("https://jsonplaceholder.typicode.com/todos/1")
  .pipe(
    tap((response) => {
      console.log("success");
    }),
    catchError(this.handleError)
  )
}

Updated Stackblitz

This is a wrong usage of async pipe not in syntactical but in semantic way. Every time a change detection triggered, you are making an HTTP request.

Rather than checking with async pipe, you can store two flags (boolean variables) or one boolean variable for HTTP request and one variable for response.

Below example is using two flags.

export class MessageService {

  isLoaded = false;
  hasError  = false;

  constructor(private http: HttpClient) { }

  public getData() {
    this.isLoaded = false;
    this.http.get("https://jsonplaceholder.typicode.com/todos/1")
    .subscribe(
       (response) => {
           this.hasError = false;
           this.isLoaded = true;
       },
       (error) => {  
           this.hasError = true;
           this.isLoaded = true;
       },
    )
  }
}

And in the template:

<ng-container *ngIf="isLoaded">
    <div *ngIf="!hasError">Successfull Message</div>
    <div *ngIf="hasError">Failure Message</div>
</ng-container>
Related