1

How to, inside Service method (getTasksByCategory) which role is to assign result value to one of the Component parameters, assign that value to parameter and then retirve taht value from anywhere inside the Component?

This is my code: https://pastecode.io/s/fgnemrpx

The calculations inside getTasksByCategory() are correct, it's only the order of execution issue that is messing things up.

// Method execution inside Component

export class MyComponent implements OnInit {
    
    moneyData!:any // This is the parameter I want to assign my value to !
    
    constructor( private myService: MyService) {}
    
    async ngOnInit() {
        await this.myService.getTasksByCategory(this.moneyData, 'money') // 'money' is the category name I want to query with)
        await console.log(this.money Data); // NOT WORKING!
    }
    
}

// My Service method which has parameter 'dataConsumer' 

export class MyService {
    
getTasksByCategory(dataConsumer:any, category:string) {
    const daysFromThisWeek = this.getDaysFromThisWeek();
    this.goalsService.goalsCollection()
    .pipe(
      map((goalsArr:any) => {
        goalsArr = goalsArr.filter((item:any) => item.category === category);
        return goalsArr;
      }),
      map((goalsArr:any) => {
        const goalsIDs = goalsArr.map((item:any) => item.id);
        return goalsIDs;
      })
    )
    .subscribe((goalsIDs:any) => {
      this.tasksService.tasksCollection()
        .pipe(
          // get category-matching-tasks
          map((tasksArr:any) => {
            let modArr = [] as any;
            goalsIDs.forEach((goalId:any) => {
              const forModArr = tasksArr.filter((task:any) => task.goal_id === goalId);
              modArr = modArr.concat(forModArr);
          })
          return modArr;
        }),
        map(tasksArr => {
          // get number of category-matching-tasks on each week day
          let finalTasks = [] as any;
          daysFromThisWeek.forEach((day:any) => {
              const forFinalTasks = tasksArr.filter((task:any) => task.taskDate === day);
              finalTasks = finalTasks.concat(forFinalTasks.length);
          })
          return finalTasks;
        })
        )
        // .subscribe(d => {
        //   res = d;
        // })
        .subscribe(async (finalTasks: Promise<any>[]) => {
          const completedTasks = await Promise.all(finalTasks);
          dataConsumer = completedTasks;
          // przypisz wartość do zmiennej wewnątrz subskrypcji
          }
        )
      }) // Subscribe
  }
  
}
Piotr Deja
  • 31
  • 1
  • 6
  • 1
    first, don't use await/async. Use Observables mechanics. The service should not subscribe to any Observable, it should return it instead. The service can do all the stuff you wish with pipe operators (map, switchMap, etc...), and return the final Observable. The component then subscribes to this Observable, and can get the result. – Random Jun 18 '23 at 14:04
  • I think, you should use the forkJoin and subscribe it in the component – Sonu Sindhu Jun 18 '23 at 14:04
  • @Random why dont use await/async - is it forbidden ? – Piotr Deja Jun 18 '23 at 17:38
  • it's a bad practice. When things are asynchronous, there is a reason. Using async/await makes your code stop while waiting for the HTTP call to respond. You do not want that. You want your browser to be free doing what ever he wants (like applying CSS), and when you get the response, come back to work. Whenever I see an async/await, I understand it as "I don't know exactly what I'm doing", which implies "If there is a bug, you may have to refactor this whole code", and that's a pain on asynchronous stuff. – Random Jun 18 '23 at 17:49
  • @Random "Using async/await makes your code stop while waiting for the HTTP call to respond" -> isn't it the reason why async/await was created for ? – Piotr Deja Jun 18 '23 at 17:52
  • "Using async/await makes your code stop while waiting for the HTTP call to respond. You do not want that. You want your browser to be free doing what ever he wants" This is entirely false. It's not blocking the main the main thread, it's the whole point. Whatever code is after an await will, as the name suggest wait for it to be resolved first. But it's not blocking the main thread. – maxime1992 Jun 18 '23 at 19:54
  • @maxime1992 k, my bad then. So it's just an anti pattern :) – Random Jun 19 '23 at 07:15
  • 100% agree on this. Mixing observables with promises isn't that great. That said, consuming promises from observables is totally fine and well managed by RxJS. So if there's a 3rd party API that gives you promises, one can still use those as inputs for observables – maxime1992 Jun 19 '23 at 09:37

1 Answers1

0

Hope it helps understand the idea:

export class MyComponent implements OnInit {
    
    public moneyData$;
    public moneyData2$;
    
    constructor(private myService: MyService) {}
    
    public ngOnInit() {
        this.moneyData$ = this.myService.getTasksByCategory(this.moneyData, 'money').pipe(map(value) => value.Data);
        this.moneyData2$ = this.moneyData$.pipe(map(value) => value.Data + 25);
})
}

}

in template:

<div>{{ moneyData$ | async }}</div>
<div>{{ moneyData2$ | async }}</div>

for objects in template:

<div>{{ moneyData$ | async | json }}</div>
<div>{{ moneyData2$ | async | json }}</div>

P.S.: read more about reactivity thinking (really helpful article): https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

Dmitriy Ivanko
  • 950
  • 8
  • 17
  • Nitpick but observables are cold by nature, so there's absolutely no need to assign obseravables in the ngOnInit hook and you can simply to the variable declaration and assignment directly and remove entirely the ngOnInit. Keeps the code even shorter – maxime1992 Jun 19 '23 at 09:39
  • @Dmitriy Ivanko I tried your code but I get: "Property 'pipe' does not exist on type 'void'." Maby because 'getTasksByCategory' does not return observable but subscribe – Piotr Deja Jun 21 '23 at 06:34
  • @maxime1992 notice that, ngOnInit aside, Dmitriy Ivanko's solution does not work because this.myService.getTasksByCategory(this.moneyData, 'money') does not return observable, yet he is trying to use pipe on it – Piotr Deja Jun 21 '23 at 07:09
  • @Piotr Deja, yes, this solution implies that the service will be refactor and will return Observables. According to the angular architecture, the service must return an Observable. And the subscription will take place inside the component (the pipe directive performs the subscription, as well as automatic unsubscribing). In your solution, pay attention - in the service you subscribe, but you do not unsubscribe. (stream stays hanging indefinitely) – Dmitriy Ivanko Jun 21 '23 at 09:03
  • Try to invest time and understand how ngrx works (https://ngrx.io/guide/store). Thus, you will understand how to properly organize the program code in Angular (architecture). If you continue to work with streams like this, then in the future you will constantly get confused, try to first learn how subscription / unsubscription should be carried out, what a stream is, etc. – Dmitriy Ivanko Jun 21 '23 at 09:24
  • @DmitriyIvanko ok I will check out ngrx. I managed to return observable, now, with your solution with
    {{ moneyData$ | async }}
    I get [object Object] on the frontend in both cases (moneyData$ moneyData2$)
    – Piotr Deja Jun 21 '23 at 09:42
  • It looks like your Observable returns an object to see which one you use:
    {{ moneyData$ | async | json }}
    To display not an object, but for example a number, change the method in the service: this.moneyData$ = this.myService.getTasksByCategory(this.moneyData, 'money').pipe(map(value) => value.Data.SOME_PROPERTY_OF_MY_OBJECT);
    – Dmitriy Ivanko Jun 21 '23 at 10:21