Как правильно использовать EventEmitter?
Я тоже читал это вопрос где решение предлагает передать EventEmitter дочернему элементу и подписаться на него.
Тогда мой вопрос: должен ли я или не должен вручную подписываться на EventEmitter? Как мне его использовать?
4 ответа
TL; DR :
Нет, не подписывайтесь на них вручную, не используйте их в сервисах. Используйте их, как показано в документации, только для генерации событий в компонентах. Не побеждайте абстракцию angular.
Ответ:
Нет, не следует подписываться на него вручную.
[. ] EventEmitter на самом деле является абстракцией Angular, и его следует использовать в основном только для генерации пользовательских событий в компонентах. В противном случае просто используйте Rx, как если бы это была любая другая библиотека.
Это четко указано в документации EventEmitter.
Используйте директивы и компоненты для создания настраиваемых событий.
Что плохого в его использовании?
Все вышеизложенное более ясно видно в этой статье Уорда Белла comment (рекомендуется прочитать статью, а ответ на этот комментарий). Цитата для справки
НЕ рассчитывайте, что EventEmitter продолжит оставаться наблюдаемым!
НЕ рассчитывайте на присутствие этих наблюдаемых операторов в будущем!
Они скоро станут устаревшими и, вероятно, будут удалены перед выпуском.
Используйте EventEmitter только для привязки событий между дочерним и родительским компонентами. Не подписывайтесь на это. Не вызывайте ни один из этих методов. Звоните только eve.emit()
Его комментарий согласуется с комментарием Роба давным-давно.
Итак, как им правильно пользоваться?
Просто используйте его для отправки событий из вашего компонента. Взгляните на следующий пример.
Как не использовать?
Надеюсь, эти два простых примера прояснят правильное использование EventEmitter.
Нет: ноно и нет: да. Истина посередине. И нет причин для опасений из-за следующей версии Angular.
С логической точки зрения, если у вас есть компонент, и вы хотите сообщить другим компонентам о том, что что-то происходит, событие должно быть запущено, и это можно сделать так, как вы (разработчик) думаете, что это должно быть сделано. Я не вижу причины, по которой его не использовать, и не вижу причины, по которой следует использовать его любой ценой. Также имя EventEmitter подсказывает мне, что событие происходит. Обычно я использую его для важных событий, происходящих в Компоненте. Я создаю службу, но создаю файл службы внутри папки компонентов. Таким образом, мой служебный файл становится своего рода диспетчером событий или интерфейсом событий, поэтому я могу сразу понять, на какое событие я могу подписаться в текущем компоненте.
Если вы хотите иметь межкомпонентное взаимодействие, вам нужно знать, что такое @Input, @Output, EventEmitter и Subjects.
Если между компонентами существует связь родитель-потомок или наоборот, мы используем @input и @output с эмиттером событий.
@output испускает событие, и вам нужно передать его с помощью эмиттера событий.
Да, продолжайте и используйте его.
Как также указано в документах:
Использует Rx.Observable, но предоставляет адаптер для его работы, как указано здесь: https://github.com/jhusain / observable-spec
Как только эталонная реализация спецификации станет доступной, переключитесь на нее.
Часто бывает полезно иметь эмиттер, который отправляет события определенного типа. Если это ваш вариант использования, дерзайте. Если / когда доступна эталонная реализация спецификации, на которую они ссылаются, она должна быть заменой, как и любой другой полифилл.
Все жестко сформулированные предсказания гибели и мрака, похоже, проистекают из одного комментария Stack Overflow от одного разработчика к предварительной версии Angular 2.
Тестируем Angular приложение. Часть 1. Тестирование компонента (+ EventEmitter)
В этой статье, я опишу как написать тесты для компонентов Angular приложения, используя инструменты которые предлагает нам Angular из коробки.
Вступление
По умолчанию, Angular предлагает средства для тестирования приложенния, такие как Karma, Jasmine, Protractor. В данной статье будем использовать связку из коробки Karma + Jasmine. Karma это так называемый тест раннер который позволяет нам настраивать то на каких устройствах или браузерах будут запускатся тесты и какие тестовые фреймворки и плагины, будут участвовать в тестах. Jasmine это BDD фрейворк для тестирования javascript кода.
Начальное приложение
Компонент простой и состоит из нескольких методов, первый sayHello принимает имя и возвращает строковое значение приветствия, второй giveLangCode возвращает массив значений. Третий метод setColor присваивает значение цвета переменной и «эмиттит» его значение.
Создание spec файла и запуск Karma
С компонентом определились, теперь создадим файл hello.spec.ts в папке с компонентом и добавим в него следующее содержимое:
Тут мы экпортировали наш тестируемый компонент Hello и добавили функцию обертку describe для нашего тестового набора а также объявили переменную hello для инициализации нашего компонента.
Так как сами тесты мы еще не добавили то и отчета об их прохождении, в окне не будет. Осталяем запущенным тест раннер и приступаем к написанию тестов.
Добавление тестов
Возвращаемся в наш файл hello.spec.ts и через конструкцию beforeEach добавляем инициализацию компонента для каждого теста (это создаст изолированность наших тестов друг от друга).
Затем через конструкцию it добавлем тест нашего первого метода sayHello
Тут все просто, тест вызывает метод sayHello и передает тестовое значение в качестве аргумента, затем проверяет что в ответе приходит то что должен возвращать метод.
Проверям окно тест раннера и наш тест должен появится в отчете с успешным статусом.
Теперь напишем второй тест для метода giveLangCode в котором проверим содержание всех трех значений возращаемого массива.
Проверяем окно тест раннера и видим что второй тест тоже успешно пройден.
Дополнительно можно добавить еще проверки для переменных компонента которые объявляются вначале,такие как проверка значения и типа (toEqual, toBeInstanceOf). Более подробно можно почитать в документации к Jasmine.
Тест EventEmitter’a (бонус)
Тут тест вызывает наш эмиттер и подписывается на его значение, которое помещается в переменную result (объявили ее в начале теста, как пустую строку). Затем тест, вызывает метод setColor и проверяет что наш результат( result ) изменился c пустой строки на то которое присваивается внутри метода setColor (в нашем случае ‘Black’).
Проверяем наш тест раннер и видим что наш тест прошел успешно. Таким образом видим что наш colorEmitter работает корректно.
Заключение
В этой статье мы расмотрели варианты тестирования компонента с методами, возвращающими различные типы данных и протестировали EventEmitter. В следующей части, займемся тестированием сервисов и форм.
Обмен данными между компонентами Angular
Проблема
В результате работы с фреймворком Angular, мы декомпозируем наше web-приложение. И по этому у нас возникает ситуация, когда нам нужно передавать данные между компонентами.
@Input()
Осталось только передать параметр title в дочерний компонент из родительского:
@Output()
Благодаря директиве @Output() мы можем привязаться к событиям дочернего компонента. На первый взгляд не очень понятно, так что давайте рассмотрим пример:
Но есть одно «но», мы не передали никакую информацию в родительский компонент. Рассмотрим уже другой вариант в котором мы будем передавать информацию в родительский компонент:
Давайте рассмотрим список внесенных изменений:
Добавили тип передаваемых данных new EventEmitter boolean >()
В метод emit передали нужную информацию this.buttonClick.emit(change)
@Input() и @Output() достаточно удобно, но не в ситуации, когда на надо передать данные в дочерний компонент, дочернего компонента и т.д., или же компоненты находятся в разных частях приложения.
Сервисы и RxJs
Одними из лучших вариантов обмена данных остаются сервисы. Создадим простой сервис который бы мог оповещать компоненты про изменение данных, а так же передавать значения:
Мы передали результат Math.random() и пустили его по всем подписчикам. Теперь посмотрим как следить за этими изменениями:
Мы можем подписаться на множество Subject из компонента, подписаться на один и тот же Subject из разных компонентов.
Кастомный EventEmitter в Angular
Не секрет, что у новичков в Angular могут возникнуть проблемы с передачей данных между компонентами, потому что не всегда удается использовать Input и Output декораторы. Предлагаю вашему вниманию реализацию паттерна EventEmitter на Angular.
Для начала определим наш EventEmitter сервис, где разместим наш eventEmitterSubject$, принимающий поле action(тип события) и не обязательное поле payload(данные, которые мы хотим передать), для передачи событий и подписки на них.
Теперь можно определить и дочерний компонент, который будет вызывать события у родительского, через наш EventEmitter сервис, а конкретно вызывать метод next, у eventEmitterSubject$.
И наконец, на эти события можно подписаться в родительском компоненте.
Обратите внимание, что можно подписываться, как на все события, так и на конкретные с помощью оператора filter, также надо не забыть отписатся от подписки при дестроее компонента, чтобы не было утечек памяти.
Надеюсь эта статься поможет начинающим разработчикам лучше понять, как могут взаимодействовать компоненты между собой с использованием cервисов и rxjs.
О песочнице
Это «Песочница» — раздел, в который попадают дебютные посты пользователей, желающих стать полноправными участниками сообщества.
Если у вас есть приглашение, отправьте его автору понравившейся публикации — тогда её смогут прочитать и обсудить все остальные пользователи Хабра.
Чтобы исключить предвзятость при оценке, все публикации анонимны, псевдонимы показываются случайным образом.
О модерации
Не надо пропускать:
9. Уроки Node.js. События, EventEmitter и Утечки Памяти

Следующий объект, который нас интересует, это EventEmitter или, как его иногда называют, ЕЕ. EventEmitter представляет собой основной объект, реализующий работу с событиями в Node.js. Большое количество других встроенных объектов, которые генерируют события, ему наследуют. Для того чтобы воспользоваться EventEmitter достаточно подключить модуль “events”встроенный и взять с него соответствующее свойство (создадим ee.js) :
После чего я могу создать новый объект:
У него есть методы для работы с событиями. Первый метод – это подписка:
оn – имя события, function – обработчик. Я могу указать много подписчиков, и все они будут вызваны в том же порядке, в котором назначены.
Второй основной метод – это emit:
Он генерирует события и передает данные. Эти данные попадают в функцию обработчика. Соответственно, если предположить, что мы пишем веб сервер, то в одном месте кода будут обработчики запроса. Веб сервер при запросе что-то с ним делает:
А затем, в другом месте кода, например, в обработчике входящих соединений, будет сервер emit, который генерирует события.
Давайте запустим этот код:
Как видите, оба события были обработаны. Сначала первым обработчиком, а потом вторым.
Подробное описание различных методов для работы с событиями вы, конечно, найдете в документации, а мы с вами остановимся на том, что принципиально отличает работу с событиями в Node.js по сравнению с работой с событиями в браузерах. Первое отличие мы сразу же сможем увидеть из разбора этого примера:
Если браузерные обработчики срабатывают в произвольном порядке, то Node обработчики точно в том порядке, в котором были назначены. То есть, если у меня есть какие-то обработчики, то назначая следующие, я точно уверен, он сработает после предыдущих.
Еще одно отличие в том, что в браузере я никак не могу получить список обработчиков, в которых назначен определенный элемент. А в Node.js это сделать легко: emitter.listeners(eventName) возвращает все обработчики на данное событие. А emitter.listenerCount(eventName) позволяет получить их общее количество.
Следующее, наиболее важное, отличие состоит в том, что в EventEmitter специальным образом обрабатывается событие с названием error:
Если где-либо происходит emit этого события, и у него нет обработчика, то EventEmitter генерирует исключения. Таким образом, с виду такой безобидный emit “повалит“ весь процесс. Исключения генерируются встроенного типа // throw TypeError, если в вот таком виде:
А если есть какой-нибудь объект в аргументах, например:
Если в emit передать объект, который будет описать что именно за ошибка была, то он будет передан в обработчик, там можем его разобрать и произвести какие-то действия, чтобы обработать.
Последняя особенность EventEmitter, о которой сейчас пойдет речь, это встроенное средство для борьбы с утечками памяти. Для разбора у нас есть пример:
Здесь каждые 200 миллисекунд создается новый объект типа request и выводится текущее поедание памяти. Объект типа request, в реальной жизни это может быть запрос от клиента, ну а здесь это просто некий объект, у которого есть поле bigData, в котором содержится что-то жирное, чтобы было видно, сколько памяти, на самом деле, съедается. Соответственно, если много таких объектов будет в памяти, то есть они будут тоже очень много. Ну, и еще у этого объекта есть пара методов. Пользоваться ими мы не будем, а просто посмотрим, что происходит с памятью, когда создается много таких объектов.
Итак, запускаем node leak.js. Легко увидеть, что память вырастает, а потом очищается. Затем опять вырастает и очищается. Пока у нас все хорошо. Это нормальный режим функционирования Node.js. Ведь в данном случае request – это локальная переменная данной функции:
После окончания работы функции она нигде не сохраняется. Этот объект больше не нужен, и память из-под него можно очистить.
Теперь немного расширим этот пример. Добавим объект источника данных, который назовем db.
Он может посылать какую-то информацию, которую request может, в свою очередь, присылать клиенту:
Изменение небольшое. Посмотрим, к чему это приведет при запуске кода. Мы видим какой-то Warning. Память постоянно растет. В чем же дело? Для того, чтобы это понять немного глубже познакомимся с логарифмом работы EventEmitter, а именно с тем, как работают эти события, что происходит, когда вызывают db.on data. Информацию о том, что я поставил обработчик, нужно где-то запомнить. Действительно, она запоминается в специальном свойстве объекта db. В этом свойстве находятся все обработчики события, которые назначены. Когда происходит вызов emit, они из него берутся и вызываются. Теперь уже можно понять, отчего возникла утечка. Несмотря на то, что request здесь больше не нужен, эта функция находится в свойствах объекта db. И получается так, что каждый request, который создается, сохраняет там внутри эту функцию. А эта функция ссылается через замыкание на вообще весь объект, и получается, что этот обработчик привязывает request к db. Пока живет db, будет жить и request. Если db живет очень долго, то и request тоже будет жить очень долго. Происходящее можно легко увидеть, если добавить и запустить код еще раз.
Стоп! Мы увидели достаточно. Вот объект db, и есть свойство events, в котором находятся обработчики:
И оно, действительно, все время увеличивается по размеру. Сначала было маленькое, потом функций все больше и больше. И каждая функция через замыкание тянет за собой весь объект request.
Есть еще и warning в нашей console. Оказывается, в EventEmitter есть по умолчанию максимальное число обработчиков, которые можно назначить. Оно равно 10. Как только это число превышается, то он выводит предупреждение о том, что может быть утечка памяти, которая, в нашем случае, как раз и произошла. Что делать? Как вариант, можно, например, после окончания обработки запроса убрать обработчики на событие data. Для этого нужно код немножко переписать, добавить вызов метода end в конце, и при таком вызове будет все хорошо.
Никакой утечки памяти не происходит. Когда такой сценарий наиболее опасен? В тех случаях, если по какой-то причине максимальное количество обработчиков отключают. То есть, делают вызов
Предполагая, что много кто может подписываться на эти события. Действительно, бывают такие источники события, для которых возможно очень много подписчиков, и нужно отменить этот лимит. Соответственно, лимит отменяют, а убирать обработчики забывают. Это приводит к тому, что Node растет и растет в памяти.
Как отследить эти утечки? Это достаточно проблематично. Может помочь модуль heapdump, который позволяет делать снимок памяти Node.js и потом анализировать его в Chrome. Но лучшая защита – это думать, что делаешь, когда привязываешь короткоживущие объекты в случай события долгоживущих. А также помнить о том, что может понадобиться от них отвязаться, чтобы память была очищена.
Итак, EventEmitter – это один из самых важных и широко используемых объектов в Node.js. Сам по себе он используется редко. В основном используются наследники этого класса, такие как объект запроса, объект сервера и много всего другого. Мы с этим столкнемся в ближайшем будущем. Для генерации события используется вызов emit:
Ему передаются названия событий и какие-то аргументы, данные. При этом, он вызывает обработчики, назначенные через on.
EventEmitter гарантирует, что обработчики будут вызваны в том же порядке. При этом, в отличие от браузерных обработчиков, всегда можно проверить, есть ли какие-то обработчики на определенное событие. Кроме того, сам метод emit, если событие было обработано, возвращает true а иначе false. Используется это достаточно редко.
В EventEmitter есть специальное событие, которое называется error:
Если на это событие нет обработчиков, то это приводит к тому, что EventEmitter сам делает throw. Казалось бы, зачем? Но, как мы скоро убедимся, это решение очень мудрое и полезное. Потому что многие встроенные объекты Node.js сообщают о своих ошибках именно так, через emit(error).
И без такого throw их было бы очень легко пропустить, забыть о них и потом долго искать что же и где случилось.
И, наконец, последнее, в EventEmitter есть встроенные средства по борьбе с утечкой памяти. Сейчас они нам не очень нужны, но в дальнейшем, когда мы будем делать проект на Node.js, они нам еще пригодятся.
Вы можете скачать код данного урока в репозитории.




