Search code examples
aureliacustom-element

Aurelia: Declaring custom element at top-level and access across application


I have a custom element called loading-bar which is used in a number of pages in my app. It's purpose is to show a status message while loading, download of content, and give response messages for backend actions.

loading-bar.html:

<template show.bind="visible">
    <i class="fa fa-circle-o-notch fa-spin fa-fw" if.bind="type==1"></i>
    <i class="fa fa-check fa-fw" if.bind="type==2"></i>
    <span id="loading-text">${message}</span>
</template>

loading-bar.ts:

import { customElement, bindable, bindingMode, inject } from 'aurelia-framework';

@customElement('loading-bar')
export class LoadingBarCustomElement {
    private visible = false;
    @bindable({ defaultBindingMode: bindingMode.twoWay }) message;
    @bindable({ defaultBindingMode: bindingMode.twoWay }) type = 1;


    constructor() {
        this.visible = false;
    }

    messageChanged(newValue, oldValue) {
        if (newValue && newValue !== '')
            this.visible = true;
        else
            this.visible = false;
    }
}

At the moment all pages using the loading bar have this element declared in its html:

<loading-bar message.bind="loadMsg" type.bind="loadType"></loading-bar>

The loading-Bar is controlled by changing the loadMsg and loadType local variables in every page.

What I would like to do is to declare the loading-bar html at just on place, and be able (from any page) to call a method like "showBar(msg, type)" that will affect the globally declared loading-bar.

My first though is to declare the loading-bar in app.html, (just like my nav-bar is declared here) and inject a class (in all the pages' ViewModel), which contain the showBar(msg, type) method that will control the loading-bar.

I am not sure if this is the correct way forward, or how it's best implemented, and would appreciate some help.


Solution

  • You can use the EventAggregator to enable what you want to do.

    loading-bar.ts

    import { customElement, bindable, bindingMode, autoinject } from 'aurelia-framework';
    import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
    
    @customElement('loading-bar')
    @autoinject()
    export class LoadingBarCustomElement {
        private visible = false;
        private sub1: Subscription;
        private sub2: Subscription;
        @bindable({ defaultBindingMode: bindingMode.twoWay }) message;
        @bindable({ defaultBindingMode: bindingMode.twoWay }) type = 1;
    
    
        constructor(private ea : EventAggregator ) {
            this.ea = ea;
            this.visible = false;
        }
    
        attached() {
          this.sub1 = this.ea.subscribe('show-loading', ({ message, type }) => {
            this.type = type;
            this.message = message;
          });
    
          this.sub2 = this.ea.subscribe('hide-loading', () => {
            this.message = '';
          });
        }
    
        detached() {
          this.sub1.dispose();
          this.sub2.dispose();
        }           
    
        messageChanged(newValue, oldValue) {
            if (newValue && newValue !== '')
                this.visible = true;
            else
                this.visible = false;
        }
    }
    

    and then publish the events elsewhere in your app like this:

    import {autoinject} from 'aurelia-framework';
    import {EventAggregator} from 'aurelia-event-aggregator';
    
    @autoinject()
    export class ExamplePage {
        constructor(private ea: EventAggregator){
        ...
        }
    
        async methodUsingTheLoadingBar(){
            ...
            this.ea.publish( 'show-loading, { message: 'Loading...', type: 1 });
            const foo = await getData();
            ...
            ...
            this.ea.publish('hide-loading');
        }
    }