Nikita Poltoratsky

Software Engineer at Akveo

@nikpoltoratsky

@nikpoltoratsky

Platforms in Depth. Rendering Angular Applications in Terminal

@nikpoltoratsky

// main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

platformBrowserDynamic().bootstrapModule(AppModule);

@nikpoltoratsky

// angular.json
{
  "architect": {
    "build": {
      "builder": "@angular-devkit/build-angular:browser",
      "options": {

        // Application entry point
        "main": "src/main.ts",
      }

      // ...
    }
  }
}

@nikpoltoratsky

// main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

platformBrowserDynamic().bootstrapModule(AppModule);

@nikpoltoratsky

Agenda

  • Angular is a cross-platform framework
  • Learn what are platforms
  • How do they enable the cross-platform ability
  • Bootstrap process
  • Implementing custom platform

@nikpoltoratsky

Terminal Platform

@nikpoltoratsky

Angular is a cross-platform framework

@nikpoltoratsky

Browser

@nikpoltoratsky

Server

@nikpoltoratsky

Web Worker

@nikpoltoratsky

Native Platforms

@nikpoltoratsky

Platforms

@nikpoltoratsky

  • PlatformRef
  • Services
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { PlatformRef } from '@angular/core';


// Create Browser Platform
const platformRef: PlatformRef = platformBrowserDynamic();

// Bootstrap Application
platformRef.bootstrapModule(AppModule);
// main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

platformBrowserDynamic().bootstrapModule(AppModule);

@nikpoltoratsky

Platform factory

@nikpoltoratsky

@nikpoltoratsky

Platforms Inheritance

@nikpoltoratsky

@nikpoltoratsky

@nikpoltoratsky

How is it even possible?

@nikpoltoratsky

Abstraction

  • Renderer2

  • Compiler

  • ElementSchemaRegistry

  • Sanitizer

  • etc.

@nikpoltoratsky

@nikpoltoratsky

Abstract Services

@nikpoltoratsky

@nikpoltoratsky

@nikpoltoratsky

Bootstrap Process

@nikpoltoratsky

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { PlatformRef } from '@angular/core';


// Create Browser Platform
const platformRef: PlatformRef = platformBrowserDynamic();

// Bootstrap Application
platformRef.bootstrapModule(AppModule);

@nikpoltoratsky

Bootstrap

@nikpoltoratsky

Compile Module

@nikpoltoratsky

Compile Module

@nikpoltoratsky

  1. Create compiler
  2. Load resources
  3. Compile component's templates
  4. Compile module
  5. Return Module Factory

Create Root NgZone

@nikpoltoratsky

Create Root NgZone

@nikpoltoratsky

const zone = new NgZone();

zone.run(() => {

  // Create Module
  const moduleRef = moduleFactory.create();

  // All the rest logic executed here
  ...
});

Setup ErrorHandler

@nikpoltoratsky

Setup ErrorHandler

@nikpoltoratsky

// Get error handler from injector
const exceptionHandler: ErrorHandler = injector.get(ErrorHandler);

// Setup error handling outside Angular
// To make sure change-detection will not be triggered
zone.runOutsideAngular(

  // Subscribe on zone errors
  () => zone.onError.subscribe({
    next: (error: any) => {

      // Call error handler
      exceptionHandler.handleError(error);
    }
  })
);

Run Initializers

@nikpoltoratsky

Run Initializers

{provide: APP_INITIALIZER, useValue: setupWebWorker, multi: true},

@nikpoltoratsky

Get ApplicationRef

@nikpoltoratsky

Get ApplicationRef

@nikpoltoratsky

const appRef = injector.get(ApplicationRef);

Bootstrap Component

@nikpoltoratsky

Bootstrap Component

@nikpoltoratsky

// app.module.ts
@NgModule({ bootstrap: [AppComponent] })
export class AppModule {}

// Iterate all declared root components
AppModule.bootstrap.forEach((component) => {

  // Create factory for components
  const componentFactory =
    this._componentFactoryResolver.resolveComponentFactory(component);
  
  // Actually create component
  const compRef = componentFactory.create(...);
});

Bootstrap

@nikpoltoratsky

Terminal Platform

@nikpoltoratsky

Renderer

@nikpoltoratsky

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;

  // ...
}

@nikpoltoratsky

Renderer

import { createElement, Screen } from 'ascii-renderer';

const screen = new Screen();
const button = createElement('button');

// Append one element to another
screen.append(button);

// List for events on component
button.on('event name', listener);

@nikpoltoratsky

Renderer

import { createElement, Screen } from 'ascii-renderer';

export class TerminalRenderer implements Renderer2 {
 
  createElement(name: string, namespace?: string | null): any {
    return createElement(name);
  }

  selectRootElement(): Screen {
    return new Screen();
  }

  appendChild(parent: Element, newChild: Element): void {
    parent.append(newChild);
  }

  ...
}

@nikpoltoratsky

Renderer

@nikpoltoratsky

Never touch DOM

Wrap it up

@nikpoltoratsky

Demo Time 🤘

@nikpoltoratsky

Why?

@nikpoltoratsky

  1. Change Detection
  2. HttpClient
  3. Component-based approach
  4. RxJS
  5. etc.

Nikita Poltoratsky

Software Engineer at Akveo

@nikpoltoratsky

@nikpoltoratsky

tibing/platform-terminal

The End!

Thank you for your time!​

@nikpoltoratsky

Rendering Angular applications in Terminal​

By Nikita Poltoratsky

Rendering Angular applications in Terminal​

  • 1,162