Часть 1Часть 2, Часть 3, Часть 4

В этой статье, последней из серии статей «Платформы Angular в деталях», мы с вами завершим процесс создания пользовательской платформы. Но прежде чем начать, пожалуйста, просмотрите ещё раз предыдущие статьи, чтобы убедиться, что вы понимаете, как работают платформы Angular.

Содержание

  • Очистка
  • Обработка ошибок
  • Модуль терминала
  • Терминал платформы
  • Создание приложения в терминале

Напоминаю, что в прошлой статье мы создали полнофункциональный TerminalRenderer, способный выводить Angular-приложения на экран из системного терминала с помощью графики ASCII. Но нам ещё много чего надо добавить. Так что идём дальше ?.

Очистка

Angular использует абстракцию Sanitizer для очистки потенциально опасных значений. Она требуется Angular для загрузки приложения. 

Sanitizer описывается в Angular как абстрактный класс, поэтому браузерная платформа предоставляет собственную реализацию — DomSanitizer. DomSanitizer помогает предотвратить атаки межсайтового скриптинга, очищая значения для безопасного использования в DOM. 

Например, при привязке URL-адреса в гиперссылке <a [href]=”someValue”> значение someValue будет очищено, чтобы злоумышленник не мог внедрить, скажем, javascript: URL-адрес, выполняющий код на сайте. В конкретных ситуациях может быть необходимо отключить очистку — если приложению, допустим, понадобится javascript, чтобы вставить в ссылку динамическое значение. Пользователи могут обойти защиту, создав значение с помощью одного из методов bypassSecurityTrust… и затем привязав к нему значение из шаблона.

Однако внутри терминала у нас нет DOM, поэтому атаки межсайтового скриптинга здесь невозможны. А раз так, то нет необходимости очищать шаблонные значения. Вот почему мы можем использовать в Angular пустую реализацию Sanitizer:

import { Sanitizer, SecurityContext } from '@angular/core';
export class TerminalSanitizer extends Sanitizer {
  sanitize(context: SecurityContext, value: string): string {
    return value;
  }
}

Как видно, реализация TerminalSanitizer только возвращает принятое значение. 

Обработка ошибок

Любое хорошее приложение должно уметь правильно справляться с ошибками, и Angular-приложения не являются исключением. Поэтому здесь есть возможность установить глобальный обработчик ошибок ErrorHandler, который будет реагировать на каждую нештатную ситуацию или ошибку в ваших приложениях. В Angular предусмотрена реализация по умолчанию для ErrorHandler. Она использует браузерную console, чтобы правильно регистрировать все нештатные ситуации. Но для терминала этого недостаточно.

В терминале появляется дополнительная проблема. Если приложение выбрасывает где-то ошибку, ErrorHandler просто регистрирует её, но приложение не отвисает. В браузере мы бы просто перезагрузили вкладку с приложением, но в терминале этого сделать не получится. Поэтому нам нужна пользовательская реализация для ErrorHandler, которая не только регистрирует ошибку, но и выходит из текущего процесса: 

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);
  }
}

Вот базовая реализация для ErrorHandler. Она только регистрирует ошибку в консоли, а затем выходит из процесса с кодом ошибки 1. Этот код сигнализирует, что что-то пошло не так во время выполнения приложения.

Модуль терминала

Любое приложение, созданное с помощью Angular CLI, по умолчанию настроено на выполнение в браузере. Поэтому в нём содержится BrowserModule, импортированный в AppModule. В BrowserModule есть несколько связанных с браузером провайдеров. Кроме того, он повторно экспортирует CommonModule и ApplicationModule. Эти модули содержат множество провайдеров, критически важных для Angular-приложений. 

В этих провайдерах нуждается и терминал платформы, поэтому нам надо создать пользовательский TerminalModule. Он повторно экспортирует CommonModule и ApplicationModule, а также регистрирует часть созданных выше сервисов в приложении.

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 {
}

Однако не все сервисы могут регистрироваться через TerminalModule. Некоторые из них требуются во время загрузки и должны быть подготовлены заранее. Единственный способ это сделать – создать пользовательскую платформу.

Терминал платформы

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]);

Терминал платформы создаётся через функцию createPlatformFactory, позволяющую наследовать провайдеры platformCoreDynamic, а также добавлять соответствующие провайдеры терминала платформы.

Теперь у нас все готово. Настала пора создать Angular-приложение в терминале.

Создание приложения в терминале

Прежде всего создадим новое Angular-приложение с помощью Angular CLI:

ng new AngularTerminalApp

Затем добавим TerminalModule в раздел импортирования AppModule:

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 {
}

Когда с AppModule закончили, пора настроить терминал платформы:

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));

Здесь показано, как терминал платформы импортируется из созданного нами ранее пакета platform-terminal и используется для загрузки AppModule нашего приложения.

Единственное, что нам здесь надо сделать, — это создать для приложения AppComponent со всеми требующимися элементами:

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) {
  }
}

Теперь надо как-то скомпилировать приложение. Сделаем это с помощью компилятора Angular Compiler CLI:

ngc -p tsconfig.json

Компилятор Angular Compiler CLI сформирует файлы скомпилированного приложения прямо в корневом каталоге проекта. Так что нам лишь надо загрузить его как обычное приложение node.js:

node ./dist/main.js

И затем мы увидим:

Заключение

Поздравляю! Вы добрались до конца статьи, в которой мы многое узнали об Angular-платформах, познакомились с важными сервисами и модулями Angular и, наконец, создали ту самую пользовательскую платформу, которая визуализирует Angular-приложения в терминале с помощью графики ASCII!

В репозитории вы можете найти все исходные файлы, связанные с терминалом платформы https://github.com/Tibing/platform-terminal

Читайте также:


Перевод статьи Nikita Poltoratsky: Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal

Предыдущая статьяПлатформы Аngular в деталях. Часть 3. Визуализация Angular-приложений в терминале
Следующая статьяPython-библиотеки интерпретации моделей ML