0

I'm having trouble implementing the display of a loading spinner once the user inserts a search term in the search bar. Once I detect a change on my search term property, an API request is done to fill the lists in my view with the corresponding data and I want to show my spinner during this process.

Here is my .ts:

@Component({
    selector: 'app-orders-list',
    templateUrl: './orders-list.component.html',
    styleUrls: ['./orders-list.component.css'],
})
export class OrdersListComponent implements OnChanges, AfterViewInit{
    
    @Input() ordersWithSlots: Order[] = [];
    @Input() ordersWithNoSlots: Order[] = [];
    @Input() timelinesWithSlots: string[] = [];
    @Input() timelinesWithNoSlots: string[] = [];
    @Input() currentTabIndex: number;
    @Input() searchTerm: string;
    filteredSlotsOrders: Order[] = [];
    filteredNoSlotsOrders: Order[] = []
    filteredSlotsTimelines: string[] = [];
    filteredNoSlotsTimelines: string[] = [];
    showSpinner = false;

    constructor(
        private apiManager: ApiManagerService,
        private ordersService: OrdersManagerService
    ) {}

    ngOnChanges(changes: SimpleChanges): void {
        // searchTerm comes from parent component, if it changes then a new search is active
        if (changes.searchTerm) {
            if(changes.searchTerm.currentValue !== changes.searchTerm.previousValue){
                if(changes.searchTerm.currentValue === "") {
                    this.filteredSlotsOrders = [...this.ordersWithSlots];
                    this.filteredNoSlotsOrders = [...this.ordersWithNoSlots];
                    this.filteredSlotsTimelines = [...this.timelinesWithSlots];
                    this.filteredNoSlotsTimelines = [...this.timelinesWithNoSlots];
                }
                else{
                    this.getOrdersBySearchTerm(); // want to show a spinner while this task is running
                }
            }
        }
    ...
    }

    getOrdersBySearchTerm(){
        if(this.currentTabIndex === 0){
            this.apiManager.fetchOrdersBySearchTerm("status=PENDING&status=FULFILLED&only_slots=true", this.searchTerm).subscribe(orders => {
                const loadedOrders = orders;
                this.filteredSlotsOrders = loadedOrders.results;
        });
            this.apiManager.fetchOrdersBySearchTerm("status=PENDING&status=FULFILLED", this.searchTerm).subscribe(orders => {
                const loadedOrders = orders;
                this.filteredNoSlotsOrders = this.ordersService.filterOrders(loadedOrders.results, OrderStatus.PENDING, false);
                this.filteredNoSlotsOrders = [...this.filteredNoSlotsOrders, ...this.ordersService.filterOrders(loadedOrders.results, OrderStatus.PROCESSED, false)]
        });
    ...
    }

And here is my .html:

<app-loading-spinner [hidden]="showSpinner !== true"></app-loading-spinner>
<div [hidden]="showSpinner === true">
    <!-- the actual view and data is here -->
</div>

Since the view exists and has data before we can do a search on the search bar, I can't do something like starting the spinner set to true in ngOnInit and then setting it to false after this.getOrdersBySearchTerm() is called.

What can I do to only show the spinner in this particular search situation?

Thanks in advance.

Manuel Brás
  • 413
  • 7
  • 21
  • What is not working with what you have? – BlackICE May 24 '21 at 15:31
  • https://stackoverflow.com/questions/60207721/how-to-show-a-loading-spinner-while-waiting-on-an-observable-getting-data-from-a – Eliseo May 24 '21 at 15:40
  • @BlackICE I used the spinner previously, however I used it on a load the whole component context (start spinner as true and when component is loaded and data is ready set it to false), which has worked. However, for this one I don't want to start the spinner as true, and only want to show it once I start making the API calls when the search is active. I've tried a couple of things with `setTimeout()` on `ngOnChanges` but the behaviour is never the desired one. – Manuel Brás May 24 '21 at 15:44

1 Answers1

1

How about:

    getOrdersBySearchTerm(){
        this.showSpinner = true;
        if(this.currentTabIndex === 0){
            this.apiManager.fetchOrdersBySearchTerm("status=PENDING&status=FULFILLED&only_slots=true", this.searchTerm).subscribe(orders => {
                const loadedOrders = orders;
                this.filteredSlotsOrders = loadedOrders.results;
                this.showSpinner = false;
        });
            this.apiManager.fetchOrdersBySearchTerm("status=PENDING&status=FULFILLED", this.searchTerm).subscribe(orders => {
                this.showSpinner = false;
                const loadedOrders = orders;
                this.filteredNoSlotsOrders = this.ordersService.filterOrders(loadedOrders.results, OrderStatus.PENDING, false);
                this.filteredNoSlotsOrders = [...this.filteredNoSlotsOrders, ...this.ordersService.filterOrders(loadedOrders.results, OrderStatus.PROCESSED, false)]
        });
    ...
    }
<ng-template #loading>
 <app-loading-spinner></app-loading-spinner>
</ng-template>

<div *ngIf="!showSpinner; else loading">
    <!-- the actual view and data is here -->
</div>
eko
  • 39,722
  • 10
  • 72
  • 98
  • a tap is executed after get the data, :( – Eliseo May 24 '21 at 15:38
  • @eko Not working :/ I can see the view being constructed with the new data but the spinner never shows while this happens. Even tried using a `setTimeout` in tap with 1.5s and still the spinner isn't shown. – Manuel Brás May 24 '21 at 15:39
  • haha, you're right. Then probably moving it to the beginning of the method would solve it – eko May 24 '21 at 15:39
  • @Eliseo you could/should've provide an answer btw; I can delete mine – eko May 24 '21 at 15:43
  • @eko Your updated answer is working. However, once the spinner disappears I can still see the view being constructed. I wonder if I should do `setTimeout` and wait a bit before setting it to false? – Manuel Brás May 24 '21 at 15:53
  • Is that the case even if you move `this.showSpinner = false;` to the bottom of the subscribe block? Or are you doing any other async operation inside the callback? – eko May 24 '21 at 15:55
  • @eko Yeah, seems like it's still the case. – Manuel Brás May 24 '21 at 15:58
  • I don't think working around with `setTimeout` would be clean where/how are you using the response from the subscription in the template? – eko May 24 '21 at 16:01
  • @eko In the template I have two lists setup. I'm using `*ngFor` for each list so I can display the data from the arrays `filteredNoSlotsOrders` and `filteredNoSlotsOrders`. – Manuel Brás May 24 '21 at 16:04
  • 1
    Have you tried using `trackBy` for better performance? https://stackoverflow.com/a/45391247/5706293 – eko May 24 '21 at 16:08
  • @eko On a second try, your updated answer is working great if the data I get from the parent component is not empty. It's only when I have no data originally, and I get it from the search bar API request that, even though I can see the spinner for a split second, I can still see the lists being constructed to some extent. Maybe when there is data originally, the lists are rendered first and that's why it works properly in that scenario? Conversely, when there is no data, the lists still have to be rendered. Just a wild guess. – Manuel Brás May 24 '21 at 16:16
  • huh. How about changing the `hidden` with `ngIf`? I'll update my answer with an example – eko May 24 '21 at 16:19
  • @eko Didn't seem to do much But you know what, the scenario where there is no data originally from the parent component and then data is obtained through the search bar API request is a *very* unlikely one. I'm marking your answer as the correct one as it is simple and is working great for the overwhelmingly more common scenario. – Manuel Brás May 24 '21 at 16:29