Русский
Русский
English
Статистика
Реклама

Ng

Используем DI в Angular по максимуму концепция частных провайдеров

23.06.2020 16:19:11 | Автор: admin
В Angular очень мощный механизм Dependency Injection. Он позволяет передавать по вашему приложению любые данные, преобразовывать и переопределять их в нужных частях.

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

Тем не менее DI в приложениях используется достаточно скромно. Как правило, это внедрение сервисов или передача каких-то глобальных данных сверху вниз по дереву внедрения зависимостей.

В этой статье я хотел бы показать альтернативный вариант работы с полученными из DI данными. Цель: упростить компоненты, директивы и сервисы, которые эти данные используют.




Как обычно используется DI в Angular


Я ежедневно провожу ревью Angular-кода на работе и в опенсорсе. Как правило, в большинстве приложений DI сводится к следующей функциональности:

  1. Получить сущности Angular из дерева зависимостей: ChangeDetectorRef, ElementRef и проч.
  2. Получить сервис, чтобы использовать его в компоненте.
  3. Получить какой-нибудь глобальный конфиг по токену, который объявлен где-то наверху. Например, задать токен API_URL в рутовом модуле и получать его из DI в любом месте приложения при необходимости.

Реже встречаются случаи, когда разработчики идут дальше и преобразуют уже существующий глобальный токен в более удобную форму. Хороший пример такого преобразования токен на получение WINDOW из пакета @ng-web-apis/common.

Angular предоставляет токен DOCUMENT, чтобы можно было получить объект страницы из любого места приложения: ваши компоненты не зависят от глобальных объектов, легко тестировать, ничего не сломается при SSR.

Если вам регулярно нужен доступ до объекта WINDOW, можно написать такой токен:

import {DOCUMENT} from '@angular/common';import {inject, InjectionToken} from '@angular/core';export const WINDOW = new InjectionToken<Window>(    'An abstraction over global window object',    {        factory: () => {            const {defaultView} = inject(DOCUMENT);            if (!defaultView) {                throw new Error('Window is not available');            }            return defaultView;        },    },);


Когда кто-то запросит токен WINDOW в первый раз из дерева DI, выполнится фабрика токена он получит объект DOCUMENT у Angular и получит из него ссылку на объект window.

Далее я предлагаю рассмотреть иной подход к таким преобразованиям когда они выполняются непосредственно в providers компонента или директивы, который и инжектит результат.

Частные провайдеры


В нашей команде мы активно используем DI при работе с Angular и стали замечать, что зачастую нам нужно совершить какие-то преобразования полученных из DI данных перед их использованием. Фактически наш компонент нуждается в одних данных, а мы внедряем в него другие и выполняем логику преобразования внутри него.



Давайте посмотрим сразу на солидном примере. Эрин Коглар в своем докладе The Architecture of Components на большой международной конференции Angular Connect показала такой пример:


Если вам неудобно открывать и смотреть видео, то давайте распишу кейс здесь.

Имеем:
Компонент, который отвечает за показ информации по некой сущности организации.
Query-параметр роута, который указывает id организации, с которой мы работаем в текущий момент.
Сервис, который по id возвращает Observable с информацией об организации.

Что хотим сделать
Взять из query-параметров id организации, передать его в метод сервиса, а в ответ получить стрим с информацией об организации. Эту информацию вывести в компоненте.

Рассмотрим три способа добиться желаемого и разберем их.

Как делать не нужно
Иногда я встречаю вот такой стиль работы с данными в компонентах. Пожалуйста, не делайте так:
@Component({   selector: 'organization',   templateUrl: 'organization.template.html',   styleUrls: ['organization.style.less'],   changeDetection: ChangeDetectionStrategy.OnPush,})export class OrganizationComponent implements OnInit {   organization: Organization;   constructor(       private readonly activatedRoute: ActivatedRoute,       private readonly organizationService: OrganizationService,   ) {}   ngOnInit() {       this.activatedRoute.params           .pipe(               switchMap(params => {                   const id = params.get('orgId');                   return this.organizationService.getOrganizationById$(id);               }),           )           .subscribe(organization => {               this.organization = organization;           });   }}

Чтобы использовать полученные данные в шаблоне:
<p *ngIf="organization">   {{organization.name}} from {{organization.city}}</p>


Этот код будет работать, но у него есть ряд проблем:
Неопределенность поля organization: между моментом объявления поля при создании класса и присвоения ему значения пройдет некоторое время. Все это время в данном примере поле будет undefined. Мы либо нарушаем типизацию (такое возможно при отключенном strict у TypeScript), либо предусматриваем это в типе (organization?: Organization) и обрекаем себя на ряд дополнительных проверок.
Такой код тяжелее поддерживать. Завтра нам понадобится вытащить еще один параметр, мы продолжим заполнять ngOnInit, и код начнет постепенно превращаться в кашу с кучей неявных переменных и тяжелым для понимания потоком данных.
При подобном обновлении полей можно столкнуться с проблемами проверки изменений при использовании стратегии OnPush.

Сделаем хорошо
В докладе Эрин из видео, что я прикладывал выше, сделано хорошо. С ее вариантом получается примерно так:

@Component({   selector: 'organization',   templateUrl: 'organization.template.html',   styleUrls: ['organization.style.less'],   changeDetection: ChangeDetectionStrategy.OnPush,})export class OrganizationComponent {   readonly organization$: Observable<Organization> = this.activatedRoute.params.pipe(       switchMap(params => {           const id = params.get('orgId');           return this.organizationService.getOrganizationById$(id);       }),   );   constructor(       private readonly activatedRoute: ActivatedRoute,       private readonly organizationService: OrganizationService,   ) {}}

Чтобы использовать полученные данные в шаблоне:
<p *ngIf="organization$ | async as organization">   {{organization.name}} from {{organization.city}}</p>


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

Кроме того, поток данных становится более прозрачным: у нас есть только стрим, который создается в момент создания класса компонента. Когда он выдаст данные, будет показана информация в нашем шаблоне.

Сделаем еще круче: частные провайдеры


Давайте присмотримся внимательнее к прошлому решению.

На самом деле компонент не зависит от роутера и даже от OrganizationService. Он зависит от organization$. Но такой сущности в нашем дереве внедрения зависимостей нет, поэтому мы вынуждены выполнять преобразования в компоненте.

Но что если выполнить это преобразование перед тем, как данные попадут в компонент? Давайте напишем Provider специально для компонента, в котором и будут происходить необходимые преобразования.

Для удобства мы выносим провайдеры в отдельный файл рядом с компонентом, получая такую структуру файлов:



В файле organization.providers.ts будут находиться Provider для преобразования данных и токен для их получения в компоненте:

export const ORGANIZATION_INFO = new InjectionToken<Observable<Organization>>(   'A stream with current organization information',);По этому токену будет идти стрим с необходимой компоненту информацией:export const ORGANIZATION_PROVIDERS: Provider[] = [   {       provide: ORGANIZATION_INFO,       deps: [ActivatedRoute, OrganizationService],       useFactory: organizationFactory,   },];export function organizationFactory(   {params}: ActivatedRoute,   organizationService: OrganizationService,): Observable<Organization> {   return params.pipe(       switchMap(params => {           const id = params.get('orgId');           return organizationService.getOrganizationById$(id);       }),   );}


Определим массив провайдеров для компонента. Значение для токена ORGANIZATION_INFO получим из фабрики, в которой сделаем необходимое преобразование данных.

Примечание по работе DI deps позволяет взять из дерева DI необходимые сущности и передать их как аргументы в фабрику значения токена. В них можно получить любую сущность из DI, в том числе и с использованием DI-декораторов, например:

{       provide: ACTIVE_TAB,       deps: [           [new Optional(), new Self(), RouterLinkActive],       ],       useFactory: activeTabFactory,}


Объявим providers в компоненте:

@Component({   ..   providers: [ORGANIZATION_PROVIDERS],})


И мы готовы к использованию данных в компоненте:

@Component({   selector: 'organization',   templateUrl: 'organization.template.html',   styleUrls: ['organization.style.less'],   changeDetection: ChangeDetectionStrategy.OnPush,   providers: [ORGANIZATION_PROVIDERS],})export class OrganizationComponent {   constructor(       @Inject(ORGANIZATION_INFO) readonly organization$: Observable<Organization>,   ) {}}

Класс компонента сводится к одной строчке с получением данных.

Шаблон остается прежним:
<p *ngIf="organization$ | async as organization">   {{organization.name}} from {{organization.city}}</p>


Что нам дает этот подход?

  1. Чистые зависимости: компонент не внедряет в себя и не хранит лишних сущностей. Он работает только с теми данными, которые ему нужны, при этом сам остается чистым и содержит только логику для отображения данных.
  2. Простота тестирования: мы можем легко протестировать сам провайдер, потому что его фабрика обычная функция. Нам легче тестировать компонент: в тестах нам не нужно будет собирать дерево зависимостей и подменять много сущностей мы просто передадим по токену ORGANIZATION_INFO стрим с мокаными данными.
  3. Готовность к изменению и расширению: если компонент будет работать с другим типом данных, мы поменяем лишь одну строчку. Если нужно будет изменить преобразование поменяем фабрику. Если потребуется добавить новых данных, то добавим еще один токен мы можем сложить сколько угодно токенов в наш массив провайдеров.


После внедрения этого подхода наши компоненты и директивы стали выглядеть гораздо чище и проще. Разделение логики преобразования данных и их отображения ускорило процесс их доработки и расширения. Время поимки багов также сократилось за счет того, что можно сразу срезать область проблемы: либо проблема в преобразовании данных, либо в том, как они выводятся пользователю.

Заключение


Описанный подход не избавит вас от всех проблем проектирования. Добавлять провайдеры на любую мелочь тоже не стоит: иногда код получается понятнее, если воспользоваться преобразованием в методе или использовать Pipe.

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

Что можно положить в механизм Dependency Injection в Angular?

26.08.2020 16:11:24 | Автор: admin
Почти каждый разработчик на Angular может найти в Dependency Injection решение своей проблемы. Это хорошо было видно в комментариях к моей прошлой статье. Люди рассматривали различные варианты работы с данными из DI, сравнивали их удобство для той или иной ситуации. Это здорово, потому что такой простой инструмент дает нам столько возможностей.

Но несколько человек отписались мне, что им тяжело понять DI и его возможности в Angular. В интернете не так уж много материалов о том, как использовать DI эффективно, и для многих разработчиков он сводится к работе с глобальными сервисами или передачей глобальных данных из корня приложения в компоненты.

Давайте посмотрим на этот механизм в Angular чуть глубже.




Знаете ли вы свои зависимости?


Иногда нелегко понять, сколько зависимостей имеет ваш код.

Например, взгляните на этот псевдокласс и посчитайте, сколько зависимостей он имеет:

import { API_URL } from '../../../env/api-url';import { Logger } from '../../services/logger'; class PseudoClass {   request() {       fetch(API_URL).then(...);   }    onError(error) {       const logger = new Logger();        logger.log(document.location, error);   }}

Ответ
fetch браузерное API, опираемся на глобальную переменную, ожидая, что она объявлена.

API_URL импортированные данные из другого файла тоже можно считать зависимостью вашего класса (зависимость от расположения файла).

new Logger() также импортированные данные из другого файла и пересоздания множества экземпляров класса, когда нам достаточно лишь одного.

document также браузерное API и завязка на глобальную переменную.


Ну и что же не так?


Например, такой класс тяжело протестировать, так как он зависит от импортированных данных из других файлов и конкретных сущностей в них.

Другая ситуация: document и fetch будут без проблем работать в вашем браузере. Но если однажды вам потребуется перенести приложение в Server Side Rendering, то в nodejs окружении необходимых глобальных переменных может не быть.

Так и что же за DI и зачем он нужен?


Механизм внедрения зависимостей управляет зависимостями внутри приложения. В принципе, для нас, как для Angular-разработчиков, эта система довольно простая. Есть две основные операции: положить что-то в дерево зависимостей или получить что-то из него.

Механизм внедрения зависимостей управляет зависимостями внутри приложения. В принципе, для нас, как для Angular-разработчиков, эта система довольно простая. Есть две основные операции: положить что-то в дерево зависимостей или получить что-то из него.

Если хотите рассмотреть DI с более теоретической стороны, почитайте о принципе инверсии управления. Также можете посмотреть интересные видеоматериалы по теме: серия видео про IoC и DI у Ильи Климова на русском или небольшое видео про IoC на английском.

Вся магия возникает от порядка, в котором мы поставляем и берем зависимости.

Схема работы областей видимости в DI:


Что мы можем положить в DI?


Первая из операций с DI что-то положить в него. Собственно, для этого Angular позволяет нам прописывать providers-массив в декораторах наших модулей, компонентов или директив. Давайте посмотрим, из чего этот массив может состоять.

Providing класса


Обычно это знает каждый разработчик на Angular. Это тот случай, когда вы добавляете в приложение сервис.

Angular создает экземпляр класса, когда вы запрашиваете его в первый раз. А с Angular 6 мы можем и вовсе не прописывать классы в массив providers, а указать самому классу, в какое место в DI ему встать с providedIn:

providers: [   {       provide: SomeService,       useClass: SomeService   },   // Angular позволяет сократить эту запись как самый частый кейс:   SomeService]


Providing значения


Также через DI можно поставлять константные значения. Это может быть как простая строка с URL вашего API, так и сложный Observable с данными.

Providing значения обычно реализуется в связке с InjectionToken. Этот объект ключ для DI-механизма. Сначала вы говорите: Я хочу получить вот эти данные по такому ключу. А позже приходите к DI и спрашиваете: Есть ли что-то по этому ключу?

Ну и частый кейс проброс глобальных данных из корня приложения.

Лучше посмотреть это сразу в действии, поэтому давайте взглянем на stackblitz с примером:



Итак, в примере мы получили зависимость из DI вместо того, чтобы импортировать ее как константу из другого файла напрямую. И почему нам от этого лучше?

  • Мы можем переопределить значение токена на любом уровне дерева DI, никак не меняя компоненты, которые его используют.
  • Мы можем мокировать значение токена подходящими данными при тестировании.
  • Компонент полностью изолирован и всегда будет работать одинаково, независимо от контекста.


Providing фабрики


На мой взгляд, это самый мощный инструмент в механизме внедрения зависимостей Angular.

Вы можете создать токен, в котором будет результат комбинации и преобразования значений других токенов.

Вот еще один stackbitz с подробным примером создания фабрики со стримом.



Можно найти много кейсов, когда providing фабрики экономит время или делает код более читабельным. Иногда мы внедряем зависимости в компоненты только ради того, чтобы совместить их или преобразовать в совершенно иной формат. В предыдущей статье я рассматривал этот вопрос подробнее и показывал альтернативный подход к решению таких ситуаций.

Providing существующего экземпляра


Не самый частый случай, но этот вариант бывает очень полезным инструментом.

Вы можете положить в токен сущность, которая уже была создана. Хорошо работает в связке с forwardRef.

Посмотрите еще один пример со stackblitz с директивой, которая имплементирует интерфейс и подменяет собой другой токен через useExisting. В этом примере мы хотим переопределить значение токена только в области видимости DI для дочерних компонентов элемента, на котором висит директива. Причем директива может быть любой главное, что она реализует необходимый интерфейс.



Хитрости с DI-декораторами


DI-декораторы позволяют сделать запросы к DI более гибкими.

Если вы не знаете все четыре декоратора, советую почитать вот эту статью на Медиуме. Статья на английском, но там очень классные и понятные визуализации по теме.

Не многие также знают, что DI-декораторы можно использовать в массиве deps, который готовит аргументы для фабрики в providers.

providers: [   {     provide: SOME_TOKEN,     /**      * Чтобы фабрика получила декорированное значение, используйте такой      * [new Decorator(), new Decorator(),..., TOKEN]      * синтаксис.      *      * В этом случае вы получите null, если не будет значения для      * OPTIONAL_TOKEN      */     deps: [[new Optional(), OPTIONAL_TOKEN]],     useFactory: someTokenFactory   } ]


Фабрика токена


Конструктор InjectionToken принимает два аргумента.

Второй аргумент объект с конфигурацией токена.

Фабрика токена это функция, которая вызывается в момент, когда кто-то запрашивает этот токен в первый раз. В ней можно посчитать некое стандартное значение для токена или даже обращаться к другим DI-сущностям через функцию inject.

Посмотрите пример реализации функционала стрима нажатия кнопок, но уже на фабрике токена.



Заключение


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

Надеюсь, этой статьей мне удалось дать вам фундамент, на основе которого вы сможете придумать собственные решения для упрощения работы с данными в ваших приложениях и библиотеках.
Подробнее..

Упрощаем работу с Angular с помощью taiga-uicdk 5 наших лучших практик

11.05.2021 12:14:21 | Автор: admin

CDK базовый пакет библиотеки компонентов Taiga UI. Он не имеет никакой привязки к визуальной составляющей библиотеки, а скорее служит набором полезных инструментов для упрощения создания Angular-приложений.

Среди всех этих инструментов я выделил мою пятерку фаворитов. Я использую их во всех своих проектах и уже давно не представляю, как писать на Angular без них, потому что они ежедневно экономят мне массу времени.

Дисклеймер о весе библиотеки

Перед написанием статьи хотелось бы ответить на вопрос: Для чего тащить в бандл целый мультитул, когда мне нужна пара функций?

По результатам bundlephobia мы получим следующую картинку:

23 КБ результат не самый страшный, но и не очень приятный. Но все сущности наших библиотек лежат в отдельных Secondary Entry Point, что делает их полностью tree shakable. Это значит, что такой объем в бандле мы получим только в случае импорта и использования всех сущностей библиотеки в нашем приложении. Если вы импортите пару сущностей только они и попадут к вам в бандл, добавив к нему в результате меньше 1 КБ.

tuiPure продвинутая мемоизация вычислений

Это декоратор, который можно вешать на геттеры и чистые методы классов. Давайте разберем оба сценария.

Как геттер

Можно повесить декоратор tuiPure на геттер. В таком случае он позволяет сделать отложенные вычисления.

Пример 1. Скрываем и показываем сороковое число Фибоначчи, когда пользователь нажимает на кнопку

// template<div *ngIf="show">fibonacci(40) = {{ fibonacci40 }}</div>// component@tuiPureget fibonacci40(): number {  return calculateFibonacci(40);}

Когда мы запросим число в первый раз, функция посчитает сороковой элемент Фибоначчи. Все последующие запросы будут сразу возвращать ранее посчитанное число.

Пример 2. У нас есть компонент pull to refresh, который эмулирует поведение под iOS и Android. Один из его стримов вызывается только для Андроида, а для iOS нет. Завернем его в getter с pure, и ненужный Observable не будет создан для iOS.

<tui-mobile-ios-loader   *ngIf="isIOS; else angroidLoader"></tui-mobile-ios-loader><ng-template #angroidLoader>   <tui-mobile-android-loader       [style.transform]="loaderTransform$ | async"   ></tui-mobile-android-loader></ng-template>
@tuiPureget loaderTransform$(): Observable<string> {    return this.pulling$.pipe(        map(distance => translateY(Math.min(distance, ANDROID_MAX_DISTANCE))),    );}

Также можно обратиться к changes от ContentChild / ContentChildren: если мы вызываем такой геттер из шаблона, то уже можем быть уверены, что content готов. При соблюдении порядка также это можно провернуть и с ViewChild / ViewChildren.

Как метод

На метод тоже можно повесить декоратор @tuiPure. Тогда он будет работать следующим образом: при первом вызове метода посчитает значение и вернет его. Все последующие вызовы с теми же самыми аргументами будут возвращать уже посчитанный результат. Если аргумент изменится результат пересчитается.

Это полезно не только для сложных вычислений, но и в связке с геттером для создания непримитивов объектов или массивов.

get filteredItems(): readonly string[] {   return this.computeFilteredItems(this.items);}@tuiPureprivate computeFilteredItems(items: readonly string[]): readonly string[] {   return items.filter(someCondition);}

В этом случае можно вызвать геттер из шаблона, один раз отфильтровать items, и он будет возвращать тот же самый массив до тех пор, пока this.itemsне изменится. Это поможет избежать лишних операций пересоздания массива на каждый чих проверки изменений, а также проблем из-за постоянных изменений ссылки на массив при передаче дальше. При этом нам не придется, например, самим синхронизировать состояние в ngOnChanges, если this.items инпут компонента.

Документация по tuiPure

*tuiLet

Это простая структурная директива для объявления локальных переменных в шаблонах.

<ng-container *tuiLet="timer$ | async as time">   <p>Timer value: {{time}}</p>   <p>       It can be used many times:       <tui-badge [value]="time"></tui-badge>   </p>   <p>       It subsribed once and async pipe unsubsribes it after component destroy   </p></ng-container>

Вместо *tuiLetможно использовать *ngIf если вам не нужно показывать шаблон при falsy-значении (или если оно не предусмотрено). Но если вы работаете, например, с числами, то 0, скорее всего, является вполне адекватным значением. Тут и поможет *tuiLet

Документация по tuiLet

Метапайпы tuiMapper и tuiFilter

Мы создали пайп, чтобы не создавать другие пайпы, tuiMapper.

Он очень простой. Это pure-пайп, который принимает в себя чистую функцию для преобразования и произвольное количество аргументов к ней. В шаблоне это выглядит так:

{{value | tuiMapper : mapper : arg1 : arg2 }}

Также удобно и преобразовывать данные для инпутов компонентов в шаблоне или использовать через *ngIf / *tuiLet:

<div    *ngIf="item | tuiMapper : toMarkers : itemIsToday(item) : !!getItemRange(item) as markers"    class="dots">    <div class="dot" [tuiBackground]="markers[0]"></div>    <div        *ngIf="markers.length > 1"        class="dot"        [tuiBackground]="markers[1]"    ></div></div>

Добавление цветных маркеров-точек в календарях @taiga-ui/core

Чистые пайпы кэшируют предыдущее значение и не пересчитываются на каждый цикл проверки изменений, что полностью решает проблемы производительности. А такая передача чистых функций извне позволяет орудовать хендлерами, подменять их или даже передавать в компонент извне. Кроме того, нам не нужно создавать отдельный пайп под каждый кейс, если он нужен нам лишь в одном месте.

А еще у нас есть tuiFilter для удобства фильтрации массивов. Фактически это частный случай маппера, но нам нужен довольно часто. Поскольку это чистый пайп, то никаких проблем с производительностью из-за пересоздания массивов тут нет.

Документация на mapper / документация на filter

destroy$

Это Observable-based сервис, который упрощает процесс отписки в компонентах и директивах.

@Component({   // ...   providers: [TuiDestroyService],})export class TuiDestroyExample {   constructor(     @Inject(TuiDestroyService)      private readonly destroy$: Observable<void>   ) {}   //    subscribeSomething() {       fromEvent(this.element, 'click')           .pipe(takeUntil(this.destroy$))           .subscribe(() => {               console.log('click');           });   }}

Все что нам нужно добавить его в providers компонента и заинжектить в конструкторе. Я предпочитаю писать типы сущностей из DI, которые минимально необходимы в компоненте. Здесь это Observable<void>. Но можно писать и покороче:

constructor(private destroy$: TuiDestroyService) {}

Кстати, сервис в такой ситуации привязывается не к лайфсайклу компонента, а к его DI Injectorу. Это может помочь в ситуации, когда нужно подписаться в сервисе или внутри DI фабрики. Такие кейсы довольно редки, но зато TuiDestroyService в них буквально спасает например, когда мы хотели дергать markForCheck из фабрики токена в статье о DI фокусах для проброса данных.

Ссылка на документацию

Плагины ng-event-plugins

Фактически это внешняя библиотека ng-event-plugins, которая поставляется вместе с cdk (прямая зависимость, которую не нужно устанавливать отдельно). Она добавляет свои обработчики к менеджеру плагинов Angular. В ней есть несколько очень полезных плагинов, которые добавляют ряд возможностей в шаблоны компонентов.

Например, .stopи .preventпозволяют декларативно делать stopPropagation и preventDefault на любой прилетающий ивент.

Было:

<some-input (mousedown)="handle($event)">    Choose date</some-input>
export class SomeComponent {   //    handle(event: MouseEvent) {      event.preventDefault();      event.stopPropagation();      this.onMouseDown(event);   }}

Стало:

<some-input (mousedown.prevent.stop)="onMouseDown()">    Choose date</some-input>

Или модификатор .silent который позволяет не запускать проверку изменений на событие:

<div (mousemove.silent)="onMouseMove()">    Callbacks to mousemove will not trigger change detection</div>

Можно отслеживать ивенты в capture-фазе с помощью .capture:

<div (click.capture.stop)="onClick()">    <div (click)="never()">Clicks will be stopped before reaching this DIV</div></div>

Все это работает и с @HostListenerами, и с кастомными событиями. Вы можете почитать подробнее в документации ng-event-plugins.

Итого

Мы посмотрели ряд сущностей пакета @taiga-ui/cdk. Надеюсь, какие-нибудь из них вам приглянулись и тоже будут помогать во всех дальнейших проектах!

Кстати, у меня еще есть статья про саму библиотеку Taiga UI, в которой описаны остальные пакеты и общая философия библиотеки.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru