Home » Coding » Angular » *ngLoaded, an Angular directive to display a loading spinner

*ngLoaded, an Angular directive to display a loading spinner

  • by

If you have ever used Angular for more than a few minutes, you have definitely used the *ngIf directive. It allows you to display, or not, a block of code in a view, based on whether the expression that you pass evaluates to false or not.

The main use I have for the directive is to hide some parts of the template until the data is loaded from the webservice. This works great, but one issue is that an empty page isn’t very attractive, and it is always better to display a loading spinner while waiting for the data to come back. This is why I made this very simple *ngLoaded directive, which has the same exact functionality as *ngIf, but displays a spinner when needed.

A reminder about false values

As a reminder, all the following values will evaluate to false, but you can also do a more complex expression, like calling a function:

false
"" // an empty string
0  // a number equal to 0
null
undefinied // the value of a variable before you assigned anything to it
NaN // not a number

This means that whenever the expression that you put in the *ngIf is evaluated to one of those values, the spinner will be displayed.

The *ngLoaded directive

It will be a bit special, because unlike a lot of directives, this one also requires a component, in order to display the spinner itself. For that reason, I encourage you to have a dedicated module for the directive, to separate it from the rest of your application and make it easily reusable in another project.

The directive itself

The type of directive that we will be creating is a structural directive, because it changes the layout of the page. We need to inject 3 services here:

  • The TemplateRef represents the element that has the directive on it, so your <div> or <ng-container> for example,
  • The ViewContainerRef is used to manipulate the view part of the element, to manipulate the DOM.
  • The ComponentFactoryResolver is used when you want to instantiate new components on the fly, directly using code, rather than letting the framework handle that part.
@Directive({
	selector: '[ngLoaded]'
})
export class LoadedDirective {

	@Input()
	public set ngLoaded(data) {
		if (data) {
			this.viewContainer.clear();
			this.viewContainer.createEmbeddedView(this.templateRef);
		} else {
			this.viewContainer.clear();
			this.viewContainer.createComponent(this.componentFactoryResolver.resolveComponentFactory(LoadedComponent));
		}
	}

	constructor(private templateRef: TemplateRef<any>,
		private viewContainer: ViewContainerRef,
		private componentFactoryResolver: ComponentFactoryResolver) {
	}

}

It might look strange, but the ngLoaded setter is marked as @Input, because it will receive the result of the expression that determines whether or not the data is loaded.

What it does inside, is that if the expression evaluates to true (which means the webservice returned the data already), it will clear what is currently displayed, and then append the real content (represented by this.templateRef). However, when the expression evaluates to false, and the data isn’t ready, it will also clear the current display, but this time it will create a new instance of LoadedComponent, which we will create to display the spinner!

The component

The component is extremely simple, the only thing it does is display a spinner. In this example I will only use a GIF image and embed it, but you can choose to use any spinner that you like, for example some cool CSS spinners.

@Component({
	selector: 'app-loaded',
	template: '<img src="/images/spinner.gif" alt="The data is loading" />'
})
export class LoadedComponent {

	constructor() {
	}

}

The component literally just displays the spinner, but you can add a <div> for example to display it as a block. The last step remaining before it can be used it to register those two elements in a module.

The module

For this last part you have the choice, either you can use the following imports and declarations in your AppModule, or you can create a new LoadedModule, as followed, which will be better if you want to be able to copy and paste your directive in a new project without any hassle.

@NgModule({
	imports: [
		CommonModule
	],
	declarations: [
		LoadedComponent,
		LoadedDirective
	],
	entryComponents: [
		LoadedComponent
	],
	exports: [
		LoadedDirective
	]
})
export class LoadedModule {
}

Using our new loading spinner directive

Now that we have our new very practical directive, it’s time to put it to work! It is very simple, just make sure that the module is imported in your AppModule, and you can then use it in any component’s view:

<div *ngLoaded="myDataArray != null">
    <div *ngFor="let d of myDataArray">
        ...
    </div>
</div>

I use this directive all the time in all of my Angular projects. Insist on the Highest Standard is one of the 14 Amazon leadership principles, and not displaying an empty page while your content is loading is definitely a better customer experience.

I hope this article has provided you with a useful directive, and maybe even gave you ideas on how to leverage this system for new features!

Leave a Reply

Your email address will not be published. Required fields are marked *