Привет. Меня зовут Роман, и я не изобретатель велосипедов. Мне нравится фреймворк Angular и экосистема вокруг него, и я разрабатываю с его помощью свои веб-приложения. С моей точки зрения, основное преимущество Angular в долгосрочной перспективе базируется на разделении кода между HTML и TypeScript, что подробно было описано одним из его разработчиков why-angular-renders-components-with.html Это преимущество имеет и обратную сторону: необходимость компиляции в принципе и сложность динамической компиляции компонентов в runtime. А так хочется использовать уже знакомый синтаксис шаблонов Angular, чтобы дать пользователю своих приложений возможность настраивать шаблоны писем, генерировать отчеты и таблицы для печати или задавать формат экспорта xml файлов! Чтобы узнать, как это сделать — добро пожаловать под кат!
В целом, использование шаблонов Angular пользователем может выглядеть следующим образом: у нас есть некий набор данных:
Нужно дать возможность настроить текст письма, который будет отправляться пользователю после редактирования проекта. С помощью шаблона Angular это может выглядеть так:
Эту задачу можно решить использованием компилятора Angular на клиентской (или даже серверной стороне), но это весьма трудоёмко и потребует притащить много мегабайт кода на клиент. Почему же компилятор Angular такой большой? Это связано с тем, что он поддерживает море разнообразного функционала для композиции компонентов и модулей, а также содержит собственный парсер HTML! Поэтому я решил написать минимальный преобразователь шаблонов Angular, который будет использовать встроенный в браузер парсер HTML. Это удалось сделать всего лишь в 200 с небольшим строчек кода за пару часов. Результатом я решил поделиться с общественностью на GitHub
Использовать библиотеку ng-template довольно просто:
Устанавливаем зависимость из npm
или через yarn
И используем следующим образом:
Подробнее смотрите в тестах ng-template.spec.ts
Для вычисления выражений в шаблонах используется eval с преферансом и куртизанками. Дело в том, что в шаблонах Angular доступ к переменным используется без привычного для JavaScript префикса this. Поэтому требуется вызвать eval(), у которого в области видимости лежат все переменные из объекта с данными. Сгенерировать такой код для eval() у меня не получилось, т.к. код вида
не позволяет передать функции
Решение было найдено путем создания функции, у которой параметры имеют имена полей объекта с данными:
P.S.: Я надеюсь в будущем, когда API нового компилятора Ivy стабилизируется, можно будет генерировать набор операторов для Ivy и создавать полноценные компоненты в динамике!
Ссылка на исходники
Задача
В целом, использование шаблонов Angular пользователем может выглядеть следующим образом: у нас есть некий набор данных:
const data = { project: 'MySuperProject', userName: 'Roman', role: 'admin', projectLink: 'https://example.com/my-super-projectproject' }
Нужно дать возможность настроить текст письма, который будет отправляться пользователю после редактирования проекта. С помощью шаблона Angular это может выглядеть так:
<body> Добрый день! Проект {{project}} доступен по ссылке <a href="{{projectLink}}">3D проект вашего заказа</a> <div *ngIf="role == 'admin'"> Для редактирования проекта пройдите по ссылке <a href="{{projectLink}}?mode=edit">Редактировать</a> </div> </body>
Библиотека ng-template
Эту задачу можно решить использованием компилятора Angular на клиентской (или даже серверной стороне), но это весьма трудоёмко и потребует притащить много мегабайт кода на клиент. Почему же компилятор Angular такой большой? Это связано с тем, что он поддерживает море разнообразного функционала для композиции компонентов и модулей, а также содержит собственный парсер HTML! Поэтому я решил написать минимальный преобразователь шаблонов Angular, который будет использовать встроенный в браузер парсер HTML. Это удалось сделать всего лишь в 200 с небольшим строчек кода за пару часов. Результатом я решил поделиться с общественностью на GitHub
Использовать библиотеку ng-template довольно просто:
Устанавливаем зависимость из npm
npm install --save @quanterion/ng-template
или через yarn
yarn add @quanterion/ng-template
И используем следующим образом:
import { compileTemplate, htmlToElement } from '@quanterion/ng-template'; async test() { let data = { name: 'Roman' }; let element = htmlToElement(`<div>{{name}}</div>`); await compileTemplate(element, data); alert(element.outerHTML); }
Поддерживаемый синтаксис
- Выражения {{expression}} с возможностью доступа к переменным и вызова функций
- Шаблоны ng-template
- Контейнеры ng-container
- Условия *ngIf + *ngIf as
- Циклы *ngFor
- Стили [style.xxx]=«value» и [style.xxx.px]=«value»
- Условные классы [class.xxx]=«value»
- Observables {{name$}} c автоматической подпиской на значение (как пайп async)
Подробнее смотрите в тестах ng-template.spec.ts
Использование Eval
Для вычисления выражений в шаблонах используется eval с преферансом и куртизанками. Дело в том, что в шаблонах Angular доступ к переменным используется без привычного для JavaScript префикса this. Поэтому требуется вызвать eval(), у которого в области видимости лежат все переменные из объекта с данными. Сгенерировать такой код для eval() у меня не получилось, т.к. код вида
const data = { a: 1, b: () => 4 }; const expression = 'a+b()'; eval('a =1; b = ??;' + expression);
не позволяет передать функции
Решение было найдено путем создания функции, у которой параметры имеют имена полей объекта с данными:
const data = { a: 1, b: () => 4 }; let entries = [] for (let property in data ) { entries.push([property, data[property]]) } const params = entries.map(e => e[0]); const fun = new Function('code', ...params, `return eval(code)`); const args = entries.map(e => e[1]); const expression = 'a+b()'; const result = fun.call(undefined, expression , ...args);
P.S.: Я надеюсь в будущем, когда API нового компилятора Ivy стабилизируется, можно будет генерировать набор операторов для Ivy и создавать полноценные компоненты в динамике!
Ссылка на исходники
