Модальні вікна на Angular, Angular 2 і ReactJS

У цій статті ми розглянемо, як створювати спливаючі і перекривають елементи на React, Angular 1.5 і Angular 2. Реалізуємо створення і показ модального вікна на кожному з фреймворків. Весь код написаний на typescript. Вихідний код прикладів доступний на github.
Введення
Що таке "спливаючі і перекривають" елементи? Це DOM елементи, які показуються поверх основного вмісту документа.
Це різні спливаючі вікна (в тому числі модальні), випадаючі списки і менюшки, панельки для вибору дати і так далі.
Як правило, для таких елементів застосовують абсолютне позиціонування в координатах вікна (для модальних вікон) за допомогою
position: fixed
, або абсолютне позиціонування в координатах документа — для меню, випадаючих списків, які повинні розташовуватися біля своїх "батьківських" елементів, за допомогою
position: absolute
.
Просте розміщення спливаючих елементів біля "батьків" і приховування/відображення їх не працюють повністю. Причина — це батьківські контейнери з
overflow
, відмінним від
visible
(
fixed
). Все, що виступає за межі контейнера, буде обрізано. Також такі елементи можуть перекриватися елементами нижче по дереву, і z-index не завжди тут допоможе, так як працює лише в межах одного контексту накладення.
По-хорошому, цю проблему елегантно міг би вирішити Shadow DOM (і то не факт), але поки він не готовий. Могло б допомогти CSS властивість, що забороняє обрізку і перекриття, або позиціонування щодо документа (а не батька), але його немає. Тому використовуємо милицю — DOM елементи екранних компонентів поміщаємо в
body
, ну або, на худий кінець, ближче до нього, в спеціальний контейнер, у батьків якого свідомо немає "обрезающих" стилів.
Зауваження: ми не будемо розглядати позиціювання елементів з різними координатами і батьками — це окрема складна тема, повна милицями, javascript-му і обробкою несподіваних подій браузера.
Наприклад, ні один з існуючих способів не ідеально вирішує проблему позиціонування, якщо нам потрібно зробити компонент типу
select
або autocomplete з випадаючим списком біля елемента.
Використання
position: fixed
, мабуть, дозволяє уникнути обрізки батьківським контейнером, але змушує обробляти скролінг документа і контейнера (простіше всього тупо закривати випадаючий список). Використання
position: absolute
та приміщення елемента
body
обробляє прокручування документа правильно, але вимагає перерахунку позиції при прокрутці контейнера.
Всі способи вимагають обробки події resize. Загалом, немає тут хорошого рішення.
Приклади
Всі приклади містять однакову верстку, і складаються з поля вводу з текстом і кнопки. По натисненню на кнопку введений текст з'являється в "модальне" віконці з кнопкою "Закрити".
Всі приклади написані на typescript. Для компіляції і бандлинга використовується webpack. Щоб запустити приклади, у вас повинен бути встановлений NodeJS.
Для запуску перейдіть в папку з відповідним прикладом і виконайте в командному рядку NodeJS один раз
npm run prepare
, щоб встановити глобальні і локальні пакети. Потім виконайте
npm run server
. Після цього відкрийте в браузері адресу http://localhost:8080
Якщо це робити лінь, можна просто відкрити в браузері
index.html
з папки відповідного прикладу.
Angular 1.5
Компоненти
У версії 1.5 Angular придбав синтаксичний цукор у вигляді методу
component
у модуля, який дозволяє оголошувати компоненти. Компоненти — це насправді директиви, але код оголошення орієнтований на створення цеглинок предметної області застосування, тоді як директиви більше орієнтовані (ідеологічно, технічно все ідентично) на низькорівневі і імперативну роботу з DOM. Це нововведення просте, але прикольне, і дозволяє оголошувати компоненти способом, схожим з Angular 2. Ніяких нових фіч цей спосіб не привносить, але може кардинально вплинути на структуру програми, особливо, якщо раніше ви користувалися
<div ng-controller="MyController as $c">...</div>
.
Від себе я можу додати, що я в захваті від цієї можливості. Вона дозволяє створювати компоненти з чітким контрактом і високим потенціалом для повторного використання. Більш того, з цією можливістю я відмовився від винесення HTML розмітки компонента в окремий файл, т. к. розмітка виходить маленька і акуратна — в ній використовуються вкладені компоненти — і не захаращує ісходник компонента.
У прикладі я теж використовую цю можливість, тому, якщо ви ще не знайомі з нею, почитати можна тут.
Два способи
Напевно, існує більше способів помістити компонент в довільне місце DOM. Я покажу два з них, один за допомогою сервісу
$compile
, другий — за допомогою директиви з
transclude
.
Перший спосіб імперативний, і підходить більше для ad-hoc показу модальних вікон, наприклад, для виведення повідомлень або запиту у користувача якихось параметрів. Також цей спосіб можна застосовувати, якщо тип компонента невідомий, або розмітка динамічна.
Другий спосіб — декларативний, він дозволяє вбудувати спливаючий елемент шаблон компонента,
але при показі поміщати його в
body
. Підходить для компонентів типу дроп-дауна, дозволяючи реактивно керувати видимістю.
Спосіб 1: $compile
Сервіс
$compile
дозволяє перетворити рядок з Angular розміткою в DOM елемент і пов'язати його з
$scope
.
Цей елемент може бути доданий в довільне місце документа.
Все досить просто.
Ось документація сервісу. За посиланням повне керівництво по API директив, що цікавить нас, частина в самому кінці — використання
$compile
як функції.
Отримуємо доступ до
$compile

// popup.service.ts
import * as angular from "angular";

export class PopupService {
static $inject = ["$compile"];

constructor(private $compile: angular.ICompileService) {}
}

Оголошення
static $inject=["$compile"]
еквівалентом Javascript коду:
function PopupService($compile) {
this.$compile = $compile;
} 

PopupService.$inject = ["$compile"];

$compile
працює у дві фази. На першій він перетворює рядок у функцію-фабрику. На другий потрібно викликати отриману фабрику і передати їй
$scope
. Фабрика поверне DOM елементи, пов'язані з цим $scope.
$compile
приймає три аргументи, нас цікавить тільки перший. Перший аргумент — це рядок, що містить HTML шаблон, який буде потім перетворений в працюючий фрагмент Angular програми. В шаблоні можна використовувати будь-які зареєстровані компоненти з вашого модуля і його залежностей, а також будь-які валідні конструкції Angular — директиви, інтерполяцію рядків і т. п.
Результатом компіляції буде фабрика — функція, яка дозволить зв'язати рядковий шаблон з будь-яким
$scope
. Таким чином, задаючи шаблон, можна використовувати будь-які поля і методи вашого скоупа. Наприклад, ось як виглядає код відкриття спливаючого вікна:
/// test-popup.component.ts

export class TestPopupComponentController {
static $inject = ["$scope", PopupService.Name];

text: string = "Open popup with this text";

constructor(
private $scope: angular.IScope,
private popupService: PopupService) {
}

openPopup() {
const template = `<popup-content text="$c.text" ...></popup-content> `
this.popupService.open(template)(this.$scope);
}
}

Зверніть увагу на кілька речей.
По-перше, шаблон містить компонент
<popup-content></popup-content>
.
По-друге, шаблон містить звернення до поля
text
контролера:
text="$c.text"
.
$c
— це псевдонім контролера, заданий при оголошенні компонента.
PopupService.open
повертає фабрику, що дозволяє зв'язати шаблон
$scope
. Для того, щоб зв'язати динамічний компонент
$scope
нашого компонента, доводиться передавати
$scope
в контролер.
Ось як виглядає
PopupService.open
:
// popup.service.ts

open(popupContentTemplate: string): ($scope: angular.IScope) => () => void {
const content = `
<div class="popup-overlay">
${popupContentTemplate}
</div>
`;

return ($scope: angular.IScope) => {
const element = this.$compile(content)($scope);
const popupElement = document.body.appendChild(element[0]);

return () => {
body.removeChild(popupElement);
};
};
}

В нашій функції ми обертаємо переданий шаблон розмітку модального вікна. Потім компілюємо шаблон, отримуючи фабрику динамічних компонентів. Потім викликаємо отриману фабрику, передаючи
$scope
, і отримуємо HTML елемент, який являє собою повністю робочий фрагмент Angular програми, пов'язаний з переданим
$scope
. Тепер його можна додати в будь-яке місце документа.
Хоча наш метод
PopupService.open
теж повертає фабрику для зв'язку з
$scope
, він робить додаткову роботу. По-перше, коли фабрика викликається, він не тільки створює елемент, але і додає його в
body
. По-друге, він створює функцію, яка дозволить "закрити" поп-ап вікна, видаливши його з документа.
PopupService.open
повертає цю функцію для закриття вікна.
Що ж, варіант не такий поганий. Хоча саме відображення вікна імперативне, тим не менш, вміст вікна все ще реактивно, і може бути декларативно пов'язано з батьківським
$scope
. Хоча для відображення контенту доводиться використовувати рядки, але якщо сам контент вікна зробити у вигляді компонента, то пов'язувати потрібно буде тільки input і output властивості, а не весь контент. Метод дозволяє помістити поп-ап елемент в будь-яке місце документа, навіть якщо воно поза
ng-app
.
Спосіб 2: Директива з transclude
Другий спосіб дозволяє задавати вміст спливаючого елемента прямо біля його "батьків". При показі елемент буде насправді доданий в
body
.
<div>
<div class="form-group">
<label>
Enter text to display in popup:
</label>
<input class="form-control" ng-model="$c.text" type="text" />
</div>
<p>
<button class="btn btn-default" 
ng-click="$c.openInlinePopup()">
Open inline 
</button>
</p>
<!-- Ось це буде body -->
<popup ng-if="$c.popupVisible">
<popup-content text="$c.text" close="$c.closeInlinePopup()">
</popup-content>
</popup>
</div>

Тут шукана директива —
<popup>...</popup>
. Все, що всередині неї, буде показано у спливаючому вікні, і розташоване в
body
.
Невеликий недолік цього методу в тому, що показувати і ховати вікно необхідно за допомогою директиви
ng-if
, яка фізично буде прибирати/додавати вміст в DOM дерево.
transclude
transclude
— це спосіб директив працювати зі своїм вмістом. Під вмістом розуміється те, що розташоване між відкривається і закривається тегами директиви.
<my-directive>
<!-- Все, що всередині my-directive, це вміст -->
<div class="">
<other-component>...</other-component>
</div>
</my-directive>

Це дуже потужна можливість, на основі якої можна зробити багато цікавого. Ми ж будемо брати вміст і поміщати його в
body
.
Як використовувати
transclude
? Безпосередньо використовувати контент (наприклад, через
$element.children
) не можна — він не пов'язаний з правильним scope, і не скомпільовано (не замінені директиви тощо). Для використання
transclude
потрібно отримати доступ до т. зв.
transclude function
. Це фабрика, яка вміє створювати скомпільовані копії (клони) вмісту. Ці клони будуть скомпільовані і пов'язані з правильним scope, і взагалі, дуже схожі на результат роботи
$compile
. Transclude function, однак, не повертає значення, як звичайна фабрика, а передає його в коллбек-функцію.
Можна створювати скільки завгодно клонів, перекрити їм scope, додавати в будь-яке місце документа, і так далі. Здорово.
Для директив, які керують вмістом (викликають transclude function), необхідно реалізовувати lifecycle методи для очищення вмісту. Ці методи реалізуються в контролері директиви. Видаляти доданий вміст потрібно
$onDestroy
.
Залишилося останнє — як отримати доступ до transclude function. Вона передається в декількох місцях, але ми її заинжектим в контролер. Для того, щоб вона передалася, в конфігурації директиви має бути встановлено
transclude: true
.
Отже, повний код:
import * as angular from "angular";

export class PopupDirectiveController {
private content: Node;

constructor(private transclude: angular.ITranscludeFunction) {
}

$onInit() {
this.transclude(clone => { 
const popup = document.createElement("div");
popup.className = "popup-overlay";
for(let i = 0; i < clone.length; i++) {
popup.appendChild(clone[i]);
}

this.content = document.body.appendChild(popup);
});
}

$onDestroy() {
if (this.content) {
document.body.removeChild(this.content)
this.content = null;
}
}
}

export const name = "popup";

export const configuration: angular.IDirective = {
controller: ["$transclude", PopupDirectiveController],
replace: true,
restrict: "Е",
transclude: true
};

Непогано, всього 36 рядків.
Переваги:
  • Повністю реактивне відображення та приховання, реактивне вміст
  • Зручно розносить "віртуальне" розташування в дереві компонентів і "фізична" розташування в DOM дереві.
  • Декларативно прив'язане до поточного scope.
Недоліки:
  • У цьому варіанті реалізації потрібно використовувати
    ng-if
    для управління відображенням.
Angular 2
Нова версія Angular, що відрізняється від першого настільки, що, фактично, це новий продукт.
Мої враження від нього двоякі.
З одного боку, код компонентів безсумнівно чистіше і ясніше. При написанні бізнес-компонентів поділ коду та подання відмінне, change tracking працює чудово, прощайте
$watch
та
$apply
, прекрасні засоби для опису контракту компонента.
З іншого боку, не залишає відчуття монструозності. 5 min quickstart виглядає знущанням. Безліч додаткових бібліотек, багато з яких є обов'язковими до використання (як
rxjs
). Те, що я встигаю побачити напис Loading... при відкритті документа з файлової системи, вселяє сумніви в його швидкості. Розмір пакету 4.3 MB проти 1.3 MB у Angular 1 і 700KB React (правда, це без оптимізацій, дефолтний бандлинг webpack-а). (Нагадую, що webpack збирає (бандлит) весь код програми і його залежностей (npm) в один великий javascript файл).
Минифицированный розмір: Angular 1 — 156KB, Angular 2 — близько 630KB, в залежності від варіанту, React — 150KB.
Angular 2 на момент написання ще RC. Код практично готовий, багів начебто немає, основні речі зроблені (ну крім хіба що переробки форм). Однак неповна документація, багато речей доводиться шукати в коментарях до github issue
(як, наприклад, динамічне завантаження компонентів, що, власне, і підштовхнуло мене до написання цієї статті).
Disclaimer
Витрачати півтори години на кроки, описані в згаданому 5 min quickstart, не хотілося, тому проект налаштований не зовсім, кхм, традиційно для Angular 2. SystemJS не використовується, натомість бандлится webpack-му. Причому Angular 2 не зазначається як externals, а береться з npm пакета як є. У результаті виходить гігантський бандл в 4.5 MB вагою. Тому не використовуйте цю конфігурацію в продакшені, якщо, звичайно, не хочете, щоб користувачі зненавиділи ваш індикатор завантаження. Друга дивина, що не знаю, чим викликана, це відрізняються назви модулів. У всіх прикладах (у тому числі в офіційній документації) імпорт Angular виглядає як
import { } from "@angular/core"
. У той же час, у мене так не запрацювало, а працює
import {} from "angular2/core"
.
Динамічне завантаження
До честі Angular 2, код динамічного завантаження викликає труднощі тільки при пошуку. Для динамічного завантаження використовується клас ComponentResolver в поєднанні з ViewContainerRef.

// Асинхронно створює новий компонент типу.
// Тип - це клас компонента (його функція-конструктор)
loadComponentDynamically(componentType: Type, container: ViewContainerRef) {
this.componentResolve
.resolveComponent(componentType)
.then(factory => container.createComponent(factory))
.then(componentRef => {
// Отримуємо доступ до екземпляру класу компонента
componentRef.instance;
// Отримуємо доступ ElementRef контейнера, у який поміщений компонент
componentRef.location;
// Отримуємо доступ до DOM елементу. 
componentRef.location.nativeElement;
// Видаляємо компонент
componentRef.destroy();
});
}

ComponentResolver
легко отримати через dependency injection.
ViewContainerRef
, мабуть, не може бути створений для довільного DOM елемента, і може бути отриманий тільки для існуючого Angular компонента. Це означає, що помістити динамічно створений елемента в довільне місце DOM дерева не, принаймні, в реліз-кандидата.
Тому, наш механізм для показу поп-апів буде складовим.
По-перше, у нас буде компонент, який динамічно додаватися поп-ап елементи. Його потрібно буде розмістити де-небудь в дереві компонентів, бажано ближче до кореневого елементу. Крім того, жоден з його батьківських контейнерів не повинен містити стилів, обрезающих вміст. У коді це
overlay-host.component.ts
.
По-друге, у нас є допоміжний компонент, що містить в собі розмітку для поп-ап вікна. Це
OverlayComponent
, який обертається динамічно створюваний компонент.
по-третє, у нас є сервіс, який забезпечує зв'язок між хост-компонентом для поп-апів і клієнтами, які хочуть показувати компонент. Сервіс досить простий, хост-компонент реєструє себе в ньому при створенні, і метод сервісу просто перенаправляє дзвінки відкриття вікна цього хост-компоненту.
Хост-компонент
Я наведу клас, він не дуже великий, і потім пройдуся по тонким місць:
import { Component, ComponentRef, ComponentResolver, OnInit, Type, ViewChild, ViewContainerRef } from "angular2/core";

import { OverlayComponent } from "./overlay.component";
import { IOverlayHost, OverlayService } from "./overlay.service";

@Component({
selector: "overlay-host",
template: "<template #container></template>"
})
export class OverlayHostComponent implements IOverlayHost, OnInit {

@ViewChild("container", { read: ViewContainerRef }) container: ViewContainerRef;

constructor(
private overlayService: OverlayService,
private componentResolver: ComponentResolver) {
}

openComponentInPopup<T>(componentType: Type): Promise<ComponentRef> {
return this.componentResolver
.resolveComponent(OverlayComponent)
.then(factory => this.container.createComponent(factory))
.then((overlayRef: ComponentRef) => {

return overlayRef.instance
.addComponent(componentType)
.then(result => {

result.onDestroy(() => {
overlayRef.destroy();
});

return result;
});
});
}

ngOnInit(): void {
this.overlayService.registerHost(this);
}
}

Що робить цей код? Він динамічно створює компонент, використовуючи його тип (тип компонента — це його функція-конструктор). Попередньо створюється компонент-обгортка (
OverlayComponent
), наш запитаний компонент додається до нього. Також ми підписуємося на подію
destroy
, щоб знищити обгортку при знищенні компонента.
Перше, на що потрібно звернути увагу, це як виходить
ViewContainerRef
за допомогою запиту до вмісту.
Декоратор
@ViewChild()
дозволяє отримувати
ViewContainerRef
по імені template variable
template: <template #container></template>
.
#container
— це і є template variable, мінлива шаблону. До неї можна звертатися по імені, але тільки в самому шаблоні. Щоб отримати доступ до неї з класу компонента, що використовується згаданий декоратор.
Чесно кажучи, я це нагуглил, і як на мене, це взагалі неинтуитивно. Це одна з особливостей другого Angular-а, яка мені дуже сильно кинулася в очі, — в документації дуже складно, або взагалі неможливо, знайти рішення для типових завдань низькорівневої розробки директив. Документація для створення саме бізнес-компонентів нормальна, та й нічого там особливо складного немає. Однак для написання сценаріїв контролів, основних компонентів, неможливо знайти документації. Динамічне створення компонентів, взаємодія з шаблоном з класу — ці області просто не документовані. Навіть в описі @ViewChild нічого не сказано про другому параметрі.
Що ж, сподіваюся, до релізу задокументують.
Код
OverlayHostComponent
, який я привів вище, — це найцікавіше в нашому прикладі.
OverlayComponent
містить схожий код для динамічного додавання вмісту,
OverlayService
перенаправляє дзвінки відкриття поп-апа до хост-компоненту. Я не наводжу лістинги через тривіальності, якщо цікаво, подивіться в исходниках.
Подивимося тепер, як цим користуватися:
import { Component, Input } from "angular2/core";

import { OverlayService } from "./overlay";
import { PopupContent } from "./popup-content.component";

@Component({
selector: "test-popup",
template: `
<div>
<div class="form-group">
<label>
Enter text to display in popup:
</label>
<input class="form-control" [(ngModel)]="text" type="text" />
</div>
<p>
<button class="btn btn-primary" 
(click)="openPopup()">
Open popup
</button>
</p> 
</div> 
`
})
export class TestPopup {
text: string = "Show me in popup";

constructor(private overlayService: OverlayService) {
}

openPopup() {
this.overlayService
.openComponentInPopup(PopupContent)
.then(c => {
const popup: PopupContent = c.instance;
popup.text = this.text;
popup.close.subscribe(n => {
c.destroy();
});
});
}
}

OverlayService
вказаний в providers
Root
компонента, в нашому компоненті його реєструвати не потрібно.
Після створення екземпляра компоненту можна отримати до нього доступ через
ComponentRef.instance
.
Потім починається страшна императивщина: встановлюємо властивості компонента, підписуємося на події, все це руками. Ні про яку декларативність або реактивності мови не йде. Особливо весело, якщо потрібно забезпечити двостороннє зв'язування. Кілька варіантів з нескінченними циклами і прапорами
isRunning
вам забезпечені.
Висновок
Чесно кажучи, це виглядає жахливо. Я щиро сподіваюся, що я шукав недостатньо добре, та десь є гарний спосіб, що дозволяє розмістити компонент в довільному місці DOM дерева, і нормально зв'язати властивості динамічно створеного компонента з батьком.
Я довго розглядав ісходник ngFor, але не зміг вирішити проблему зв'язування. Я думав над фабрикою компонентів з динамічними шаблонами, але не впевнений, що існує спосіб динамічної реєстрації компонентів у масиві
directives
.
Відсутність способу поміщати компоненти в довільне місце DOM це не дуже добре, і може вносити обмеження, особливо якщо це сторінка кілька мікро-додатків. Однак відсутність динамічного зв'язування компонентів це, на мій погляд, набагато більш серйозна проблема.
ReactJS
У Реакте стандартний спосіб відображення компонента в дерево DOM — це метод
render
, який повертає віртуальний вузол віртуального DOM. Однак, це зовсім не означає, що це єдиний спосіб. Для вставки компонента в довільне місце методу
render
повертається
null
, і перехоплюються lifecycle-методи
componentDidMount
,
componentWillUnmount
,
componentDidUpdate
. В
componentDidMount
та
componentDidUpdate
, використовуючи
ReactDOM.render
, можна отрендерить вміст в будь-яке місце. В
componentWillUnmount
вміст, відповідно, знищується.
Власне, код:
import * as React from "react";
import * as ReactDOM from "react-dom";

export class Popup extends React.Component<{}, {}> {
popup: HTMLElement;

constructor() {
super();
}

render() {
return (<noscript></noscript>);
}

componentDidMount() {
this.renderPopup();
}

componentDidUpdate() {
this.renderPopup();
}

componentWillUnmount() {
ReactDOM.unmountComponentAtNode(this.popup);
document.body.removeChild(this.popup);
}

renderPopup() {
if (!this.popup) {
this.popup = document.createElement("div");
document.body.appendChild(this.popup);
}

ReactDOM.render(
<div className="popup-overlay">
<div className="popup-content">
{ this.props.children }
</div>
</div>,
this.popup);
}
}

Все просто і зрозуміло. Видно, що такий сценарій творцями Реакта продумывался.
Взагалі Реактив після Angular справляє дуже приємне враження. Відсутні милиці відстеження змін, які начебто не потрібно використовувати, але завжди доводиться. Простий доступ до DOM елементів, якщо він потрібен. Простий доступ до вмісту реактив-елемента через
children
, причому це не рядок і не HTMLElement, а структура, яка містить в собі повноцінні реактив-елементи (для роботи з ними потрібно використовувати
React.Children
).
Гаразд, тепер подивимося, як це використовувати. Наводжу, для стислості, тільки метод
render
:
render() {
return (
<div>
<div className="form-group">
<label>
Enter text to display in popup:
</label>
<input className="form-control"
value={ this.state.text }
onChange={ e => this.setText(e.target.value) }
type="text" />
</div>
<p>
<button className="btn btn-primary" onClick={e => this.openPopup() } type="button" >
Open popup
</button>
</p>

<Ifc condition={ () => this.state.isPopupVisible } >
<Popup>
<div className="alert alert-success">
<h2>
{ this.state.text}
</h2>
<button className="btn btn-warning" onClick={e => this.closePopup() } type="button" >
Close popup
</button>
</div>
</Popup>
</Ifc>
</div>
)
}

Ifc
це костылик, який рендерить вміст, тільки якщо
condition
істинно. Це дозволяє позбутися від монструозных IIFE, якщо потрібно отрендерить шматок компонента за умовою.
В іншому все просто: якщо компонент
<Popup></Popup>
є у віртуальному дереві — поп-ап віконце показується, якщо немає — то ховається. При цьому фізично в DOM дереві воно знаходиться в
body
.
Як бачимо, дуже схоже на другий спосіб з Angular 1.5, з директивою.
Підсумки
В принципі, поп-ап в Реакте можна зробити і імперативним способом, схожим на спосіб Angular з
$compile
. Це може спростити деякі сценарії і не створювати прапор в змозі програми для показу кожного алерта. Принцип той же (використовуючи
ReactDOM.render
), але тільки не в методах життєвого циклу компонента, а в методі
openPopup
. Це, звичайно ж, порушує реактивність, але зробить код зрозуміліше, збільшивши зв'язність.
Недоліки наведеного способу — не буде працювати в серверному рендерінгу.
Висновок
Підходи, спочатку закладені в Реакте — односпрямовані потоки даних, компоненти, чіткий input/output контракт компонента — знайшли своє відображення і в Angular: by design в Angular 2, і в оновленнях Angular 1.5. Це зміна без сумніву пішло на користь першого Angular.
Що стосується показу спливаючих елементів — це приклад милиць, які виникли через недосконалість CSS, але вплинули на всю екосистему веб-розробки. Це яскравий приклад поточної абстракції, а також балансу між "чистої архітектурою" і "реальним життям" веб-розробки. Як бачимо, розробники Angular 2 небудь не замислювалися про це сценарії, або реалізували його, але нікому не сказали. У той же час, перший Angular і React досить гнучкі (а розробники Реакта мабуть ще й продумані), щоб можна було реалізувати рендеринг елемента відмінне від його розташування в дереві компонентів.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.