Convert exponential numbers to decimal form in Javascript

As almost every developers knows, Javascript has notorious problems when working with really big or really small decimal numbers. The reason is floating point. Purpose of this article is not to explain why it behaves the way it does, there are much better resources dedicated just for that. You can read the explanation here or here. There are also various libraries dedicated to working with decimals and precise calculations, such as big.js.

To ilustrate the problem, see the short video below. The input shows the text value, and on the right there is raw value as represented in Javascript. The issue it can cause, is that when Angular (or any other framework for that sake) re-renders the value, it uses the raw value. So the 0.00000001 is replaced with 1e-8. Not very user friendly.

So as a workaround, I’ve written simple utility function. It takes a number and returns correctly formatted string. Be aware that because how Javascript handles decimal numbers, you are not able to represent small fractions in their decimal form. Therefore it has to be represented as a string or some kind of object.

/**
 * Checks if number is in exponential format (eg: 1e-8 for 0.00000001).
 * If it does not, original number is returned.
 * If it does it converts it to string representation of that number
 * which forces it to format 0.00000001
 */
export function convertExponentialToDecimal(exponentialNumber: number): number|string {
  // sanity check - is it exponential number
  const str = exponentialNumber.toString();
  if (str.indexOf('e') !== -1) {
    const exponent = parseInt(str.split('-')[1], 10);
    // Unfortunately I can not return 1e-8 as 0.00000001, because even if I call parseFloat() on it,
    // it will still return the exponential representation
    // So I have to use .toFixed()
    const result = exponentialNumber.toFixed(exponent);
    return result;
  } else {
    return exponentialNumber;
  }

If it helped you solve your problem, let me know in comments below

Decimal number is not a number!

You have an input field, where user can enter decimal values. Problem is, that some cultures use decimal point “.” and some uses decimal comma “,”. So what is a valid number in one culture, is not a number at all in a different culture. You can see the full list of differences on wikipedia.

There is no built-in validator for numbers in Angular, so we have built our own like this:

import { AbstractControl, ValidatorFn } from '@angular/forms';

export class NumberValidators {

  static isNumber(): ValidatorFn {
    return (c: AbstractControl): { [key: string]: boolean } | null => {
      if (isNaN(c.value)) {
        return { 'isNumber': true };
      }
      return null;
    };
  }
}

Can you see, where the problem is? It uses isNaN() to check if given value is number or not. But isNaN() does not accept number with decimal comma as valid.

isNaN('10.0');
// logs 'false'
isNaN('10,0')
// logs 'true'

As usual there are many ways how to do validate numeric values, I could rewrite the validator to allow both variants, but then I would have to handle both variants on server. Which I do not want. So the solution is to use a directive, which will automagically convert decimal comma to decimal point before validation kicks in.

The directive works like this:

  1. Subscribe to keydown and paste events
  2. Check if the new value contains decimal comma
  3. If yes, suppress the event and
  4. Replace comma with point
  5. Then triggers native DOM event again

I use customRenderer to dispatch native DOM events, which naturally bubbles up the DOM tree. Angular then reacts to that event and validation kicks in. Here is the source code.

import { AfterViewInit, Directive, ElementRef, OnDestroy } from '@angular/core';
import { CustomRenderer } from 'app/utils/custom-renderer';
import { replaceAll } from 'app/utils/replace-string';
import { fromEvent } from 'rxjs/observable/fromEvent';

/**
 * Detects when user press key "decimal point"
 * which depends on user's locale.
 * In some locales it prints decimal point "."
 * and in other it prints decimal comma ","
 * This directive forces that decimal comma is always translated to decimal point.
 */
// Full list of countries using either decimal point or comma can be found here https://en.wikipedia.org/wiki/Decimal_separator
@Directive({
  selector: '[decimalPoint]'
})
export class DecimalPointDirective implements AfterViewInit, OnDestroy {

  private input: HTMLInputElement;
  private alive = true;

  constructor(
    private el: ElementRef,
    private customRenderer: CustomRenderer
  ) {
  }

  ngAfterViewInit() {
    this.input = this.el.nativeElement;

    fromEvent(this.input, 'keydown')
    .takeWhile(() => this.alive)
    .subscribe((event: KeyboardEvent) => {
      if (event.key === ',') {
        event.preventDefault();
        event.stopPropagation();
        this.input.value = this.input.value + '.';
        this.triggerEvent('input');
      }
    });

    fromEvent(this.input, 'paste')
    .takeWhile(() => this.alive)
    .subscribe((event: Event) => {
      const pastedValue = <string>(<any>event).clipboardData.getData('text/plain');

      if (pastedValue.indexOf(',')) {
        event.preventDefault();
        event.stopPropagation();
        const fixedValue = replaceAll(pastedValue, ',', '.');
        this.input.value = fixedValue;
        this.triggerEvent('input');
      }
    });
  }

  // Trigger dom native event, which bubbles up as usual.
  private triggerEvent(eventName: string) {
    this.customRenderer.invokeElementMethod(this.el, 'dispatchEvent', [new CustomEvent(eventName, {bubbles: true})]);
  }

  ngOnDestroy() {
    this.alive = false;
  }
}

Example usage

<input type="number" formControlName="amount" decimalPoint>

Angular – .map is not a function

I’ve encountered really weird error. After I’ve removed clearly unused code, our Angular app stopped working. It did however pass lint tests and also the production build passed without problems. The error occurred only during runtime. The error said something like “map is not a function at blablabla” and on the first glance it was totally unrelated to the code where it occurred.

After long digging and googling I found the root cause. Somewhere in our app we used import ‘rxjs/RX’, which imported the whole RxJS library which is large. It has about 50+ operators. And because it was included in the bundle accidentally, the app magically worked. We never had to worry about manually importing the RxJs operators. That is until we fixed the unused and unrelated import, hehe.

Conclusion

So the conclusion is, never import rxjs/RX because it will increase your bundle code size. But include only the specific operators which you really use. You can do it in the main module (usually called the app.module.ts) or specifically in those files, where they are actually used.

 

totally unrelated image :)

My import statement in app.module.ts now looks like this

import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/bufferWhen';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/publish';
import 'rxjs/add/operator/publishLast';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/takeWhile';
import 'rxjs/add/operator/timeout';
import 'rxjs/add/operator/withLatestFrom';

Which is much better than this

// never ever!
import 'rxjs/Rx';

Invoke native element method with Angular 6

The useful method invokeElementMethod() has been removed from the new Renderer v3 in the upcoming version 6 of Angular. What does it mean? For example it is now harder to invoke focus() on input field or create custom native event, which bubbles up. The motivation of the Angular team is that this method has not been used internally, therefore they have removed it. I don’t think this was the best solution, because this method has been used by many.

So now when you need to focus some input field, you need to write up your own renderer service. It makes the things more complicated without obvious benefit. Or am I missing something?

You can create your own injectable renderer service like this

import { Injectable, PLATFORM_ID, Inject, ElementRef } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Injectable()
// Based on https://github.com/angular/angular/issues/13818#issuecomment-372276031
// and source code of the old Renderer V1
export class CustomRenderer {

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object
  ) { }

  invokeElementMethod(eleRef: ElementRef, method: string, args: any[]) {
    if (isPlatformBrowser(this.platformId)) {
      let element = eleRef.nativeElement;
      element[method].apply(element, args);
    }
  }
}

You can use it to create native DOM elements which will bubble-up. That is sometimes easier, than having chain of EventEmmiters across a tree of components. You can create custom event like this

@Component({
  selector: 'app-demo',
  templateUrl: './demo.component.html',
  styleUrls: ['./demo.component.scss']
})
export class DemoComponent implements OnInit {
 constructor(private elRef: ElementRef, private customRenderer: CustomRenderer) { }

 this.customRenderer.invokeElementMethod(
  this.elRef, 
  'dispatchEvent', 
  [new CustomEvent('nameOfCustomEvent', {bubbles: true})]
 );
}

Hope this helps someone

Frozen global object

If your app uses global object as a config, you should make sure, that this object is truly readonly. So that an attacker can not modify it in dev console.

This is how to do it.

// example of frozen global object
// freeze the config object properties
const config = Object.freeze({
  httpApi: 'http://127.0.0.1:20003/v1',
  wsApi: 'ws://127.0.0.1:3000',
  enableReduxDevTools: true,
  minLegalAge: 18 // in years
});

// make sure that the config object can not be replaced
Object.defineProperty(window, "config", { 
  value: config,
  configurable: false,
  writable: false 
});

PS: I assume that “window” is sufficient as a global object. If your app runs in different environment then browser, then you will need to modify the code accordingly.

Custom command line shortcuts with Doskey

I’ve learned this nice trick from Petr Horáček aka Besir while working on Nakamotox. So the credit goes to him.

You can define custom commands via doskey (only on Windows) and then invoke them via command line. It is quite powerfull, because you can define alias for command with predefined attributes, so it save you lot of repetitive typing.

Steps:

  1. Open any text editor and define your shortcut commands
@echo off
REM example shortcut for docker-compose
doskey dcdu=docker-compose -f docker-compose-dev.yml up
doskey dcdd=docker-compose -f docker-compose-dev.yml down
  1. Add path to file with commands to registry:
  2. open regedit
  3. go to \HKEY_CURRENT_USER\Software\Microsoft\Command Processor
  4. define AutoRun
  5. enter path to file with doskey commands

Email validator for Angular

Angular has built in email validator, but it is very benevolent one. It allows for single letter domains, therefore email a@b is considered valid. Their motivation is that when used in intranet it can be perfectly valid email, because you can have custom domain names. But it does not make much sense in the broader internet. I would prefer if the built in validator would be stricter, or that there would be two validators, something like emailValidator and intranetEmailValidator.

Here is my solution, hope it will help someone

import {FormControl} from '@angular/forms';

// copied from http://emailregex.com/
export const emailRegexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

/**
 *
 * @param controlName
 * @returns {(control:FormControl)=>({validateEmail: boolean}|null)}
 */
export function validateEmail(controlName: string) {
  return (control: FormControl) => {
    const value = control.value;

    if (value != "" && (value.length <= 5 || !emailRegexp.test(value))) {
      return { "incorrectMailFormat": true };
    }

    return null;
  };
}

Example usage in component

ngOnInit() {
    this.model.email = this.currentEmail;

    this.changeEmailForm = this.formBuilder.group({
      email: [this.model.email, [
        Validators.required,
        validateEmail('email')
      ]]
    });
  }

Print document from cross-origin iframe

When you need to show content from some other site, such as Google Docs, you can easily embed it inside an iframe. Google Docs even generates it for you. But what if you need to print that document via some button click? You can not do that, because the cross-origin policy does not allow it.

You have basically two options.

A) Create some proxy function on your server. But that is only available when you actually have a server and not just some hosted WordPress.

B) Create proxy iframe, which you are actually allowed to print, because there is no cross-origin involved. Then load the desired iframe inside the proxy iframe. Attach onload=”print()” on the target iframe, so the print starts automatically once it is loaded.

Working solution is below, have a nice day 🙂 Let me know, if it helped you.

Comments and suggestions for improvements are welcome. Hints: The iframe is not destroyed after print() is invoked, which might lead to memory leaks. Second issue is that you might need to calculate height of the iframe content, so that it is correctly printed. But if you can safely guess it, then you are fine.

  /**
   * Load iframe from cross-origin via proxy iframe
   * and then invokes the print dialog.
   * It is not possible to call window.print() on the target iframe directly
   * because of cross-origin policy.
   * 
   * Downside is that the iframe stays loaded. 
   */
  function printIframe(url) {
    var proxyIframe = document.createElement('iframe');
    var body = document.getElementsByTagName('body')[0];
    body.appendChild(proxyIframe);
    proxyIframe.style.width = '100%';
    proxyIframe.style.height = '100%';
    proxyIframe.style.display = 'none';

    var contentWindow = proxyIframe.contentWindow;
    contentWindow.document.open();
    // Set dimensions according to your needs.
    // You may need to calculate the dynamically after the content has loaded
    contentWindow.document.write('<iframe src="' + url + '" onload="print();" width="1000" height="1800" frameborder="0" marginheight="0" marginwidth="0">');
    contentWindow.document.close();
  }

Workaround for ExpressionChangedAfterItHasBeenCheckedError in async pipe

Imagine that you have this piece of code, where userInfo$ is Observable and it value is not yet emitted.

<app-change-email [currentEmail]="(userInfo$ | async).user.email"></app-change-email>

The code works, but it will report error ExpressionChangedAfterItHasBeenCheckedError to the console (only in dev mode). That is because the component has received the value, but the value has changed later. Angular does not like it. I googled for solutions, but could not use them, or it would be too complicated in given setup.

Workaround is easy

<ng-template #loading>
    <div id="loading"></div>
</ng-template>

<ng-container *ngIf="userInfo$ | async; let userInfoState; else loading">
    <app-change-email [currentEmail]="userInfoState.user.email"></app-change-email>
</ng-container>

Angular custom event bubbling

Custom events in Angular by default do not bubble up. It is because they are not really events, they are based on EventEmitter, which is similar to pub/sub mechanism. You can read lengthy discussion about it on Angular’s Github. Unfortunately the examples provided by Angular team often mention $event as named parameter in the event listener, which only adds to the confusion. There are valid use cases when you really need the custom events to bubble up.

Luckily, you can still create DOM-native custom events, and they will bubble up as expected.

import { Component, OnInit, Input, ElementRef, Renderer } from '@angular/core';

@Component({
  selector: 'app-bubble',
  templateUrl: './bubble.component.html',
  styleUrls: ['./bubble.component.scss']
})
export class BubbleComponent implements OnInit {

  /**
   * Optional.
   * Defaults to 'myCustomEvent'
   */
  @Input()
  customEventName: string;

  private _defaultEventName: string = 'myCustomEvent';

  /**
   * Invokes DOM-native custom event, which bubbles up.
   * That solves the issue with EventEmmiter-based events, which are not really events and do not bubble up.
   */
  emitEvent() {
    const eventName = this.customEventName || this._defaultEventName;

    this.renderer.invokeElementMethod(
      this.elRef.nativeElement,
      'dispatchEvent',
      [new CustomEvent(eventName, {bubbles: true})]
    );
  }

  constructor(private elRef: ElementRef, private renderer: Renderer) { }

  ngOnInit() {
  }

}

And the html template

<button (click)="emitEvent()">Emit custom event which bubbles up</button>

Then somewhere up the DOM tree, you listen for the event like this

<app-my-listener (myCustomEvent)="doSomething($event)"></app-my-listener>

More reading:

https://www.radzen.com/blog/angular-event-bubbling/