Инфраструктура для разработки приложений Angular была задумана как платформенно-независимая технология (далее по тексту — фреймворк). Такой подход позволяет запускать приложения на Angular в разных средах: в браузере, сервере, веб-воркере и даже на мобильных устройствах.
В данной серии статей я опишу, как это вообще возможно — запускать Angular-приложения в разных средах. Мы также научимся создавать пользовательскую платформу Angular, с помощью которой можно визуализировать приложения из терминала, используя графику ASCII.
Содержание
- Angular — это кроссплатформенный фреймворк
- Что такое платформы Angular?
- Как платформы Angular делают возможным кроссплатформенный запуск приложений?
Angular — это кроссплатформенный фреймворк
Как было сказано выше, Angular была задумана платформенно-независимой инфраструктурой, в которую была заложена возможность вариативного применения. Благодаря этому, Angular является кроссплатформенным фреймворком, не ограничивающимся только браузером. Единственное, что необходимо для запуска приложений на Angular, — это движок JavaScript. Рассмотрим самые популярные рабочие среды Angular.
Браузер
Когда мы создаём новое приложение на Angular, используя Angular CLI ng new MyNewApplication
, в качестве среды для нашего приложения по умолчанию используется браузер.
Сервер
Приложения на Angular могут компилироваться и запускаться на серверной стороне. В этом случае мы можем компилировать Angular-приложение в статические HTML-файлы и затем отправлять эти файлы клиентам.
Благодаря этому, мы можем ускорить загрузку приложений и заодно позаботиться о правильной индексации приложений всеми поисковыми системами.
Веб-воркер
Также мы можем перетащить часть Angular-приложения в отдельный поток. В фоновый поток веб-воркер. В этом случае в основном потоке остаётся лишь малая часть приложения, обеспечивающая взаимодействие части, находящейся в веб-вокере, с API-интерфейсом документа.
Благодаря этому подходу, пользовательский интерфейс улучшается, очищаясь от «мусора», так как большая часть работы приложения проходит теперь за его пределами.
NativeScript
Кроме того, существует множество сторонних библиотек, позволяющих запускать Angular-приложения в разных средах. Например, NativeScript, который делает возможным запуск Angular на мобильных устройствах с использованием всей функциональности их собственных платформ.
Но как это вообще возможно — запускать Angular-приложения в разных средах?
Что такое платформы Angular?
Чтобы разобраться, что такое платформы Angular, надо обратиться к точке входа любого Angular-приложения — файлу main.ts
:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
platformBrowserDynamic().bootstrapModule(AppModule);
Здесь для нас важны две части:
platformBrowserDynamic()
функция, вызывающая и возвращающая некий объект.- Этот объект используется для начальной загрузки нашего приложения.
Если мы слегка её перепишем, обнаружится одна интересная деталь:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { PlatformRef } from '@angular/core';
// Создать браузерную платформу
const platformRef: PlatformRef = platformBrowserDynamic();
// Начальная загрузка приложения
platformRef.bootstrapModule(AppModule);
platformBrowserDynamic
—это фабрика платформ, функция, создающая новые экземпляры платформ. Результатом вызова функции platformBrowserDynamic
является PlatformRef
. PlatformRef
— это простой сервис Angular, который умеет производить начальную загрузку наших приложений. Для лучшего понимания того, как этот экземпляр PlatformRef
создаётся, давайте повнимательнее проследим за реализацией этой функции platformBrowserDynamic
:
export const platformBrowserDynamic = createPlatformFactory(
// Родительская фабрика платформ
platformCoreDynamic,
// Название для новой платформы
'browserDynamic',
// Дополнительные провайдеры
INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
);
Здесь мы можем видеть, что функция platformBrowserDynamic
— это всего лишь результат функции createPlatformFactory
, которая принимает следующие параметры:
- Родительская фабрика платформ —
platformCoreDynamic
- Название для новой платформы —
‘browserDynamic’
- Дополнительные провайдеры —
INTERNAL_BROWSER_DYNAMIC_PLAFORM_PROVIDERS
Здесь platformCoreDynamic
— это функция родительской фабрики платформ. Мы можем рассматривать platformCoreDynamic
и platformBrowserDynamic
как состоящие в иерархии наследования. Поэтому функция createPlatformFactory
просто помогает нам получить из одной фабрики платформ другую. Всё просто.
Самое интересное происходит в этой иерархии наследования чуть дальше. На самом деле, platformCoreDynamic
наследует platformCore
, у которой, в свою очередь, нет родителя.
Так что полная иерархия для platformBrowserDynamic
выглядит следующим образом:
Однако фабрики платформ Angular не меняют поведения родительских фабрик платформ в процессе наследования. Они обеспечивают наши приложения дополнительными токенами и сервисами.
Кажется, немного сложновато. Попробуем разобраться с функцией createPlatformFactory
и понять, как именно создаются фабрики платформ Angular.
Вот суперупрощённый алгоритм для createPlatformFactory
:
type PlatformFactory = (extraProviders?: StaticProvider[]) => PlatformRef;
export function createPlatformFactory(
parentPlatformFactory: PlatformFactory,
name: string,
providers: StaticProvider[] = [],
): PlatformFactory {
return (extraProviders: StaticProvider[] = []) => {
const injectedProviders: StaticProvider[] = providers.concat(extraProviders);
if (parentPlatformFactory) {
return parentPlatformFactory(injectedProviders);
} else {
return createPlatform(Injector.create({ providers: injectedProviders }));
}
};
}
Когда мы вызываем эту функцию, она возвращает функцию фабрики платформ, которая принимает дополнительные StaticProviders
для наших приложений. И если мы используем родительскую фабрику платформ, функция createPlatformFactory
вызовет её и вернёт её значение или же просто создаст и вернёт новую платформу. Для лучшего понимания рассмотрим процесс создания platformBrowserDynamic
шаг за шагом:
platformBrowserDynamic
создаётся в результате вызова функцииcreatePlatformFactory
сplatformCoreDynamic
в качестве родительской платформы.- Для создания новой платформы вызываем функцию
platformBrowserDynamic
. - Она проверяет, существует ли
parentPlatformFactory
, и вызывает её с помощью ряда дополнительных провайдеров, а затем просто возвращает её значение:
if (parentPlatformFactory) {
return parentPlatformFactory(injectedProviders);
}
4. На этом этапе можно заметить, что результатом функции platformBrowserDynamic
на самом деле является результат функции platformCoreDynamic
со всеми сервисами, используемыми platformBrowserDynamic
.
5. platformCoreDynamic
создаётся так же, как platformBrowserDynamic
, но с двумя отличиями — она расширяет platformCore
и использует собственные провайдеры.
export const platformCoreDynamic = createPlatformFactory(
platformCore,
'coreDynamic',
CORE_DYNAMIC_PROVIDERS,
);
Здесь мы можем заметить ту же ситуацию: из-за существования родительской платформы мы просто возвращаем результат фабрики родительской платформы с дополнительными провайдерами:
platformCore([ ...CORE_DYNAMIC_PROVIDERS, ...BROWSER_DYNAMIC_PROVIDERS ]);
6. Но внутри platformCore
у нас несколько другая ситуация.
export const platformCore = createPlatformFactory(
null,
'core',
CORE_PLATFORM_PROVIDERS,
);
Здесь в CORE_PLATFORM_PROVIDERS
содержится самый важный провайдер — сервис PlatformRef
. Когда мы используем null
в качестве родительской фабрики платформ, функция createPlatformFactory
просто возвращает результат функции createPlatform
.
7. Функция createPlatform
, в свою очередь, будет просто извлекать PlatformRef
из injector. И возвращать её к источнику вызова.
function createPlatform(injector: Injector): PlatformRef {
return injector.get(PlatformRef);
}
8. Теперь у нас есть PlatformRef
:
const ref: PlatformRef = platformBrowserDynamic();
Обратите внимание: в процессе наследования платформы не меняют поведения PlatformRef
явным образом. Вместо этого они дают новые наборы сервисов, которые использует PlatformRef
в процессе начальной загрузки.
Здесь можно заметить, что platformCore
не похож на другие платформы. platformCore
в каком-то роде особенный, потому как он отвечает за использование PlatformRef
для процесса создания платформ и служит корневой платформой для всех платформ в экосистеме Angular.
В итоге мы можем сказать, что каждая платформа состоит из двух важных частей:
PlatformRef
— сервис, который производит начальную загрузку приложения Angular.- Провайдеры —массив токенов и сервисов, используемых во время загрузки и запуска.
Как Angular-платформы делают возможным кроссплатформенный запуск
На этом этапе мы узнали, что такое Angular-платформы и как они создаются. Теперь обсудим, каким образом Angular-платформы делают из Angular кроссплатформенный фреймворк.
Всё дело в абстракции. Как известно, Angular в значительной степени основана на системе внедрения зависимостей. Именно поэтому довольно большая часть самой Angular представлена абстрактными сервисами:
- Renderer2
- Compiler
- ElementSchemaRegistry
- Sanitizer
- и др.
Все эти сервисы и многие другие представлены абстрактными классами внутри Angular. Когда мы используем разные платформы, эти платформы обеспечивают соответствующие средства реализации для этих абстрактных классов. Например, здесь у нас ряд абстрактных сервисов в Angular. Лично я предпочитаю обозначать их синими кружочками:
Но этим абстрактным классам не хватает реализации или функциональности. Когда мы используем браузерную платформу, она даёт собственные средства реализации для этих сервисов:
Когда же мы используем, скажем, серверную платформу, она даёт уже свои собственные средства реализации этих абстрактных базовых сервисов:
Теперь приведём конкретный пример.
Предположим, Angular использует абстракцию DomAdapter
для обработки данных объектной модели документа DOM независимо от среды. Здесь имеет место упрощённая версия абстрактного класса DomAdapter
.
export abstract class DomAdapter {
abstract setProperty(el: Element, name: string, value: any): any;
abstract getProperty(el: Element, name: string): any;
abstract querySelector(el: any, selector: string): any;
abstract querySelectorAll(el: any, selector: string): any[];
abstract appendChild(el: any, node: any): any;
abstract removeChild(el: any, node: any): any;
//... и т.д.
}
Когда мы используем браузерную платформу, она даёт соответствующие средства браузерной реализации для этого абстрактного класса:
export class BrowserDomAdapter extends DomAdapter { ... }
BrowserDomAdapter
взаимодействует с браузерным DOM непосредственно, и поэтому не может быть использован где-то ещё, кроме браузера.
Вот почему для запуска на серверной стороне и в целях визуализации на стороне сервера мы используем серверную платформу, которая, в свою очередь, реализуется следующим образом:
export class DominoAdapter extends DomAdapter { ... }
DominAdapter
не взаимодействует с DOM, так как у нас нет DOM на серверной стороне. Вместо этого он использует библиотеку domino, которая имитирует DOM для node.js.
В результате имеем следующую структуру:
Заключение
Поздравляем! Вы добрались до конца статьи, в которой мы осветили ряд вопросов: что такое платформы Angular, как они создаются — а также прошли шаг за шагом процесс создания platformBrowserDynamic
. И, наконец, разобрались, как концепция платформы делает из Angular кроссплатформенный фреймворк.
Читайте также:
- Переиспользование форм в Angular
- Динамические заголовки страницы в Angular
- Веселимся с Angular и трансформаторами в TypeScript
Перевод статьи Nikita Poltoratsky: Angular Platforms in depth. Part 1. What are Angular Platforms?