Shuhei Kagawa

Angular 2 with Babel

@2016-05-08 15:47 - JavaScript, Angular 2, Babel

Although Angular 2's primary language is apparently TypeScript, many people want to use Babel as shown in a survey.

However, The official documentation targets only TypeScript and ES5. In addition, many pages are not yet available for ES5. That is because Angular 2 relies heavily on cutting-edge ES7 decorators and TypeScript's type annotations for annotating components and services.

To fill the gap, you can use babel-preset-angular2 that supports all the decorators and annotations available in TypeScript. With the preset, you can follow the official documentation for TypeScript to learn Angular 2 itself.

How to use it

npm install -D babel-preset-es2015 babel-preset-angular2

Add presets to .babelrc. Note that the presets' order is important.

{
  "presets": ["es2015", "angular2"]
}

See babel-angular2-app for more complete example.

Supported annotations

Name Example EcmaScript TypeScript Babel* Babel + angular2
Class decorator @Component() Stage 1 Yes Yes Yes
Property decorator @Input() Stage 1 Yes Partial* Yes
Parameter decorator @Optional() Stage 0 Yes No Yes
Type annotation foo: Foo - Yes No Yes

"Babel*" above means Babel with the following official plugins:

Property decorator in Babel is marked "Partial" because babel-plugin-transform-decorators-legacy ignores class properties without initializers.

You can emulate parameter decorator and type annotation with plain ES2015 like the following but it's a little bit counterintuitive.

class HelloComponent {
  constructor(foo: Foo, @Optional() bar: Bar) {
    // Do something with foo and bar.
  }
}

class HelloComponent {
  static get parameters() {
    return [[Foo], [Bar, Optional()]];
  }

  constructor(foo, bar) {
    // Do something with foo and bar.
  }
}

Polyfills

Angular 2 beta versions had polyfill bundles but RC versions don't. But never mind. We can just import them before bootstrapping our app.

npm install -S babel-polyfill zone.js

src/index.js

// Import polyfills.
import 'babel-polyfill';
import 'zone.js/dist/zone.js';

// Bootstrap app!
import {provide} from '@angular/core';
import {bootstrap} from '@angular/platform-browser-dynamic';
import {ROUTER_PROVIDERS} from '@angular/router';
import {LocationStrategy, HashLocationStrategy} from '@angular/common';

import {HelloApp} from './app';

bootstrap(HelloApp, [
  ROUTER_PROVIDERS,
  provide(LocationStrategy, { useClass: HashLocationStrategy })
]).catch(err => console.error(err));

Note that we can use babel-polyfill that includes core-js instead of es6-shim and reflect-metadata. According to use core-js instead of es6-shim, we can use whatever ES6 + ES7 polyfill we like.

Module resolution

You can use any module resolver as long as it works with Babel. I'll pick Browserify here for its simplicity.

npm install -D browserify babelify

Add a build script to your package.json assuming that your bootstrap script locates at src/index.js.

{
  "scripts": {
    "build": "browserify -t babelify src/index > public/bundle.js"
  }
}
npm run build

Isn't this simple? babelify automatically finds your .babelrc and uses the presets specified above.

Of course you can use other module resolvers like Webpack or SystemJS.

Offline compilation

This is not yet available for Babel. Not completed even for TypeScript.

The compiler_cli seems to be deeply integrated with TypeScript compiler. It statically collects metadata from the source and feed it to the compiler. I believe that it is achievable with Babel to do the same thing.

I'm thinking of working on it once the TypeScript version is published and the compiler API becomes more stable.

Conclusion

I've presented how to use TypeScript-specific annotations in Babel. You can enjoy Angular 2 with your favorite transpiler.

See babel-angular2-app for more complete example.

Incremental search with RxJS switchMap

@2016-05-02 02:18 - JavaScript, RxJS

RxJS leads us to better design separating data flow and side-effects. In addition, it provides powerful functionalities that sophisticate the outcome application. My favorite is switchMap of RxJS 5, which is equivalent to flatMapLatest in RxJS 4.

switchMap

switchMap(func) is equivalent to map(func).switch(). It keeps subscribing latest observable at the moment and unsubscribing outdated ones so that it only streams events from latest observable at the moment. Take a look at the marble chart for switch. It illustrates the behavior well.

switchMap is convenient for properly implementing incremental search. Incremental search makes multiple requests to a server. The server can respond in a different order from requests'. Because of the order, a naive implementation may show a wrong result. However, you can effortlessly avoid the caveat if you use switchMap.

Here is an example. Type fast in the text fields. Without switchMap sometimes shows a wrong result while With switchMap always works fine.

JS Bin on jsbin.com

search function mocks an AJAX request. It returns a Promise that resolves after a random delay.

function search(keyword) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Result of ' + keyword);
    }, Math.random() * 1000);
  });
}

A naive implementation always shows the last response at the time. A wrong result is shown if responses come in a different order from requests'. We could add some debouncing to decrease the chance of the wrong order but it still may happen when response time is longer than the debounce time.

const keyword = document.getElementById('keyword-without');
const result = document.getElementById('result-without');

keyword.addEventListener('keyup', e => {
  const value = e.target.value;
  search(value)
    .then(data => result.textContent = data);
});

switchMap guarantees that the last keyword's result is finally shown.

const keyword = document.getElementById('keyword-with');
const result = document.getElementById('result-with');

const keyword$ = Rx.Observable.fromEvent(keyword, 'keyup')
  .map(e => e.target.value);
keyword$
  .switchMap(search)
  .subscribe(data => result.textContent = data);

Clean up before exiting in Haskell

@2016-04-06 00:32 - Haskell

Once upon a time (or a several days ago), I was reading Programming in Haskell. When I ran 9.7's Game of Life, which shows Game of Life animation on the terminal, the terminal's cursor was flickering and annoying. So I tried to hide it when starting and show when exiting.

import System.Process (system)

main :: IO ()
main = do
  -- Hide the cursor
  system "tput civis"
  -- Show the Game of Life
  life glider
  -- Show the cursor (but the code does not reach here!)
  system "tput cvvis"
  return ()

life :: Board -> IO ()
glider :: Board

But the code does not reach the line that shows the cursor because life is a infinite loop. If I quit the program with Ctrl+C, the cursor remains hidden.

So I wrote a function that loops a -> IO a until interrupted by a signal, referring to unix - Killing a Haskell binary - Stack Overflow. It manages a state of whether the program was interrupted in a MVar and stops the loop when interrupted.

import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, tryTakeMVar)
import System.Posix.Signals (Handler, Handler(CatchOnce), installHandler, sigINT, sigTERM)

loopUntilInterruption :: (a -> IO a) -> a -> IO ()
loopUntilInterruption p init = do
  v <- newEmptyMVar
  installHandler sigINT (handler v) Nothing
  installHandler sigTERM (handler v) Nothing
  loop v p init

handler :: MVar () -> Handler
handler v = CatchOnce $ putMVar v ()

loop :: MVar () -> (a -> IO a) -> a -> IO ()
loop v p prev = do
  x <- p prev
  val <- tryTakeMVar v
  case val of
    Just _ -> return ()
    Nothing -> loop v p x >> return ()

In the Game of Life, I changed the type of life so that it returns the result of its previous result and loop with loop. Now the clean up code will be called when interrupted by a signal.

import System.Process (system)

main :: IO ()
main = do
  -- Hide the cursor
  system "tput civis"
  -- Loop until interrupted
  loopUntilInterruption life glider
  -- Show the cursor (the code will reach here now!)
  system "tput cvvis"
  return ()

life :: Board -> IO Board
glider :: Board

And they lived happily ever after.