Angular 2 in Action

Solving Real life challenges with angular 2
@ Algotec

Medical Imaging Workstation

Carestream Vue PACS

  • 15 years of development
  • Assembly, C, C++, C#, etc..
  • Patient/Study list, Numeros Image viewers, Medical applications, reports ....
  • Client/server - PACS (server) has data, client does all the processing work


Win32  ---->  Web 

  • Ecosystem (devs, libs...)
  • Easy deployment
  • Easy to maintain up to date
  • Easy to make cross platform 
  • Easy on hardware demands
  • Difficult to keep performance
  • Difficult to access hardware



Hardware access

  • Multiple monitors
  • Proprietary professional hardware
  • Image/Audio/video hardware acceleration w/ custom code - 3D modeling, voice recognition etc..

Angular 2

  • DI
  • Components
  • Observable streams
  • Great performance
  • Testing
  • Typescript
  • Still evolving and changing
  • Ecosystem not as rich as Angular 1.x or React etc..

High level Architecture -

Choose not to choose. Use DI

  • Desktop (Nw.js) and web versions
    • Desktop will be able to automatically use all monitors, access scanner hardware etc.
    • Web uses the same code, with some limitations.
    • Node.js thread/Shared Worker for server API code to allow multiple tabs with one connection to server
  • Dual client rendering pipes - WebGL and native JS (using ASM.js) as well as server rendering- use decided at runtime
    • Main App is in UI thread to allow WebGL access
    • Additional web workers do processing tasks.

Everything is a stream

Great for complex user interaction




mouseDown.flatMap((ev) => {  
  return =>{
    return {
      x: ev.clientX,
      y: ev.clientY


Great For async failure managment

ServerConnectionObservable.retryWhen(function (attempts) {
      return Rx.Observable.range(1, 3)
                .zip(attempts, (i) => i)
                        console.log("delay retry by " + i + " second(s)");
                        return Rx.Observable.timer(i * 1000);

State Managment

  • Handle async actions
  • Great error handling.
  • Great performance.
  • Debuggability & predictability.


State Managment


  • Components are mere event emitters & state presentores
  • Containers connect to data , catch component events, and pass them on to action producers.
  • Producers create actions and exposes store data as observables
  • Handlers deal with action, do async tasks which produce one or more sub actions to dispatcher when state needs to change
  • Reducers change state (synchronously) 

NgRx+  ---> Flux on steroids

Demo - one simple button click...


import {Input, Output, Component, EventEmitter, ChangeDetectionStrategy} from "@angular/core";
import {ButtonModel} from "./../models/button.model.ts";
	selector: 'alg-button',
	changeDetection: ChangeDetectionStrategy.OnPush,
			<button *ngIf="button.isIcon" md-icon-button  (click)="onClick()" [disabled]="button.disabled" 
				<i [ngClass]="button.getIconsClassList()">{{button.iconText}}</i>
			<button *ngIf="!button.isIcon" md-button  (click)="onClick()" [disabled]="button.disabled" 
export class ButtonComponent {

	buttonClick:EventEmitter<any> = new EventEmitter(false); //<buttonModel>

	onClick() {
import {Component, Output, Input, EventEmitter, ChangeDetectionStrategy} from "@angular/core";
import {IButtonsMap, ButtonModel} from "../models/button.model.ts";
import {ButtonComponent} from "./button.component";
import {MdToolbar} from "@angular2-material/toolbar";
import {MdButton} from "@angular2-material/button";

	selector: 'at-ribbon',
	directives: [ButtonComponent, MdToolbar, MdButton],
	changeDetection: ChangeDetectionStrategy.OnPush,
	template: `<div class="btn-group btn-group-lg" role="group">
		     <alg-button *ngFor="let button of buttons" (buttonClick)="buttonClick.emit($event)" [button]="button[1]"></alg-button>						
 // the default iterator of immutableJS  will return entries style [key,value] so [1] is the value,
 // it could have been nice to iterate over .values() or to deconstruct but angular does not support this for now
export class RibbonComponent {
	buttonClick:EventEmitter<any> = new EventEmitter(false); //<ButtonModel>
//custom event do not bubble:;
 when this is fixed, we can remove the chaining of buttonClick emitters
import {Component, Input, ChangeDetectionStrategy} from "@angular/core";
import {RibbonComponent} from "../components/ribbon.component";
import {RibbonProducer} from "../producer/ribbon.producer.ts";
import {RIBBON_PROVIDERS} from "../ribbon.providers";

	selector: 'alg-ribbonContainer',
	directives: [RibbonComponent],
	providers : RIBBON_PROVIDERS,
	changeDetection: ChangeDetectionStrategy.OnPush,
	template: `<at-ribbon [buttons]="ribbon.buttons$ | async" [panelID]="panelID" (buttonClick)="internalButtonClick($event)"></at-ribbon>`
export class RibbonContainer {

	public constructor(private ribbon:RibbonProducer) {

	internalButtonClick(buttonClickEvent) {
		this.ribbon.ribbonButtonClick(this.panelID, buttonClickEvent);
import {Store, Dispatcher} from "@ngrx/store";
import {IButtonsMap, ButtonModel} from "./../models/button.model.ts";
import {ribbonActions} from "./../actions/ribbon.actions.ts";
import {Injectable, Inject, Injector} from "@angular/core";
import {Observable} from "rxjs/Observable";
import {RIBBON_HANDLERS} from "../handlers/ribbon.handlers";
import {BaseProducer} from "../../../common/producers/base.producer";

export class RibbonProducer extends BaseProducer {

	constructor(store:Store<any>, @Inject(RIBBON_HANDLERS) ribbonHandlers, injector:Injector, dispatcher:Dispatcher<any>) {
		super(injector, dispatcher, ribbonHandlers);
		this.buttons$ =<any>('ribbon').map(ribbonState => ribbonState.buttons);

	ribbonButtonClick(panelId:string, button:ButtonModel) {
			type: ribbonActions.RIBBON_BUTTON_CLICKED,
			payload: {panelId, button}

import {Dispatcher, Action} from "@ngrx/store";
import {Injectable, ReflectiveInjector, Provider} from "@angular/core";
import {IHandler} from "../models/handler.interface";
import {Injector, provide, ResolvedReflectiveProvider} from "@angular/core";

export class BaseProducer {
	private handlers:Array<IHandler>;
	private injector:ReflectiveInjector;

	constructor(injector:Injector, protected dispatcher:Dispatcher<any>, handlersProvidersArray:Provider[]) {
		this.injector = ReflectiveInjector.resolveAndCreate(handlersProvidersArray, injector);
		this.handlers = this.$setHandlers(handlersProvidersArray);


	private $setHandlers(providersArray:Provider[]):Array<any> {
		return => this.injector.resolveAndInstantiate(handlerProvider)[0]);

	protected $handlersToDispatch(action:Action):void {
		let handlersActingOnAction = this.handlers
			.map(handler => handler.handleCommand(action))
			.filter(v => typeof v !== 'undefined');
		if (handlersActingOnAction.length) {
			handlersActingOnAction.forEach(thunk => this.dispatcher.dispatch(thunk));
		} else {


import {ribbonActions} from "../actions/ribbon.actions";
import {Dispatcher, Action} from "@ngrx/store";
import {Injectable} from "@angular/core";
import {Observable} from "rxjs/Observable";
import {just} from "../../../common/helpers/observable.helpers";
import {transformButtonToUiCommandModel} from "../models/button.model";
import {ServerApi} from "../../../common/services/server.api";
import {IHandler} from "../../../common/models/handler.interface";

export class RibbonButtonClickHandler implements IHandler {
constructor(private dispatcher:Dispatcher<Action>, private serverApi:ServerApi) {

handleCommand(action:Action) {
	if (action.type === ribbonActions.RIBBON_BUTTON_CLICKED) {
		return (dispatch, getState) => {
                            .map(response => ({type:(action.payload.sentToServer) ?
		  	payload: Object.assign({}, action.payload, {response})
			.catch(err => Observable.empty())
			.subscribe(resAction => dispatch(resAction));

	private passToServer(action) {
		if (typeof action.payload.button.clientActionOnly === 'undefined') {
			action.payload.sentToServer = true;
			return this.serverApi.sendCommand('ui', transformButtonToUiCommandModel(action.payload.button))
				.catch(e => {
						type   : ribbonActions.RIBBON_BUTTON_CLICK_NOT_HANDLED,
						payload: action.payload
					return Observable.throw(e);
		} else {
			return just(null);
import * as Immutable from "immutable";
import {Action, Reducer} from "@ngrx/store";
import {ribbonActions} from "../actions/ribbon.actions";
import {ribbonInitialState} from "./ribbon.initialstate";
import {RibbonState} from "./ribbon.initialstate";
import {ButtonModel} from "../models/button.model";
import {ActionType} from "../models/button-defs.ts";

export function RibbonReducer(state = ribbonInitialState, action:Action) {
	let newState;
	switch (action.type) {
		case ribbonActions.RIBBON_BUTTON_CLICKED:
			newState = state.withMutations(mState => {
				let button:ButtonModel = mState.getIn(['buttons',]);
				button.disabled = true;
				button.classList = button.classList.add('active');
			newState = state.withMutations(mState => {
				let button:ButtonModel = mState.getIn(['buttons',]);
				button.disabled = false;
				button.classList = button.classList.remove('active');
			newState = state;
	return newState;

Questions? Comments?


Angular 2 in Action

By Nadav SInai

Angular 2 in Action

Solving real life challenges with Angular 2. A short look at the development of a cutting edge medical imaging application @Algotec

  • 953
Loading comments...

More from Nadav SInai