Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal
In this series of articles, I’m going to reveal to you how does it even possible — execute Angular applications across different environments. Also, we’ll learn how to build custom Angular platform which renders applications inside the system’s terminal using ASCII graphics.

The Angular framework was designed with flexibility in mind. That approach allows Angular applications to be executed across different environments — browser, server, web-worker, and even mobile devices are possible.
Articles:
- Angular Platforms in depth. Part 1. What are Angular Platforms?
- Angular Platforms in depth. Part 2. Application bootstrap process
- Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal

That article is the last one in the Angular platforms in-depth series. During the article, I’m going to guide you through the custom platform creation process. But before we start, please, make sure you understand how Angular platforms work or just check previous articles from the series.
As I stated in the first article of the series, Angular is allowed to be executed across different environments because of abstraction. A pretty big part of the Angular is declared as abstract classes and when we’re using different platforms those platforms provide its own implementations for those abstract classes. That’s why to create a terminal platform we just need to provide a number of implementations of services for Angular.
Table of contents
- Renderer
- Sanitizer
- Error handling
- Terminal module
- Platform terminal
- Building terminal application
Renderer
Let’s start with the most important part of the platform terminal — renderer. Angular utilizes Renderer
abstraction to perform application rendering in an environment-agnostic way. The renderer is just an abstract class, so we’re going to create an implementation which will render applications inside the system terminal using ASCII graphics.
But wait, how will we render applications inside the system’s terminal? I think the easiest way is to find an appropriate library which is able to create some widgets in terminal using ASCII graphics. I decided to use blessed
— a curses-like library with a high-level terminal interface API for node.js. First of all, let’s install the library with appropriate typings:
npm install blessed @types/blessed
As I decided to use a specific library for UI rendering the only thing I need to do here is to build some kind of adapter. We need to find a way to match Angular renderer’s API with the API of the blessed library.
Well, here is a simplified declaration of the Angular Renderer:
export abstract class Renderer2 {
abstract createElement(name: string, namespace?: string|null): any;
abstract createText(value: string): any;
abstract appendChild(parent: any, newChild: any): void;
abstract addClass(el: any, name: string): void;
abstract removeClass(el: any, name: string): void;
// ...
}
Its responsibility is to create and remove elements, add classes and attributes, register event listeners and so forth.
On the other hand, we have the blessed library with the following interface:
const blessed = require('blessed');
// Create blessed screen
const screen = blessed.screen();
// Create some elements
const box = blessed.box();
const table = blessed.table();
// Add elements on the screen
table.append(box);
screen.append(table);
// Display all changes
screen.render();
As you could notice here, blessed is a plain node.js library, which provides a screen
and a number of components here. Screen
is a kind of browser’s document
. It serves as a root element of the application, also as contains a number of useful API’s.
Screen
Let’s start renderer-blessed integration with screen
. First of all, let’s create a separate Screen
service which will provide a screen
for the renderer.
import { Injectable } from '@angular/core';
import * as blessed from 'blessed';
import { Widgets } from 'blessed';
@Injectable()
export class Screen {
private screen: Widgets.Screen;
constructor() {
this.screen = blessed.screen({ smartCSR: true });
this.setupExitListener();
}
selectRootElement(): Widgets.Screen {
return this.screen;
}
private setupExitListener() {
this.screen.key(['C-c'], () => process.exit(0));
}
}
Here we have a basic Screen
implementation. The service is responsible for creating a blessed screen as it was stated above, also as set up an exit listener. Because we’re going to execute our applications inside the terminal, and it’s a pretty common behavior when terminal applications are closing when pressing control+c
. That’s why we need to listen for that event on the screen and exit process as a reaction. process.exit
is a standard node.js API, which allows a script to exit itself with the appropriate code. 0
means that process just completed without any errors. Also, here we could see that Screen
provides the ability to select a root element for our application through selectRootElement
call.
Elements registry
When we have Screen
and can select the root element it’s a time to deal with the elements creation process. As I stated above, blessed
provides the ability to create elements on the screen through the set of functions exported directly through the blessed
package. Meanwhile, Angular renderer utilizes a single createElement
function. That’s why we need to create some kind of adapter for that element creation logic. For instance, I decided to wrap blessed
elements creation logic in special service — ElementsRegistry
. ElementsRegistry
is responsible for the creation blessed
elements through single createElement
function:
import { Injectable } from '@angular/core';
import * as blessed from 'blessed';
import { Widgets } from 'blessed';
export type ElementFactory = (any) => Widgets.BoxElement;
export const elementsFactory: Map<string, ElementFactory> = new Map()
.set('text', blessed.text)
.set('box', blessed.box)
.set('table', blessed.table)
@Injectable()
export class ElementsRegistry {
createElement(name: string, options: any = {}): Widgets.BoxElement {
let elementFactory: ElementFactory = elementsFactory.get(name);
if (!elementFactory) {
elementFactory = elementsFactory.get('box');
}
return elementFactory({ ...options, screen: this.screen });
}
}
So, as you can notice here, ElementsRegistry
has a single createElement
method, which actually tries to find the requested element in elements map and return an instance of that element. In case of the element wasn’t find ElementsRegistry
will fallback to the box
element, which is analog for div
element in the browser.
On that stage, we have all the required entities to create Angular Renderer which will render applications directly inside the system’s terminal using ASCII graphics.
Renderer
Here is the basic renderer implementation.
export class TerminalRenderer implements Renderer2 {
constructor(private screen: Screen, private elementsRegistry: ElementsRegistry) {
}
createElement(name: string, namespace?: string | null): any {
return this.elementsRegistry.createElement(name);
}
createText(value: string): any {
return this.elementsRegistry.createElement('text', { content: value });
}
selectRootElement(): Widgets.Screen {
return this.screen.selectRootElement();
}
appendChild(parent: Widgets.BlessedElement, newChild: Widgets.BlessedElement): void {
parent.append(newChild);
}
setAttribute(el: Widgets.BlessedElement, name: string, value: string, namespace?: string | null): void {
el[name] = value;
}
setValue(node: Widgets.BlessedElement, value: string): void {
node.setContent(value);
}
}
I’ve decided to implement only the small part of all required methods here, leaving the rest of the implementations for you. So, here we have TerminalRenderer
class which implements Renderer2
interface. It utilizes Screen
and ElementsRegistry
created above to perform elements creation.
But look, TerminalRenderer
is not an Injectable
service here. Angular requires Renderer
to be created through the RendererFactory
. So, let’s add one:
@Injectable()
export class TerminalRendererFactory implements RendererFactory2 {
constructor(private screen: Screen, private elementsRegistry: ElementsRegistry)
createRenderer(): Renderer2 {
return new TerminalRenderer(this.screen, this.elementsRegistry);
}
}
TerminalRendererFactory
here implements RendererFactory2
interface and implements only one method — createRenderer
which actually creates a new instance of TerminalRenderer
with required services.
On that stage, we have full featured Angular TerminalRenderer
which is able to render Angular applications inside the system’s terminal using ASCII graphics. But we still need to add a lot, so, let’s dig deeper ?
Sanitizer
Angular utilizes Sanitizer
abstraction to sanitize potentially dangerous values. It’s required by the Angular to bootstrap the application. As Sanitizer
is declared as an abstract class in the Angular core package, Browser platform provides its own implementation — DomSanitizer
. DomSanitizer
helps to prevent Cross Site Scripting attacks by sanitizing values to be safely used in the DOM.
For instance, when binding a URL in an <a [href]=”someValue”>
hyperlink, someValue
will be sanitized so that an attacker cannot inject e.g. a javascript
: URL that would execute code on the website. In specific situations, it might be necessary to disable sanitization, for example, if the
application genuinely needs to produce a javascript:
style link with a dynamic value in it. Users can bypass security by constructing a value with one of the bypassSecurityTrust...
methods, and then binding to that value from the template.
But inside the terminal we haven’t got DOM, that’s why Cross-Site Scripting attacks can’t be performed. But, if Cross-Site Scripting attacks can’t be performed, then, we don’t need to sanitize any templates values. That’s why we can provide just an empty implementation of Sanitizer
to the Angular:
import { Sanitizer, SecurityContext } from '@angular/core';
export class TerminalSanitizer extends Sanitizer {
sanitize(context: SecurityContext, value: string): string {
return value;
}
}
As you can notice here, that TerminalSanitizer
implementation just does nothing, it just returns accepted value.
Error handling
Every good application has to know how to deal with errors properly. Angular applications are not exclusions. That’s why Angular provides the ability to set up global ErrorHandler
which will react on each unhandled exception in your applications. Angular provides a default implementation for the ErrorHandler
which utilizes a browser console
to log all unhandled exceptions properly. But in it’s not enough for the terminal.
In the terminal, we have an additional issue — if an application throws an exception somewhere, ErrorHandler
will just log it. But the application will remain stuck. In the browser, we could just reload the tab with the application but it’s impossible to do right in the terminal. That’s why we need to provide a custom implementation for the ErrorHandler
which will not only log the exception but also exit the current process:
import { ErrorHandler, Injectable } from '@angular/core';
@Injectable()
export class TerminalErrorHandler implements ErrorHandler {
handleError(error: Error): void {
console.error(error.message, error.stack);
process.exit(1);
}
}
Here is the basic implementation for the ErrorHandler
. It just logs the error in the console and then exits the process with the error code 1
which means that something went wrong during the application execution.
Terminal module
As you remember, each Angular application created with the Angular CLI configured to be executed in the browser by default, that’s why it contains BrowserModule
imported in the AppModule
. BrowserModule
contains a number of browser-specific providers, also as reexports CommonModule
and ApplicationModule
. Those modules contain multiple crucial providers for Angular applications. The terminal platform also requires those providers, that’s why we need to create a custom TerminalModule
which will reexport CommonModule
and ApplicationModule
, also as register part of created above services in the application.
import { CommonModule, ApplicationModule, ErrorHandler, NgModule, RendererFactory2 } from '@angular/core';
import { Screen } from './screen';
import { ElementsRegistry } from './elements-registry';
import { TerminalRendererFactory } from './renderer';
import { TerminalErrorHandler } from './error-handler';
@NgModule({
exports: [CommonModule, ApplicationModule],
providers: [
Screen,
ElementsRegistry,
{ provide: RendererFactory2, useClass: TerminalRendererFactory },
{ provide: ErrorHandler, useClass: TerminalErrorHandler },
],
})
export class TerminalModule {
}
But not all services could be registered through TerminalModule
some of them are required during the bootstrap phase and have to be provided before. The only way to provide services, in that case, is to create a custom platform
Platform terminal
import { COMPILER_OPTIONS, createPlatformFactory, Sanitizer } from '@angular/core';
import { ɵplatformCoreDynamic as platformCoreDynamic } from '@angular/platform-browser-dynamic';
import { DOCUMENT } from '@angular/common';
import { ElementSchemaRegistry } from '@angular/compiler';
import { TerminalSanitizer } from './sanitizer';
const COMMON_PROVIDERS = [
{ provide: DOCUMENT, useValue: {} },
{ provide: Sanitizer, useClass: TerminalSanitizer, deps: [] },
];
export const platformTerminalDynamic = createPlatformFactory(platformCoreDynamic,
'terminalDynamic', COMMON_RPOVIDERS]);
Platform terminal here is created through createPlatformFactory
function which allows us to inherit platformCoreDynamic
providers. Also as add platform terminal specific providers.
On that stage, we’ve done all the things and it’s a time to build an Angular application for the terminal.
Building terminal application
First of all, let’s create a new Angular application using Angular CLI:
ng new AngularTerminalApp
Then, let’s add TerminalModule
in the AppModule
imports section:
import { NgModule } from '@angular/core';
import { TerminalModule } from 'platform-terminal';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
TerminalModule,
],
bootstrap: [AppComponent],
})
export class AppModule {
}
When AppModule
is done it’s a time to set up terminal platform:
import { platformTerminalDynamic } from 'platform-terminal';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformTerminalDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
Here you could notice how terminal platform imported from the platform-terminal
package we built previously. And then used to bootstrap our application’s AppModule
.
The only thing we need to do here is to createAppComponent
with all the required elements for the application:
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TransactionsService } from '../transactions.service';
import { SparklineService } from '../sparkline.service';
import { ServerUtilizationService } from '../server-utilization.service';
import { ProcessManagerService } from '../process-manager.service';
@Component({
selector: 'app-component',
template: `
<grid rows="12" cols="12">
<line
[row]="0"
[col]="0"
[rowSpan]="3"
[colSpan]="3"
label="Total Transactions"
[data]="transactions$ | async">
</line>
<bar
[row]="0"
[col]="3"
[rowSpan]="3"
[colSpan]="3"
label="Server Utilization (%)"
[barWidth]="4"
[barSpacing]="6"
[xOffset]="3"
[maxHeight]="9"
[data]="serversUtilization$ | async">
</bar>
<line
[row]="0"
[col]="6"
[rowSpan]="6"
[colSpan]="6"
label="Total Transactions"
[data]="transactions$ | async">
</line>
<table
[row]="3"
[col]="0"
[rowSpan]="3"
[colSpan]="6"
fg="green"
label="Active Processes"
[keys]="true"
[columnSpacing]="1"
[columnWidth]="[28,20,20]"
[data]="process$ |async">
</table>
<map
[row]="6"
[col]="0"
[rowSpan]="6"
[colSpan]="9"
label="Servers Location">
</map>
<sparkline
row="6"
col="9"
rowSpan="6"
colSpan="3"
label="Throughput (bits/sec)"
[tags]="true"
[style]="{ fg: 'blue', titleFg: 'white', border: {} }"
[data]="sparkline$ | async">
</sparkline>
</grid>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
transactions$ = this.transactionsService.transactions$;
sparkline$ = this.sparklineService.sparkline$;
serversUtilization$ = this.serversUtilization.serversUtilization$;
process$ = this.processManager.process$;
constructor(private transactionsService: TransactionsService,
private sparklineService: SparklineService,
private serversUtilization: ServerUtilizationService,
private processManager: ProcessManagerService) {
}
}
AppComponent
is pretty dumb here, so, I’ll leave it without comments.
Then we need to compile the application somehow. It could be done using Angular Compiler CLI:
ngc -p tsconfig.json
Angular Compiler CLI will produce compiled application files in the dist
folder right in the root of the project folder. So, we just need to bootstrap it as a plain node.js application:
node ./dist/main.js
And then we’ll see:

Conclusion
Congratulations ? you’ve reached the end of the article. During the article, we’ve learned a lot about Angular platforms. Went through a custom platform creation process. Learned about crucial Angular services and modules and finally, built custom platform which renders Angular applications inside the system’s terminal using ASCII graphics!
Here is the repository with all the source files related to the Terminal platform: https://github.com/Tibing/platform-terminal
If you want to get deeper knowledge on Angular platforms, take a look at the rest articles of the series:
- Angular Platforms in depth. Part 1. What are Angular Platforms?
- Angular Platforms in depth. Part 2. Application bootstrap process
- Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal
Also, follow me on twitter to be notified about new Angular articles as soon as possible!