Async / await on ngOninit()

Viewed 2812

I'm trying to use the async/await (for the first time) and I can't wrap my head around it.

What am I doing wrong here:

tree.component.ts

export class TreeComponent implements OnInit {

  private routeSub: any;
  acronym: string;
  flare: any[];

  constructor(
    private location: Location,
        private route: ActivatedRoute,
        private http: HttpClient,
        private router: Router,
        private fileTreeService: FileTreeService
  ) {


  }
  ngOnInit(): void {
      this.routeSub = this.route.params.subscribe(
        params => {
            this.acronym = params.name
            this.fileTreeService.getFileTree(params.name).subscribe(item => {
                this.flare = item;

            });
        });

filetree.service.ts

export class FileTreeService {

    res: any;

    constructor(private http: HttpClient) { }



    async getFileTree(acronym) {
        const headers = new HttpHeaders()
        this.res = await this.http.get<any>(`${IP}/filetree/tree/`, { headers });
        return this.res;
    }
}

I'm getting the error "Property 'subscribe' does not exist on type 'Promise'" in filetree.component.ts. I've reached the end of the road here, so I'm reaching out to you guys. Thanks in advance.

Update:

Thanks for the help, it kinda worked but it didn't do what I was trying to achive. Isn't supposed to wait for the result before continue executing the code?

  ngOnInit(): void {
      this.routeSub = this.route.params.subscribe(async (params) => {
            this.acronym = params.name
            this.flare = await this.fileTreeService.getFileTree(params.name).toPromise();
            console.log("flare = ", this.flare);
        });

    let test = this.flare;
    console.log("test = ", test);
} 

root is declared before flare has been given a value. This what my browser console spits out.

test =  undefined
flare =  {name: "data", children: Array(81)}
2 Answers

async/await work on promise so in case you use httpClinet methods you need to convert the return value from Observable to promise by toPromise method

filetree.service.ts

export class FileTreeService {
    res: any;

    constructor(private http: HttpClient) { }

    getFileTree(acronym) {
        const headers = new HttpHeaders()
        // return an observable 
        return  this.http.get<any>(`${IP}/filetree/tree/`, { headers })    
   }
}

component

  ngOnInit(): void {
      //  mark the upper function with async
      this.routeSub = this.route.params.subscribe(async (params) => {  
        this.acronym = params.name;
        //  now we can use await but we need to convert the observable to promise 
        this.flare = await this.fileTreeService.getFileTree(params.name).toPromise();
        });
}

you can read in depth tutorial here

Updated

we can mark ngOnInit with async and use await for all observances after we convert theme to promise in that case we will no longer to use subscribe.

  //  mark the upper function with async
  async ngOnInit(): Promise<void> {
    //  now we can use await but we need to convert the observable to promise 
    this.acronym = (await this.route.params.toPromise()).name;
    this.flare = await this.fileTreeService.getFileTree(this.acronym).toPromise();
    let test = this.flare;  // => {name: "data", children: Array(81)}
    console.log("test = ", test); // => {name: "data", children: Array(81)}

  }

If you are new to async/reactive programming, I wouldn't recommend you to mix promises and observables. Since you're using Angular HttpClient Module, which returns an observble for an HTTP request, we could stick only with it.

So you could return the HTTP request directly without converting it to a promise. Sidenote: observables also provide much more control on the data flow and data transform operators compared to promises.

Service

export class FileTreeService {
  res: any;

  constructor(private http: HttpClient) { }

  getFileTree(acronym): Observable<any> {      // <-- `http.get()` returns an observable
    const headers = new HttpHeaders();
    return this.http.get<any>(`${IP}/filetree/tree/`, { headers });
  }
}

Additionally, now you have a child observable from this.fileTreeService.getFileTree() function that depends on the notification from another observable this.route.params.

In such cases instead of having multiple inner subscriptions like you're doing, you could use RxJS higher order mapping operator like switchMap to map from one observable to another. Now 2 different data streams from your code is reduced to one single stream. Lesser the streams - lesser the potential memory leaks.

Component

import { Subject } from 'rxjs';
import { switchMap, takeUntil } from 'rxjs/operators';

export class TreeComponent implements OnInit, OnDestroy {
  private routeSub: any;
  acronym: string;
  flare: any[];
  complete$ = new Subject<any>();     // <-- use to close open subscriptions

  constructor(
    private location: Location,
        private route: ActivatedRoute,
        private http: HttpClient,
        private router: Router,
        private fileTreeService: FileTreeService
  ) { }
 
  ngOnInit(): void {
    this.routeSub = this.route.params.pipe(
      switchMap(params => {           // <-- use `switchMap` to map from one observable to another
        this.acronym = params.name;
        return this.fileTreeService.getFileTree(params.name);
      }),
      takeUntil(this.complete$)       // <-- close the subscription when `this.complete$` emits
    ).subscribe(
      item => {
        this.flare = item;
        console.log(this.flare);      // <-- correct log: will print the value of `this.flare`
      },
      error => {
        // always good practice to handle errors from HTTP requests
      }
    );

    console.log(this.flare);          // <-- wrong log: will print `undefined`
  }

  ngOnDestroy() {
    this.complete$.next();            // <-- close open subscriptions
  }
}

Also remember, since the this.flare variable is assigned asynchronously, all statements that depend on it must be inside the subscription. It is illustrated in the code above using the different console.log() statements. The one inside subscription will print the correct value whereas the one outside the subscription will wrongfully print either undefined or older value held by the this.flare.

I've also used RxJS takeUntil operator to close open subscriptions in the ngOnDestory() hook. You could find more details about it and other easier method of handling unsubscriptions here.

Related