#Is it correct async solution?

1 messages · Page 1 of 1 (latest)

ocean quest
#

I had

  private getActiveOffersCount(): void {
    this.activeOffersCount$ = from(this._offersApi.offers.my.count.get())
      .pipe(
        map(q => q?.count!),
        catchError(() => of(0))
      );
  }```

```html
<ng-container *ngIf="activeOffersCount$ | async as activeOffersCount; else activeOffersCountSpinner">
  <p class="display-4">{{ activeOffersCount }}</p>
</ng-container>
<ng-template #activeOffersCountSpinner>
  <div class="spinner-border" role="status"></div>
</ng-template>```

And it worked but idk what happend
#

Then i changed to first approach and it works but I'm not really sure this is the best approach

#

Is it correct async solution?

#

By saying it does not work I mean it showed spinner all the time

#

And my request was executed successfully

split brook
#

In your first code, you set isLoadingActiveOffersCount to false before the operation has even started. If you moved it into the pipe, then the element won't display, and the async subscription won't start. That flag is effectively doing nothing useful.

In both variations of your code, you are mapping the result. Apparently the results aren't guaranteed (hence "q?), so there is a possibility that this map will return undefined.

In both variations of your code you are swallowing errors with catchError, so you have no way of knowing if the operation is actually succeeding or not.

My guess is that one of the following is happening:

  • the result is undefined
  • the count is zero
  • there is an error that is converted to a result of zero

Zero and undefined are not truthy, so the *ngIf condition is never met and it stays on the spinner.

ocean quest
split brook
#

*ngIf="(activeOffersCount$ | async as activeOffersCount) >= 0; else activeOffersCountSpinner"

ocean quest
#

Something like that works

 private getActiveOffersCount(): void {
    from(this._offersApi.offers.my.count.get())
      .pipe(
        map(q => q?.count!),
        catchError(() => of(0))
      )
      .subscribe(q => {
        this.activeOffersCount = q;
        this.isLoadingActiveOffersCount = false;
      })
  }```
#

is it good approach?

ocean quest
#

Here is another example in my code, it does not show any data but my request is returning normal array i see in dev tools and it supposed to return some elements

    <ng-container *ngIf="(invoices$ | async) as invoices; else loading">
private getInvoices(): void {
    this.invoices$ = scheduled(this._commerceApi.invoices.get(), asyncScheduler)
      .pipe(
        map(q => q!),
        catchError(() => {
          this._snackBar.open('Something went wrong while fetching invoices', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-danger']
          });

          return of([]);
        })
      );
  }```
split brook
#

Sorry, you might have to move the parenthesis to after async or add another set around the async part of the untested *ngIf I wrote to satisfy the syntax.

Regardless, your new code should be fine as long as you add a takeUntil(this.destroyed$) or unsubscribe in ngOnDestroy to clean up the subscription.

ocean quest
#

I think I do everything correct

#

But something is still wrong

#

Also I saw documentation

#

And I do it 1:1

#

Can you help me with that? @split brook

split brook
#

For your invoices code, why are you using asyncScheduler?

ocean quest
#

I used from before scheduled but vs code show me that from is deprecated and navigated me to rxjs docs where they say to use scheduled

#

Is that something that I shouldn't use?

#

Also when I add tap(q => console.log(q)) to it it shows correct data

#

I really don't know why it does not show data in html

split brook
#

from is not deprecated, but some signatures of it might be. What does this._commerceApi.invoices.get() actually return? Promise<Invoice[]>?

ocean quest
#

Promise<InvoiceDto[] | undefined>

#

Okey, smth like this works

this.invoices$ = from(this._commerceApi.invoices.get())
      .pipe(
        map(q => q || []), 
        tap(q => {
          console.log(q);

        }),
        catchError(() => {
          this._snackBar.open('Something went wrong while fetching invoices', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-danger']
          });

          return [];
        })
      );```
#

Is it good solution?

split brook
#

That looks fine. Was it the change to map that primarily fixed it?

ocean quest
#

@split brook
Any idea why when I put my tickets update pipe outside subscribe it works, I mean it update the tickets observable but when I use it inside subscribe it does not update?

 this._dialog
      .open(CloseTicketDialogComponent, dialogConfig)
      .afterClosed()
      .subscribe(res => {
        this.tickets$?.pipe(
          map(q => {
            const i = q.findIndex(q => q.id == '1112002917570707456');
    
            q[i].status = 'Closed';
    
            return q;
          }),
          tap(q => console.log(q))
        );
      });```
#

Subscribe works

#

But this pipe does not run inside subscribe

split brook
#

A lot of terms in RxJS like "pipe" are metaphors: A "stream" won't flow from a "pipe" until you turn a knob at the sink (subscribe). You are subscribing to afterClosed(), but your inner pipe is not connected to the outer one so nothing comes out of it. You need to adjust your plumbing to link the pipes together:

    this._dialog
      .open(CloseTicketDialogComponent, dialogConfig)
      .afterClosed()
      .pipe(
        /* "switch" from one stream to another */
        switchMap((res) => this.tickets$)
        map(q => {
          const i = q.findIndex(q => q.id == '1112002917570707456');
          q[i].status = 'Closed';
          return q;
        }),
        tap(q => console.log(q))
      )
      .subscribe();

Note: I didn't put a question mark after this.tickets$. You really should be sure if this exists or not, or this will error out.

ocean quest
#

how about if I want execute it in res.success like this?

this._dialog
      .open(CloseTicketDialogComponent, dialogConfig)
      .afterClosed()
      .subscribe(res => {
        if (!res) {
          return;
        }
        
        if (res.success) {
          this._snackBar.open('Successfully closed ticket', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-success']
          });

          this.tickets$ = this.tickets$?.pipe(
            map(q => {
              const i = q.findIndex(q => q.id == '1112002917570707456');
      
              q[i].status = 'Closed';
      
              return q;
            }),
            tap(q => console.log(q))
          );

          return;
        }
        
        this._snackBar.open('Something went wrong while closing ticket', '', {
          horizontalPosition: 'right',
          verticalPosition: 'top',
          duration: 2500,
          panelClass: ['snackbar-danger']
        });
      });```
#

Where should i do this if statements

#

In switch map?

ocean quest
#

That's weird

#

I mean it updates the status when I click 2nd time on button

#

on first click nothing is changed

split brook
#

Yeah that's pretty much the same problem. The inner pipe doesn't connect to the outer pipe, so it doesn't get run.

ocean quest
#

So how do i connect inner pipe to the outer pipe?

split brook
#

My last comment was referring to the new code you posted. You need to use switchMap just like in the other code.

ocean quest
#

Firsly let me fix that

split brook
#

If the code I suggested isn't working, then either tickets$ doesn't exist the first time, the index being searched for in map wasn't found, or something is wrong in the tickets$ implementation.

ocean quest
#

public tickets$: Observable<TicketDto[]> = new Observable<TicketDto[]>();

#

So how this looks like

#

They exist I see them in a table

split brook
#

When and how is tickets updated?

ocean quest
#

In map, even console log in tap show correct data (Tickets with updated element)

 this._dialog
      .open(CloseTicketDialogComponent, dialogConfig)
      .afterClosed()
      .pipe(
        switchMap((res) => this.tickets$),
        map(q => {
          const i = q.findIndex(q => q.id == '1112002917570707456');
          console.log(i)
          q[i].status = 'Closed';
          return q;
        }),
        tap(q => console.log(q))
      )
      .subscribe();```
#

After first time, but changes on html is showed after second button click

split brook
#

That code gets a value from tickets$ what puts the values in?

ocean quest
#

You mean the map part?

#

or what

split brook
#

no nothing in this code. What code have you not shared yet that puts a value in the tickets$ observable?

ocean quest
#

ahh okey

#
public getTickets(event: PageEvent): void {
    const conf = new MyRequestBuilderGetRequestConfiguration();
    const queryParameters = new MyRequestBuilderGetQueryParameters();

    queryParameters.pageNumber = event.pageIndex;
    queryParameters.pageSize = event.pageSize;

    conf.queryParameters = queryParameters;

    this.tickets$ = from(this._ticketsApi.tickets.my.get(conf))
      .pipe(
        map(q =>  {
          this.pageEvent.length = q?.totalItems!;
          this.pageEvent.pageIndex = q?.pageNumber!;

          return q?.tickets!;
        }),
        catchError(() => {
          this._snackBar.open('Something went wrong while fetching tickets', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-danger']
          });

          return [];
        })
      );
  }```
#
<ng-container *ngIf="(tickets$ | async) as tickets; else loading">
        <table class="table table-striped table-sm">
            <thead>
                <tr>
                    <th class="col-sm-1">Status</th>
                    <th class="col-sm-3">Nr</th>
                    <th class="col">Category</th>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let ticket of tickets">
                    <td *ngIf="ticket.status == 'Opened'"><span class="badge text-bg-info">Opened</span></td>
                    <td *ngIf="ticket.status == 'In progress'"><span class="badge text-bg-warning">In progress</span></td>
                    <td *ngIf="ticket.status == 'Resolved'"><span class="badge text-bg-success">Resolved</span></td>
                    <td *ngIf="ticket.status == 'Closed'"><span class="badge text-bg-danger">Closed</span></td>
                    <td>#{{ ticket.id }}</td>
                    <td>{{ ticket.category }}</td>
                    <td>
                        <div class="float-end">
                            <i-feather *ngIf="ticket.status != 'Opened'" (click)="onReOpenTicket(ticket.id!)" type="button" name="rotate-ccw" title="Re-open ticket" class="td-icon re-open-ticket"></i-feather>
                            <i-feather *ngIf="ticket.status != 'Closed'" (click)="onCloseTicket(ticket.id!)" type="button" name="x-circle" title="Close ticket" class="td-icon close-ticket"></i-feather>
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>
        <mat-paginator #paginator
            (page)="getTickets($event)"
            [length]="pageEvent.length"
            [pageSize]="pageEvent.pageSize"
            showFirstLastButtons="false"
            hidePageSize="true"
            [pageIndex]="pageEvent.pageIndex">
        </mat-paginator>
    </ng-container>```
#

I want badge to be updated after status update

#

Now it's more clear maybe?

split brook
#

Probably what is happening is that the first time you click the button, the code we worked on is subscribing to the default Observable you defined that emits no values (new Observable<TicketDto[]>()). The second time you click the button is after getTickets is called, which is a completely different Observable.

You should not be giving a default value to ticket$ if that is not the observable you will actually be updating. You should not be running the code above for after the dialog has closed without knowing if the tickets have been fetched. You should structure the code so that this.tickets$ is set once and only once for the lifetime of the page, which means you should probably be using a BehaviorSubject.

ocean quest
#

Okey so from what I understand, I should use this right?
public tickets$ = new BehaviorSubject<TicketDto[]>([]);

split brook
#

Yep, and when there is new data call this.tickets$.next(newvalue)

ocean quest
#

So it should looks like this?

public getTickets(event: PageEvent): void {
    const conf = new MyRequestBuilderGetRequestConfiguration();
    const queryParameters = new MyRequestBuilderGetQueryParameters();

    queryParameters.pageNumber = event.pageIndex;
    queryParameters.pageSize = event.pageSize;

    conf.queryParameters = queryParameters;

    from(this._ticketsApi.tickets.my.get(conf))
      .pipe(
        map(q =>  {
          this.pageEvent.length = q?.totalItems!;
          this.pageEvent.pageIndex = q?.pageNumber!;

          return q?.tickets!;
        }),
        catchError(() => {
          this._snackBar.open('Something went wrong while fetching tickets', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-danger']
          });

          return [];
        })
      )
      .subscribe(q => this.tickets$.next(q));
  }```
split brook
#

looks good except for return q?.tickets!. If you can't count on your API to return data you still need to pass an array when it doesn't.

ocean quest
#

Yeah but if something is wrong i return empty array thats what I do in catch error

#

or its not how it works

split brook
#

q? means that q can possibly be undefined. If you are sure a value will be returned if there are no HTTP errors then you don't need ?.

ocean quest
#

Okey so right now it's updating the status but with minimal delay but I think it's because it prints like 1000x in tap

  this._dialog
      .open(CloseTicketDialogComponent, dialogConfig)
      .afterClosed()
      .pipe(
        switchMap((res) => this.tickets$),
        map(q => {
          const i = q.findIndex(q => q.id == ticketId);
          console.log(i)
          q[i].status = 'Closed';
          return q;
        }),
        tap(q => console.log(q))
      )
      .subscribe(q => this.tickets$.next(q));```
#

Something like that seems working

this._dialog
      .open(CloseTicketDialogComponent, dialogConfig)
      .afterClosed()
      .subscribe(res => {
        if (!res) {
          return;
        }
        
        if (res.success) {
          this._snackBar.open('Successfully closed ticket', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-success']
          });

          const tickets = this.tickets$.getValue();
          const i = tickets.findIndex(q => q.id === ticketId);

          tickets[i].status = 'Closed';

          this.tickets$.next(tickets);

          return;
        }
        
        this._snackBar.open('Something went wrong while closing ticket', '', {
          horizontalPosition: 'right',
          verticalPosition: 'top',
          duration: 2500,
          panelClass: ['snackbar-danger']
        });
      });```
split brook
#

Okay so now you need one more thing. Since subjects are observables that emit multiple values, you need to tell this code that you only need the most recent value. In the switchMap, add .pipe(take(1)) after this.tickets$

ocean quest
#

Is my solution good or nah?

split brook
#

Actually yeah that's even a better idea to use getValue rather than piping the stream. Nice work.

ocean quest
#

So it's better to use BehaviorSubject when I need to modify my data instead of observability?

#

Also is BehaviorSubject is not being replaced by signal?

split brook
#

Subjects in general, yes...or you can get really fancy with your pipes. Subject, BehaviorSubject, ReplaySubject, etc. are still observables, but they have extra capabilities.

Signals are indeed incredibly similar to BehaviorSubjects. Learning this pattern will serve you well for both RxJS (which isn't going anywhere) as well as for using signals. And you will be able to convert between the two to boot.

ocean quest
#

Mhm but, when should I use BehaviorSubject and When plain Observability?

ocean quest
#

Also right now it does not show my loading, should I use boolean property to show it? When I used Observability my spinner normally showed

<div class="table-responsive">
    <ng-container *ngIf="(tickets$ | async) as tickets; else loading">
        =
    </ng-container>
</div>
<ng-template #loading>
    <div class="text-center text-grey">
        <div class="spinner-border"></div>
    </div>
</ng-template>```
split brook
#

Subjects are useful for "multicasting" (when you have multiple subscribers), and when you don't need to perform an operation more than once, but need to share the values. In most situations Subjects are usually in Angular services, where multiple components can access them.

ocean quest
#

Okey I understand

split brook
#

tickets$ will always be truthy now (empty arrays are also truthy), so yeah you'll have to adjust the *ngIf and perhaps add a flag for when it is loading

ocean quest
#

"add a flag for when it is loading" wyd?

#

You mean the property?

split brook
#

public isLoading = true;

ocean quest
#

ah yeah

#

Mhm, alright

#

Okey, I think that's it for me for today, I'm going to sleep it's 4am. Thank you very much for helping and explaining everything to me.

split brook
#

no problem. pay it forward!

ocean quest
#

Or I have one more question

#

When I was writing in angular app the only thing I need to done is for example
this._ticketsApi.tickets.my.get(conf).then(q => /* code */)
but then I moved to nx and I started creating angular libraries and write code there and right now I need to use rxjs and this subjects,
You know maybe why is that?

#

Thats how it looked when I was writing in angular app

this._ticketsApi.tickets.my
      .get(conf)
      .then(q => {
        this.tickets = q?.tickets!;
        this.pageEvent.length = q?.totalItems!;
        this.isLoading = false;
        this.fetchTicketsOut.emit(false);
      });```
#

And i just used normal properties

#

without any subjects

#

and it worked

#

does angular app handle async other way that lib does or what?

#

I don't understand it

split brook
#

That code uses JavaScript Promises. Nothing after Promise.then() executes until the operation finishes, which makes it easy to code and understand but limited. You can't easily do other things while waiting on a Promise to finish. You can think of Observables as Promises on steroids. With Observables you can easily set things up like "I want to detect typing in an input, and if the user stop typing for 500 milliseconds call an API to get autocompletion suggestions, and if the connection fails then try up to 3 more times before displaying an error". You're already tapping into some of that power with what you're doing now.

ocean quest
#

Mhm okey I understand

#

Also do you think it’s good idea to use signal instead of behavior subject for that case that you helped me earlier? And if I will signal do I need to use async pipe?

split brook
#

With your current design, it's probably better to leave it as a BehaviorSubject because you would have to integrate it with the RxJS code anyway. Async pipe is specifically for observables.

ocean quest
#

Okey

#

For data that I will not change better will use Observability instead of BehaviorSubject? I suppose I just BehaviorSubject in this case because of update data functionality?

#

And why when I used Observability I does not needed the is loading flag? But when I use subject I need? How Observability handle loading automatically

split brook
#

Yes use a simple observable and an async pipe in template whenever possible, but if you have to manually subscribe just put the result in a property.

They're both Observables, but a BehaviorSubject starts off with a value, and a simple Observable has no value at first.

#

You could perhaps avoid having to add a flag if you set the initial value to null.

#

But if you need to refresh the data, you might want that flag.

ocean quest
#

Is this approach is better than using async pipe with subject?

  public tickets = signal<TicketDto[]>([]);
  public isLoading = signal<boolean>(true);

    this._ticketsApi.tickets.my.get(conf)
      .then(q => {
        this.pageEvent.length = q?.totalItems!;
        this.pageEvent.pageIndex = q?.pageNumber!;
        this.tickets.update(v => v = q?.tickets!)
        this.isLoading.update(v => v = false);
      })
      .catch(() => {
        this.isLoading.update(v => v = false);

        this._snackBar.open('Something went wrong while fetching tickets', '', {
          horizontalPosition: 'right',
          verticalPosition: 'top',
          duration: 2500,
          panelClass: ['snackbar-danger']
        });
      });
  from(this._ticketsApi.tickets.my.get(conf))
      .pipe(
        map(q =>  {
          this.pageEvent.length = q?.totalItems!;
          this.pageEvent.pageIndex = q?.pageNumber!;

          return q!.tickets!;
        }),
        catchError(() => {
          this._snackBar.open('Something went wrong while fetching tickets', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-danger']
          });

          return [];
        })
      )
      .subscribe(q => this.tickets$.next(q));```
#

@split brook

split brook
#

Both versions have pros/cons.

First version: The spinner will stay up in an error scenario. It looks cleaner using promises, but halts code until the promise is done. Promises also can't be cancelled in the event that the user decides to leave the page early or click a button multiple times.

Second version: Same thing we discussed earlier about not knowing whether the API returns anything (q? and q!) and returning a default value if it does not. Unlike promises, subscriptions can be cancelled. You want to take advantage of this to avoid unexpected behavior when someone leaves the page quickly or clicks a button multiple times. You should use the takeUntil operator or unsubscribe upon component destruction to avoid this.

ocean quest
#
  public tickets = signal<TicketDto[]>([]);
  public isLoading = signal<boolean>(true);
  private unsubscribe$ = new Subject<void>();

  public ngOnInit(): void {
    this.getTickets(this.pageEvent);
  }

  public ngOnDestroy(): void {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }

 public getTickets(event: PageEvent): void {
    this.isLoading.update(() => true);

    const conf = new MyRequestBuilderGetRequestConfiguration();
    const queryParameters = new MyRequestBuilderGetQueryParameters();

    queryParameters.pageNumber = event.pageIndex;
    queryParameters.pageSize = event.pageSize;

    conf.queryParameters = queryParameters;

    from(this._ticketsApi.tickets.my.get(conf))
      .pipe(
        takeUntil(this.unsubscribe$),
        map(q =>  {
          this.pageEvent.length = q?.totalItems!;
          this.pageEvent.pageIndex = q?.pageNumber!;

          return q!.tickets!;
        }),
        catchError(() => {
          this._snackBar.open('Something went wrong while fetching tickets', '', {
            horizontalPosition: 'right',
            verticalPosition: 'top',
            duration: 2500,
            panelClass: ['snackbar-danger']
          });

          return [];
        })
    )
    .subscribe(q => {
      this.tickets.update(() => q)
      this.isLoading.update(() => false);
    });
  }```
#

What do you think about it right now? @split brook

split brook
ocean quest
#

It only get called in on init

#

but thank you very much