We were recently asked about options for mixing Dojo widgets and Angular 2 components into the same application:

  • Is it possible to render an Angular 2 component and Dojo widgets on the same page?
  • Are there any special configuration settings needed?
  • What’s the best way for Angular 2 and Dojo to communicate and/or send messages?
  • What kind of complex challenges and communication issues should we be aware of?
  • For an application that assembles many different components, if some of these are are Dojo widgets and some are Angular 2 components, how can we get them to play nicely together?

It certainly is possible to render Angular 2 components and Dojo widgets on the same page. However, this could lead to more complex challenges regarding the architecture and data communication that would require deeper investigation to determine, based on the specific approach to building apps with Angular 2 and Dojo. We created two examples that demonstrate how Angular 2 and dgrid can be rendered within the same page. For both examples described below, Angular 2 (UMD distribution) is loaded using script tags and the custom components are loaded using the Dojo loader.

Dojo widgets within an Angular 2 app

One approach is to include a Dojo widget and wrap it within an Angular 2 component. This approach is favorable if you have an Angular 2 application, and want to include a few Dojo widgets, and effectively manage the communication between the Angular application and the Dojo widgets.

For example, we may wrap a dgrid instance as an Angular 2 component and manage the events and widget lifecycle of dgrid within Angular 2:

define([
	'dgrid/OnDemandGrid',
	'dstore/Memory'
],function(
	OnDemandGrid,
	Memory
) {
	return ng.core.Component({
		selector: 'my-app',
		template: `<div><input type="text" value="" placeholder="enter search term" /> <button value="">Search</button><div></div></div>`,
		queries: { dgridNodeRef: new ng.core.ViewChild('grid') }
	}).Class({
		constructor: function() { },
		ngAfterViewInit: function() {
			var names = [ 'sasquatch', 'foo', 'bar', 'zaphod', 'beeblebrox' ];
			function createItems () {
				var items = [];
				var i;
				for (var i = 1; i <= 200; i++) {
					items.push({ id: i, name: names[i % 5] });
				}
				return items;
			}
			this.store = new Memory({ data: createItems() });
			this.dGridInstance = new OnDemandGrid({
				columns: { id: 'id', name: 'name' },
				collection: this.store
			},
			this.dgridNodeRef.nativeElement);
		},
		ngOnDestroy: function() {
			this.dGridInstance.destroy();
		},
		filter: function(value) {
			var filter = value ? { name: value } : {};
			this.dGridInstance.set('collection', this.store.filter(filter));
		}
	});
});

See this example in action

Dojo application, Angular 2, and RxJS observables

With Dojo 2, we are leveraging RxJS observables, which follow the ES proposal for Observable. So another approach to solve this problem would be to leverage RxJS observables to provide the communication between separately managed Dojo and Angular 2 components rendered on the same page.

We begin this example by defining an Angular 2 AppComponent:

define(['example2/components/FilterComponent', 'example2/events/buttonEvent'], function (FilterComponent, buttonEvent) {
	var Component = ng.core.Component({
		selector: 'my-app',
		template: '<h3>Angular Component controlled by Dojo component clicked {{count}} {{label}}</h3>',
		directives: [FilterComponent]
	})
	.Class({
		constructor: [ng.core.ApplicationRef, function Component(_appRef) {
			this.count = 0;
			this.label = 'times';
			buttonEvent.subject.subscribe(function() {
				this.count++;
				this.label = this.count === 1 ? 'time' : 'times';
				_appRef.tick();
			}.bind(this));
		}]
	});
	return Component;
});

and an Angular 2 component for filtering:

define(['example2/events/searchEvent'], function (searchEvent) {
	return ng.core.Component({
		selector: 'filter',
		template:
			`
				<input type="text" value="" placeholder="enter search term" />
				<button value="">Search</button>
			`
	})
	.Class({
		constructor: function() {
		},
		search: function(value) {
			searchEvent.emit(value);
		}
	});
});

Then we define our dgrid instance:

define([
	'dojo/_base/declare',
	'dgrid/OnDemandGrid',
	'dstore/Memory',
	'example2/events/searchEvent'
], function (
	declare,
	OnDemandGrid,
	Memory,
	searchEvent
) {

	var names = [ 'sasquatch', 'foo', 'bar', 'zaphod', 'beeblebrox' ];

	function createItems () {
		var items = [];
		var i;
		for (i = 1; i <= 200; i++) {
			items.push({
				id: i,
				name: names[i % 5]
			});
		}
		return items;
	}

	var store = new Memory({
		data: createItems()
	});

	var grid = new OnDemandGrid({
		columns: { id: 'id', name: 'name' },
		collection: store
	}, 'grid');

	searchEvent.subject.subscribe(function(value) {
		var filter = value ? { name: value } : {};
		grid.set('collection', store.filter(filter));
	});
});

Then we connect this together through events, using RxJS Observables to connect to dgrid’s emit API:

define(function() {
	var buttonSubject = new Rx.Subject();
	var buttonObservable = buttonSubject.asObservable();

	return {
		subject: buttonObservable,
		emit: function () {
			buttonSubject.next();
		}
	};
});

define(function() {
	var searchSubject = new Rx.Subject();
	var searchObservable = searchSubject.asObservable();

	return {
		subject: searchObservable,
		emit: function (value) {
			searchSubject.next(value);
		}
	};
});

See this example in action

This also shows how easy it is to get the powerful dgrid to work within an Angular 2 application. While we’re busy working to make Dojo 2 and dgrid 2, these examples show that through a relatively small amount of code, it is possible to get two very different APIs (Angular 2 and Dojo 1) to communicate effectively.