Часть 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
Комментарии