2

there are many implementations of the loading indicator while an xhr request is in progress. Some do full page overlay, some display a message in a designated spot on the screen and so on...

I'd like to create a compact solution that I can use it over and over again across multiple projects with 0 footprint and min additional work. For that goal, I decided to implement the loading indicator right within the button being clicked on.

For example, if the button is titled as "SAVE", upon a click on this button, I'd like the button to show a spinner icon next to the word "SAVE" while the following things to happen:

1) button gets immediately disabled so a new click over it is ignored.

2) a loading indicator ( a spinning gif ) starts appearing right next to left of the word "SAVE" and this spinner continues to spin until XHR comes back with a response

3) when XHR responds, button gets enabled again

Since the spinner takes place within the button, the footprint is 0. Since the spinner takes place within the button, there is no overhead of creating special <div>'s in the HTML..

Could someone share with me the topics I need to learn in order to pull this off?

I know about XHR part and the angular's (click) event stuff.

Where I need help is the managing of that spinner's appearing and disappearing and that making that button active or inactive...

I know the solution is somewhere around ngStyle, ngClass, mat-icons, and the [disabled] directive of the button but cannot wrap my mind around it.

newbyJoe
  • 43
  • 7
  • You already know the hard parts, so start one step at a time – aletzo Mar 14 '18 at 10:41
  • Surely, I can get this done on my own... but that will take r&d, time and I've got a ton of other things to do in the project. Is the stackoverflow really overflow now? – newbyJoe Mar 14 '18 at 10:47
  • SO isn't a free coding service for parts of the project you haven't got time for - it's for where you have attempted something and got stuck with a specific problem / piece of code – Pete Mar 14 '18 at 10:50
  • And a great percentage of the SO questions exactly like that. "Majority" explain the failed methods first ( adding 0 value ) to question just to prove the guys like you that the asker has done his/her part. Go on... – newbyJoe Mar 14 '18 at 11:04
  • I already find the answer but I guess to improve SO, I guess I should not share it. SO is not a free coding service... :) and who was asking for that anyway? – newbyJoe Mar 14 '18 at 11:07

1 Answers1

2

As mentioned in the comment, you know the hard parts. But let's take it through the steps nevertheless.

From Angular perspective, there are a few things here:

  • your button with (click) handler and [disabled] property,
  • some sort of a <div class="spinner"/> tag,
  • a service that calls some backend.

Now, the algorhytm is:

  • when the "loading" starts, disable button and show spinner,
  • when the "loading" ends, reenable button, hide spinner.

So, let's assume your service is trivial:

class MyService {
  constructor(private http: HttpClient) {}

  loadResults() {
    return this.http.get('/api/endpoint');
  }
}

It will return an observable, which either succeeds or fails (or hangs, but that is a different question).

Let's solve our tasks. First, button (click) handler:

<button (click)="getResults()">Get results</button>

Now, your component will have to load the results and handle them. But not if we already have loading in progress. Consequently it can also update a "loading" property.

getResults() {
  if (!this.loading) {
    return;
  }
  // otherwise set `loading` flag
  this.loading = true;
  this.myService.loadResults()
    .subscribe(results => this.results = results)
    // trigger this even if we fail
    // note: you might need to import finally operator
    .finally(() => this.loading = false); 
}

So, now we have a boolean loading flag on the component. Great, let's use it on the component, button AND spinner.

<!-- set disabled property depending on the loading status -->
<button (click)="getResults()" [disabled]="loading">Get results</button>

<!-- add "hidden" class depending on the loading status -->
<div class="spinner" [ngClass]="{ hidden: loading }"></div>

Now you should be set.

Edit: as per your comment, your spinner is within the <button> tag itself. Also, you want to use the HTML hidden attribute, instead of hidden css class. That's also possible, code can look like this:

<button class="login-button" (click)="checkData()">
  <i [hidden]="!isLoading" class='material-icons'>spinner</i>
  <span [hidden]="isLoading">Log In <i class='material-icons'>arrow_forward</i></span>
</button>

Alternatively, you could even go with removing the element completely from the DOM:

<button class="login-button" (click)="checkData()">
  <i *ngIf="loading" class='material-icons'>spinner</i>
  <span *ngIf="!loading">Log In <i class='material-icons'>arrow_forward</i></span>
</button>
Zlatko
  • 18,936
  • 14
  • 70
  • 123
  • Ziatko, thanks a lot for the detailed answer, I'm sure it will help a lot of people when it is broken down like this... I see how you use the [disabled] on the button and how it ties to the loading bool. That answers one of my questions that is how to disable the button while XHR is in-flight. BUT, I was trying to put the spinner inside the button text that is to minimize the foot print. If the button say 100px and the word SAVE takes say 50px we still got another 50pc to place a tiny spinner that would prevent the rest of the page being effected. How do you pull that off? – newbyJoe Mar 14 '18 at 11:13
  • I found this solution for that purpose... – newbyJoe Mar 14 '18 at 11:15
  • if you update your answer with this, I'd like to call it complete and select it as the accepted answer. – newbyJoe Mar 14 '18 at 11:15
  • @newbyJoe I've added your example and one more. – Zlatko Mar 14 '18 at 12:47
  • Also, to whoever has downvoted, can I please have a hint on why the downvote so I can try improving my answers in the future? – Zlatko Mar 14 '18 at 12:49
  • Pretty much in most of the cases, in the last 1 year or so, you can ignore the down voters. It used to be good, not anymore. Your answer is more than enough. – newbyJoe Mar 14 '18 at 15:15