1

I created a behavior subject that I am using to toggle a loading spinner icon within my application.

Service:

// Observe our loader status
public loaderStatus: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

/**
 * Toggle the loading indicator status
 * @param value
 */
displayLoader(value: boolean) {
    this.loaderStatus.next(value);
}

Component:

this._massEmpService.displayLoader(true); // Toggle true/false

HTML:

<div *ngIf="objLoaderStatus" class="loader" align="center">
    <img src="images/loading-bars.svg" alt="" />
</div>

While this works just fine for a single spinner instance, if I want to use this spinner in multiple areas through my application, the function is too broad and would end up triggering all the spinner instances in the app if multiple spinners existed on the same page.

My Question:

Is it possible to pass an object or multiple params to a behavior subject so that I can not only pass the enabled/disabled status but also an element ID of some type so I can control which spinner I want to show.

Example Goal:

<div *ngIf="objLoaderStatus && spinnerID == 'home'" class="loader" align="center">
    <img src="images/loading-bars.svg" alt="" />
</div>

<div *ngIf="objLoaderStatus && spinnerID == 'search'" class="loader" align="center">
    <img src="images/loading-bars.svg" alt="" />
</div>

Function Call:

this._massEmpService.displayLoader(true, 'search');

Whats the best way to go about doing this? Will I need to make a second behavior subject just to hold the spinner's elementID I want to reference?

SBB
  • 8,560
  • 30
  • 108
  • 223

2 Answers2

1

If you want to use multiple spinners on the same page, I would rather use a structural directive or a spinner component for this purpose. See the xample below :-

Angular img loading directive

Angular2: progress/loading overlay directive

Krishnanunni Jeevan
  • 1,719
  • 1
  • 15
  • 24
  • Thanks for the reply. I fail to see how this is different than what I have. They have a component that wraps sections on their page. That component receives a prop that enables / disables it which is what I am essentially doing with a property and using `ngIf`. The part that confuses me is how to specifically say which one i want to toggle when dealing with multiple instances of loading spinners. – SBB Jul 24 '17 at 15:57
  • @SBB, It is more of a separation of concern. Service has a global scope. As long as you use the spinner for the whole page it is great. If you have multiple components with spinners for each of them a structural directive would make sense. It would automatically be hidden / destroyed when the component on which it is applied is hidden. The elementRef in the directive will give the element on which it was applied. So you can get all the dom properties of the element there if required. You can pass the condition to show spinner as input to the directive. I will try to update my answer with eg – Krishnanunni Jeevan Jul 25 '17 at 00:02
  • It sounds like it may fit my situation, just had a hard time gathering that from the linked sites. If time allows and you have a better example, would love to hear it and accept this as my solution once implemented. – SBB Jul 25 '17 at 00:10
1

To use multiple parameters in your BehaviorSubject, you may create a new class to hold the parameters.
I had almost the exact same use-case with a loader icon, where my app is written in TS for Angular 4, but I wanted to expose a client API in JS for plugins.
You can see more about the implementation here -
https://github.com/savantly-net/sprout-platform/tree/development/web/sprout-web-ui/src/app/client-api

Loader Options definition -

export class LoaderOptions {
  key: string;
  element: Element
}  

In the Service -

  showLoaderBehavior = new BehaviorSubject<LoaderOptions>(null);
  hideLoaderBehavior = new BehaviorSubject<LoaderOptions>(null);

  showLoader(options: LoaderOptions) {
    this.zone.run(() => this.showLoaderBehavior.next(options));
  }

  hideLoader(options: LoaderOptions) {
    this.zone.run(() => this.hideLoaderBehavior.next(options));
  }

Implementation in the component -

  ...  
  showLoader = function (options: LoaderOptions) {
    if (options == null) {
      return; // probably just initialized, so return silently
    }
    if (!options.key) {
        throw new Error('A key is required to show the loader, so that it may be removed with the same key.');
      }
      const defaultElement = document.querySelector('my-client-api');
      options.element = options.element || defaultElement;

      const imgWrapper = document.createElement('div');
      imgWrapper.setAttribute('id', options.key);
      imgWrapper.setAttribute('style', 'text-align:center;');

      const imgElement = document.createElement('img');
      imgElement.setAttribute('style', 'width:200px;');
      imgElement.setAttribute('src', './img/loader.svg');

      imgWrapper.appendChild(imgElement);
      options.element.appendChild(imgWrapper);
  };

  hideLoader = function (options: LoaderOptions) {
    if (options == null) {
      return; // probably just initialized, so return silently
    }
    if (!options.key) {
      throw new Error('A key is required to remove the loader');
    }
    const imgWrapper = document.querySelector('div#' + options.key);
    imgWrapper.remove();
  };

  ngAfterViewInit() {
    this.sproutApi.toastSubject.subscribe(options => this.handleToast(options));
    this.sproutApi.showLoaderBehavior.subscribe(options => this.showLoader(options));
    this.sproutApi.hideLoaderBehavior.subscribe(options => this.hideLoader(options));
  }
  ...  

JS Plugin using the api -

<script type="text/javascript">
    function Shack() {

        var processInfoElement = document.querySelector('.shack-processes');

        this.loadProcessInfo = function(){
            var loaderKey = 'pLoader';
            sprout.showLoader({key: loaderKey});
            sprout.zone.run(function(){
                sprout.http.get('./rest/modules/shack/processes', {responseType: 'text'}).subscribe(function(response){
                    processInfoElement.innerHTML = response;
                    sprout.hideLoader({key: loaderKey});
                });
            });
        };


    }
    window.shack = new Shack();
</script>
Jeremy
  • 2,970
  • 1
  • 26
  • 50