Directive replacing loading content with a counter in Angular 2+

In Angular 1 it was quite easy to create a upload directive that replaced content with a counter and was used like this:

<div isLoading="$scope.contentIsLoading"></div>

      

Where contentHasLoaded is a simple boolean value that you set in the controller after calling the data. The directive itself was simple, most of the work was done in the template:

<div class="spinner" ng-if="$scope.isLoading"></div>
<div ng-transclude ng-if="!$scope.isLoading"></div>

      

Is there a "clean" way to do this in Angular 2+? By clean I mean 1) inside Angular, not using vanilla JS to manipulate the DOM directly and 2) Can be implemented as a single attribute for an existing element?

I saw this article as a fallback: Image Upload Directive . However, this is a little more verbose than I would like: using a regular component requires me to wrap all my async content into a new tag, not just add an attribute.

What I'm really looking for is something like a structural directive (which should be for "DOM manipulation".) However, all the examples I've seen are recreations of something like * ngIf, which hides but does not insert new content. In particular, can a structural template 1) have a template, or 2) insert a component, or 3) insert something as simple as <div class="spinner"></div>

. Here's my best attempt:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[loading]',
  inputs: ['loading']
})
export class LoadingDirective {

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

  @Input() set loading (isLoading: boolean) {
    if (isLoading) {
      this.viewContainer.clear();
      // INSERT A COMPONENT, DIV, TEMPLATE, SOMETHING HERE FOR SPINNER
    } else {
      this.viewContainer.clear();
      // If not loading, insert the original content
      this.viewContainer.createEmbeddedView(this.templateRef);
    }
  }

}

      

+3


source to share


1 answer


This can be done in Angular2 + as you described, you are on the right track. Your structural directive will host the host element template and you can inject a component to host the boot image, etc.

Directive This directive takes an input parameter indicating the loading status. Each time this input is set, we clear the display container and inject a load component or host element template depending on the load value.

@Directive({
  selector: '[apploading]'
})
export class LoadingDirective {
  loadingFactory : ComponentFactory<LoadingComponent>;
  loadingComponent : ComponentRef<LoadingComponent>;

  @Input() 
  set apploading(loading: boolean) {
    this.vcRef.clear();

    if (loading)
    {
      // create and embed an instance of the loading component
      this.loadingComponent = this.vcRef.createComponent(this.loadingFactory);
    }
    else
    {
      // embed the contents of the host template
      this.vcRef.createEmbeddedView(this.templateRef);
    }    
  }

  constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver) {
    // Create resolver for loading component
    this.loadingFactory = this.componentFactoryResolver.resolveComponentFactory(LoadingComponent);
  }
}

      

Component You can see that it means nothing to keep the template.



@Component({
  selector: 'app-loading',
  template: `<div class="loading">
              <img src="assets/loading.svg" alt="loading">
            </div>`
})
export class LoadingComponent {

  constructor() { }
}

      

Implementation Using a structured directive associated with a boolean

<div *apploading="isLoadingBoolean">
  <h3>My content</h3>
  <p>Blah.</p>
</div>

      

Note. You also need to include LoadComponent in the entryComponents array in ngModule.

+4


source







All Articles