Sometimes your users need to know that stuff is happening behind the scenes. That's when a progress spinner comes in handy.

Fortunately, Athwart Material offers a module that makes adding progress spinners to your UI style also easy. I'll show yous how to do information technology in this guide.

Equally always, you can just jump straight to the source code on GitHub. Alternatively, hang effectually here for a while and learn near the whys and wherefores.

Call up: if yous're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub lawmaking for each guide belongs to a specific co-operative. The principal branch holds the unabridged application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Notation that the website displays the guides in reverse chronological lodge then if you desire to showtime from the get-go, go to the final page.

The Business concern Requirements

Your boss Smithers walks into your part to talk virtually the CRM app you're working on.

"Look, you got the contour prototype upload thing working just fine," he says. "Merely users don't come across anything while the upload is happening. They're left just wondering if anything is going on in the back stop!"

Y'all nod in understanding.

"So why non add together a spinny matter that will make it articulate to them that some action is happening? You can do that, correct?"

Yous nod again.

"Okay then. Information technology's settled."

He jumps out of his chair and leaves your role whistling that tune from Impale Nib.

Recent Updates

Before getting to the spinner, y'all need to make some updates to ProfileImageComponent. Hither's what it needs to look like:

          import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { HttpResponse, HttpEvent } from '@angular/mutual/http'; import { UploadFileService } from '../../service/file-upload.service'; import { UserService } from '../../service/user.service'; import { UploadedImage } from '../../ui/model/uploaded-image'; import { ImageService } from '../../ui/service/image-service';  const profileImageUploadUrl: string = 'http://localhost:8080/user/profileImage';  @Component({     selector: 'app-profile-paradigm',     templateUrl: './profile-image.component.html',     styleUrls: ['./contour-epitome.component.css'],     encapsulation: ViewEncapsulation.None }) export course ProfileImageComponent implements OnInit {    currentFileUpload: UploadedImage;   changeImage: boolean = simulated;   imageError: string = nothing;   uploading: boolean = fake;    constructor(individual uploadService: UploadFileService, individual userService: UserService,     private imageService: ImageService) { }    ngOnInit() { }    change($event) {     this.changeImage = true;   }    upload() {     this.uploading = true;      this.uploadService.pushFileToStorage(this.currentFileUpload.file, profileImageUploadUrl)         .subscribe(event => this.handleEvent(upshot),             err => this.handleError(err));   }    handleEvent(effect: HttpEvent<any>) {     if (event instanceof HttpResponse) {       let response: HttpResponse<any> = <HttpResponse<any>>upshot;       if (response.condition == 200) {         this.handleGoodResponse();       }     }   }    handleGoodResponse() {     this.currentFileUpload = undefined;     this.uploading = false;   }    handleError(err: Error) {     console.mistake("Error is", err);     this.imageError = err.message;     this.uploading = false;   }    onUploadedImage(prototype: UploadedImage) {     this.imageError = this.imageService.validateImage(prototype);      if (!this.imageError) {         this.currentFileUpload = image;     }   } }        

The biggest change there from previous guides is that the clicked boolean has been removed in favor of uploading.

Why? Because it'due south more descriptive.

The uploading boolean will become set to true when the user formally uploads the contour image to the server via the Spring Boot user service. When the upload is complete, the boolean volition go back to false.

As you lot've probably guessed, while that uploading boolean is true, the user will see a spinner indicating that work is going on under the covers.

Modifying the Module

You also need to update UserModule then information technology looks like this:

          import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@athwart/router'; import { FlexLayoutModule } from '@angular/flex-layout'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/textile/input'; import { MatButtonModule } from '@angular/cloth/button'; import { MatProgressSpinnerModule } from '@angular/textile/progress-spinner'; import { ReactiveFormsModule } from '@angular/forms'; import { AccountInfoComponent } from './account-info/account-info.component'; import { ProfileImageComponent } from './profile-image/profile-image.component'; import { ImageUploaderComponent } from '../ui/prototype-uploader/image-uploader.component';   export const routes = [   { path: '', pathMatch: 'full', redirectTo: 'account-info' },   { path: 'account-info', component: AccountInfoComponent },   { path: 'profile-image', component: ProfileImageComponent } ];  @NgModule({   declarations: [     AccountInfoComponent,     ProfileImageComponent,     ImageUploaderComponent   ],   imports: [     CommonModule,     FlexLayoutModule,     MatIconModule,     MatInputModule,     MatButtonModule,     MatProgressSpinnerModule,     ReactiveFormsModule,     RouterModule.forChild(routes)   ] }) export class UserModule { }        

The pocket-size change at that place is the addition of MatProgressSpinnerModule to the imports.

As you lot've probably guessed again, that's the Angular Material module that gives you the spinner.

And why are yous importing it in UserModule? Because the component that handles profile image uploads happens to be a part of that same module.

Spin Metropolis

Finally, information technology's time to update the HTML in profile-epitome.component.html and then it shows a spinner.

          <div>   <div>     <h5>Profile Image</h5>     <p>Upload a square image no smaller than 200x200.</p>     <div *ngIf="imageError" class="errorMessage">       <strong class="mat-error">{{imageError}}</stiff>     </div>   </div>   <div>     <div>       <app-image-uploader (uploadedImage)="onUploadedImage($result)"></app-prototype-uploader>     </div>     <div *ngIf="!uploading">       <button mat-raised-push color="primary" [disabled]="!currentFileUpload" (click)="upload()">Salve Prototype</button>     </div>     <div *ngIf="uploading" style="margin-left:10px">       <mat-spinner [bore]="50"></mat-spinner>     </div>   </div> </div>        

Take a gander at those two <div> elements towards the lesser. That's where all the practiced stuff is happening.

The showtime <div> just displays when the file is not uploading. That'southward why yous run across *ngIf="!uploading" inside the chemical element.

That *ngIf expression, by the way, is called structural directive in Angular. Structural directives handle HTML layout.

They shape the structure of the Document Object Model (DOM). Hence the name.

In this case, *ngIf tells the framework to only display the <div> and its kid elements if the uploading boolean is false.

Yep, that's the aforementioned uploading boolean y'all added to ProfileImageComponent earlier.

That <div> chemical element displays the button that the user volition press to initiate the upload. So it makes sense that it would only display when the user is not already uploading something.

Now look at that  last <div> element. It displays only when the uploading boolean is set to truthful. Users volition see that element when they're uploading the profile image.

And what'due south in that <div>? The spinner, of course.

The element that displays the spinner is <mat-spinner> . The [bore] property in that element sets the size of the spinner.

That's it. Really, it'south that elementary.

Entering the Spin Cycle

Now it's time to test out your spinner. Begin by firing upwards your Spring Kick user service. So, launch your Angular app with all the new lawmaking in place.

Login using the credentials you lot've used all along (darth/thedarkside). Then, click on User and Profile Epitome on the left-hand nav bar.

Yous'll see the familiar folio to upload a contour pic.

But before you exercise anything, stop your Spring Boot user service.

Why? Because y'all desire to come across your spinner in action. With the Spring Kicking service off, the Angular app will have a few seconds to figure out that the server is down and give you a chance to bank check out your spinner in all its glory.

And so seriously, finish the Spring Boot user service.

Now, upload a photo as usual. Click Save Prototype. You lot should come across a spinner appear where the button used to be:

Once the upload is finished, you'll see the push over again. You'll also run across an fault message because the Spring Kick server is downwards. That'southward okay.

You tin click Salvage Image again if you lot want to see the spinner in activity a 2nd time.

Wrapping Information technology Up

Well that was pretty easy, wasn't information technology?

Now it'south upwardly to you to brand changes equally you see fit. You might, for example, desire to alter the size of the spinner.

Y'all can also alter its color. See the docs for more than details.

And if you desire to borrow the lawmaking from GitHub, you can do that too.

Have fun!

Photo by Tanino  from Pexels