Angular

Часть 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