1

Hello I need to fetch data coming from API and then based on response return Observable in ngOnInit.

@Edit Basically this.dayOverViewByGroup should be fully downloaded from api when is returned in fetchChildrensData() => return Observable.of(this.dayOverViewByGroup).

@Edit2 Added code for getDayOverViewByGroupId

ngOnInit


ngOnInit() {
    const filter$: Observable<string> = this.filterControl.valueChanges.pipe(
      startWith(''),
      debounceTime(100), 
      distinctUntilChanged(),
      share()
    );
    this.dayOverViewByGroup$ = this.fetchChildrensData().pipe(
      map(days => days.map(day => (
        {
          ...day,
          filteredChildren$: filter$.pipe(this.filterFrom(day.children))
        }
      )))
    );
  }

fetchChildrensData function

 fetchChildrensData(): Observable<any[]> {
    return Observable.of(this.dayOverViewByGroup)
 }

Functions that handle API call

getChildrens(date, groupId) {
    return this.attendanceService.getDayOverViewByGroupId(date, groupId).then(
      (res1: any) => {
        let dayOverViewByGroup = res1.mobile_employee_dayoverview;
        this.dayOverViewByGroup = dayOverViewByGroup;
        this.appFunctionCtrl.dismissLoader();  
      },
      (error) => console.log('Error occured in this.attendanceService.getDayOverViewByGroupId()', error)
    );

  }
getDayOverViewByGroupId(currentDate = null, groupId) {
    let that = this;
    return new Promise((resolve, reject) => {
      that.wsseGenerator.getWSSEHeader().then(header => {
        header.append('Access-Control-Allow-Origin', '*');
        header.append('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT');
        let options = new RequestOptions({ headers: header });
        let paramsData = {
          groupId: groupId,
          currentDate: currentDate
        };
        let params = that.serializeObject(paramsData);
        that.http.post(`${that.baseUrl}/dayoverview?${params}`, null, options).timeout(this.timeTimeout).subscribe((success: any) => {
          resolve(success);
        }, (err) => {

          this.appFunctionCtrl.dismissLoader();
          if (err.name == 'TimeoutError') {
            this.alert.present();
          }
          else {
            reject(err);
          }
        });
      });
    });
  }

Verthon
  • 2,937
  • 3
  • 20
  • 32
  • 1
    It would be best to work with Observables from the beginning (i.e. in your Service) and change `this.attendanceService.getDayOverViewByGroupId` to return an Observable instead of a Promise. Could you post the code of `this.attendanceService.getDayOverViewByGroupId`. – frido Jan 08 '20 at 11:30
  • Thank you, I've updated the post with `this.attendanceService.getDayOverViewByGroupId`. – Verthon Jan 08 '20 at 11:41

1 Answers1

2

You should return an Observable from your Service that doesn't throw any errors so you can directly use the AsyncPipe in you Component to subscribe (Also see: Why handle errors with catchError and not in the subscribe error callback in Angular).

Convert Api calls that you can't change and return Promises to Observables with from and use RxJS operators from then on (I'm presuming this.wsseGenerator.getWSSEHeader() is such a call).

Use switchMap to map to a different Observable, pluck or map to extract / transform returned data, catchError to do things on errors and return an Observable that doesn't throw any errors, finalize to do things when your Observable errors or completes.

In your Service:

import { Observable } from "rxjs";
import { switchMap, map, timeout, catchError, finalize } from "rxjs/operators";

getDayOverViewByGroupId(currentDate = null, groupId): Observable<any> {
  return Observable.from(this.wsseGenerator.getWSSEHeader()).pipe( // use from to convert Promise to Observable
    switchMap(header => {
      header.append('Access-Control-Allow-Origin', '*');
      header.append('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT');
      let options = new RequestOptions({ headers: header });
      let paramsData = {
        groupId: groupId,
        currentDate: currentDate
      };
      let params = that.serializeObject(paramsData);
      // map to your http request
      return this.http.post(`${that.baseUrl}/dayoverview?${params}`, null, options);
    }),
    map(this.getRelevantData), // get relevant data from http response
    timeout(this.timeTimeout),
    catchError(error => { // handle errors and return an Observable that doesn't error
      console.log('Error occured in this.attendanceService.getDayOverViewByGroupId()', error);
      if (error.name == 'TimeoutError') {
          this.alert.present();
      }
      // you have to think about what default value you want to return on errors, null can be ok
      return Observable.of(null); 
    }),
    finalize(() => this.appFunctionCtrl.dismissLoader()) // do stuff on error and complete
  );
}

private getRelevantData(response): any { // <-- don't return 'any' in your real code, create interfaces to type your data if you can
  let dayOverViewByGroup = response.mobile_employee_dayoverview;
  dayOverViewByGroup.forEach(element => {
    // do your thing
  }
  console.log('dayOverViewByGroup', dayOverViewByGroup)
  return dayOverViewByGroup;
}

In your Component:

ngOnInit() {
  this.dayOverViewByGroup$ = this.attendanceService.getDayOverViewByGroupId( .. ).pipe( 
    // If the Observable from your service can emit 'null' and you do
    // map(days => days.map(..)) you will get an error 
    // so you might have to handle this case here

    // 1. Option: filter out 'null' and 'undefined' values before your 'map' and don't emit anything
    filter(Boolean)

    // 2. Option: handle 'null' and 'undefined' in your map function
    map(days => {     
      if (days) {
        days.map( .. )
      } else {
        // do something else or don't do anything at all, i.e. remove the else case
      }
    })
  )
}
frido
  • 13,065
  • 5
  • 42
  • 56
  • Thank you. Now I have some problems with "Cannot find name 'map'. Did you mean 'Map'?" in service and pipe is not a function. I think its a import problems. – Verthon Jan 08 '20 at 14:42
  • 1
    Did you `import { map } from 'rxjs/operators';`? If you get pipe is not a function you're not using an Observable where there should be one. – frido Jan 08 '20 at 14:50
  • Thank you I was importing from wrong package. Now I have, `Uncaught (in promise): TypeError: __WEBPACK_IMPORTED_MODULE_9_rxjs__.Observable.from is not a function` You mentioned about `this.wsseGenerator.getWSSEHeader()` how to check if this is not a problem in that case? – Verthon Jan 09 '20 at 11:17
  • Try `import { from } from 'rxjs/observable/from';` and use `from` without `Observable` like `from(this.wsseGenerator.getWSSEHeader())`. `this.wsseGenerator.getWSSEHeader()` returns a Promise right? – frido Jan 09 '20 at 11:29
  • Thanks for the answer, yes `this.wsseGenerator.getWSSEHeader()` returns public getWSSEHeader(): Promise – Verthon Jan 09 '20 at 11:55
  • Removed Observable from `from`. Error is gone, I have array from api in `getRelevantData` - however there is notthing rendered on a page. My ngOnInit – Verthon Jan 09 '20 at 12:49
  • https://gist.github.com/Verthon/4673db59f4c2b23409bf94302e6d3638 – Verthon Jan 09 '20 at 12:51
  • You should be able to fix problems like this yourself. Add `console.log` to every relevant code branch to see how the code flow is where your data gets stuck. Hint: when does `(days && days === undefined)` evaluate to `true`? – frido Jan 09 '20 at 13:33
  • Thank you, I greatly appreciate your help. – Verthon Jan 09 '20 at 13:44