Навешивание Angular 7 на HTML генерируемый сервером. Как лучше?

148
25 апреля 2019, 05:50

Проектируем блочный визуальный конструктор сайтов. Сервер будет отдавать размеченный готовый HTML блоков, но весь визуальный интерфейс, все контролы, настройки каждого блока хотим обрабатывать в приложении на Angular 7.

Подскажите best practive, какие-либо статьи или материалы по навешиванию Angular приложения (навешивание событий, модификация HTML, и т.д.) на статичный (сформированный сервером) HTML.

Понимаю, что в идеальном случае весть HTML должен формироваться из самого приложения Angular, но реальность такова, что HTML, который должен "ожить" приходит с сервера.

Answer 1

Здесь нет никаких best practice, с этим немногие сталкиваются, но регистрирование обработчиков событий в Angular работает так же, как и в обычном JavaScript. Самое большое преимущество ручного регистрирования обработчиков событий - производительность. Такой шаблон:

<button (click)="clickMe()">Click me</button>

Компилируется в NodeDef - определение узла. В рантайме у этого объекта будет свойство outputDefs (массив), который будет содержать объекты OutputDef, в случае с узлом button объект будет таков:

{
    type: OutputType.ElementOutput, // 0
    target: 'component',
    eventName: 'click',
    propName: 'clickMe'
}

Далее Angular регистрирует обработчики событий через класс EventManager, фактически все, что он делает это:

addEventListener(nodeDef.element, outputDef.eventName, componentInstance[outputDef.propName]);

Все, да не все. Эти действия происходят в зоне Angular, поэтому на любой асинхронное событие Angular запускает механизм обнаружения изменений по всему дереву.

Если же у нас будет готовый HTML, то мы можем регистрировать обработчики событий вне зоны и контролировать механизм обнаружения изменений, как пример возьмем компонент, в котором есть элемент, который сеттит innerHTML через атрибут:

@Component({
    selector: 'app-root',
    template: `
        <div [innerHTML]="html"></div>
    `
})
export class AppComponent implements OnDestroy {
    public html: SafeHtml = null!;
    private listeners: Function[] = [];
    constructor(
        private zone: NgZone,
        private renderer: Renderer2,
        private sanitizer: DomSanitizer,
        private host: ElementRef<HTMLUnknownElement>,
        private service: SomeService
    ) {
        this.service.getHTML().subscribe((html) => {
            this.setHTML(html);
        });
    }
    public ngOnDestroy(): void {
        this.listeners.forEach((listener) => listener());
    }
    private setHTML(html: string): void {
        this.html = this.sanitizer.bypassSecurityTrustHtml(html);
        this.zone.runOutsideAngular(() => {
            // обработчики событий мы должны регистрировать после того
            // как засеттится `innerHTML`, поэтому используем `setTimeout`
            setTimeout(() => this.addEventListeners());
        });
    }
    private addEventListeners(): void {
        const node = this.host.nativeElement.querySelector('some-element')!;
        this.listeners.push(
            this.renderer.listen(node, 'click', (e: MouseEvent) => {
                console.log(e);
            })
        );
    }
}

А вообще вы можете не знать на каких элементах вызываются события, но вы могли бы формировать HTML на сервере с подсказками, например:

<button data-event="click" data-method="clickMe">Click me</button>
<div data-event="mousemove" data-method="handleMousemove">Mousemove me</div>

В компоненте достаточно будет обойти все узлы и проверить свойства из dataset:

const nodes = this.host.nativeElement.querySelectorAll('*');
nodes.forEach((node: HTMLElement) => {
    const { event, method } = node.dataset;
    if (event && method && typeof this[method] === 'function') {
        this.listeners.push(this.renderer.listen(node, event, this[method]));
    }
});

А еще осторожней с контекстом, нужно тогда использовать стрелочные функции для методов:

private handleMousemove = () => {}

Или биндить:

this.renderer.listen(node, event, this[method].bind(this))

Да, кстати, обязательно нужно использовать DomSanitizer.prototype.bypassSecurityTrustHtml ибо Angular для защиты от XSS вообще удаляет все атрибуты и прочее регулярками, оставляя только элемент и текст внутри него.

READ ALSO
vue отправка формы без button и input

vue отправка формы без button и input

Имеется форма и нужно её отправить без button и input использую (a)Почему без? Ломаются стили

164
Верстка email писем 2к18/19

Верстка email писем 2к18/19

Уже несколько лет не верстал email-писем, изменилось ли что-нибудь? Мы по прежнему верстаем на таблицах, а стили – инлайним? Я пытался найти статистики...

155
Выравнивание таблицы, HTML

Выравнивание таблицы, HTML

Есть таблица, на предпоследней строке 3 ячейки, на последней 2: фотоПодскажите пожалуйста, как мне сделать выравнивание последней строки по центру,...

145
AJAX запрос mysql

AJAX запрос mysql

Подскажите пожалуйста, есть ajax запрос данных из таблицы mysql:

127