Internalize Binding Behaviour within ValueConverter

I have a ValueConverter to format numbers - originally based on numeral. Ever since I started to use i18n, I updated the ValueConverter to use Intl.NumberFormat, because I couldn’t work out how to load locales in numeraljs using typescript.

I had to hack the NumberFormatValueConverter to use the formatting currently in use throughout the app, because I didn’t want to change hundreds of references in the html.

The typical useage html using the original numeraljs formatting looks like this

 <div>${item.quantityKg | numberFormat: "0,0.000"}</div>
 <div>${item.price | numberFormat: "0,0.00"}</div>

If I add the behaviour & signal : 'aurelia-translation-signal' to the expression, the binding updates perfectly when the i18n.locale changes.

However, I want to avoid adding hundreds of the same behaviour to the html in my app.

What I would like to do, is internalize the binding behaviour within the NumberFormatValueConverter.

This is the NumberFormatValueConverter

export class NumberFormatValueConverter {

  public constructor(private readonly i18n: I18N) {

  }

  public toView(value: any, format: string) {
    if (value && isNaN(value)) {
      value = null;
    }

    if (format) {
      const arr = format.split("|");
      format = arr[0];
      if (arr.length > 1 && (value === 0 || value === null || value === undefined)) {
        return arr[1] === "null" ? "" : arr[1];
      }
    }

    let minimumFractionDigits = 0;
    let maximumFractionDigits = 0;
    let useGrouping = false;

    const partsThousands = format.split(",");

    if (partsThousands.length > 1) {
      useGrouping = true;

      const partsDecimalPlaces = partsThousands[1].split(".");
      if (partsDecimalPlaces.length > 1) {
        minimumFractionDigits = partsDecimalPlaces[1].length;
        maximumFractionDigits = minimumFractionDigits;
      }
    }

    const options = { minimumFractionDigits, maximumFractionDigits, useGrouping };

    const nf = this.i18n.nf(options, this.i18n.getLocale());
    const result = nf.format(value);

    return result;
  }
}

I looked at trying to override the NfBindingBehavior that comes with aurelia-18n but I’m not sure I really understand the concepts behind binding behaviours and gave up after a couple of hours.

So, my question is - how can I make & signal : 'aurelia-translation-signal' unobtrusively update NumberFormatValueConverter?

TIA
Jeremy

So what you’re describing is actually kinda how the NfValueConverter + NfBindingBehavior is implemented. The idea is that the VC contains the logic, and the BH is just a wrapper, emporing the logic with signaling features. Looking at the BH source over here we register a custom signal aurelia-translation-signal. The next line, essentially just rewrites the expression used in your html to be passed on to the VC. These lines just make sure it doesn’t happen twice.
Last but not least we set up the VC to be used, which uses the given expression, is based on the NfValueConverter and gets the binding arguments provided.
During cleanup we just make sure nothing stays around to pollute the memory.

So that said, you can straight away copy the NfBindingBehavior and only change the this line to read numberFormat and then use the bindingbehavior further on in your html

 <div>${item.quantityKg  & numberFormat: "0,0.000"}</div>
 <div>${item.price & numberFormat: "0,0.00"}</div>

Btw. in order to properly detect what the thousand separtor is, dunno if thats needed for your use case, take a look at how its determined based on the current locale in the method uf

Ah - I was so close!

I did exactly what you suggested except I hadn’t realized that I should have changed the pipe to &.

So instead of

 <div>${item.quantityKg  & numberFormat: "0,0.000"}</div>

I had

 <div>${item.quantityKg  | numberFormat: "0,0.000" &  fn}</div> // I had to rename nf to fn to avoid conflicts.

It all makes sense now!

Many thanks
Jeremy