Введение
Angular предоставляет удобный декларативный способ подписки на события в шаблоне, с помощью синтаксиса (eventName)=»onEventName($event)» . Вместе с политикой проверки изменений ChangeDetectionStrategy.OnPush подобный подход автоматически запускает цикл проверки изменений только по интересующему нас пользовательскому вводу. Иными словами, если мы слушаем (input) событие на элементе, то проверка изменений не будет запускаться, если пользователь просто кликает по полю ввода. Это значительно улучшает
производительность, по сравнению с политикой по умолчанию ( ChangeDetectionStrategy.Default ). В директивах мы также можем подписаться на события на хост-элементе через декоратор @HostListener(‘eventName’) .
В моей практике нередко встречаются случаи, когда обработка конкретного события требуется только при выполнении какого-либо условия. т.е. обработчик выглядит примерно так:
class ComponentWithEventHandler < // . onEvent(event: Event) < if (!this.condition) < return; >// Handling event . > >
Даже если условие не выполнилось и никакие действия по факту не произошли, цикл проверки изменений всё равно будет запущен. В случае с частыми событиями, вроде scroll или mousemove , это может негативно отразиться на производительности приложения.
В библиотеке UI компонентов, над которой я работаю, подписка на mousemove внутри выпадающих меню вызвала пересчет изменений вверх по всему дереву компонентов на каждое движение мыши. Следить за мышью было необходимо для реализации корректного поведения меню, но это явно стоило оптимизировать. Подробнее об этом ниже.
Подобные моменты особенно важны для универсальных UI элементов. Их на странице может присутствовать множество, а приложения бывают очень сложными и требовательными к производительности.
Исправить ситуацию можно, делая подписку на события в обход ngZone , например, с помощью Observable.fromEvent и запускать проверку изменений руками, вызывая changeDetectorRef.markForCheck() . Однако, это добавляет кучу лишней работы и лишает возможности использовать удобные встроенные инструменты Angular.
Ни для кого не секрет, что Angular позволяет подписываться на так называемые псевдособытия, уточняя какие именно события нас интересуют. Мы можем написать (keydown.enter)=»onEnter($event)» и обработчик (а вместе с ним и цикл проверки изменений) вызовется только при нажатии клавиши Enter .Остальные нажатия будут игнорироваться. В этой статье мы разберёмся, как можно воспользоваться тем же подходом, что и Angular, для оптимизации обработки событий. А в качестве бонуса добавим модификаторы .prevent и .stop , которые будут отменять поведение по умолчанию и останавливать всплытие события автоматически.
EventManagerPlugin
Для обработки событий Angular использует класс EventManager . Он имеет набор так называемых плагинов, расширяющих абстрактный EventManagerPlugin и делегирует обработку подписки на событие в тот плагин, который поддерживает данное событие (по имени). Внутри Angular предусмотрены несколько плагинов, среди которых обработка HammerJS событий и плагин, отвечающий за составные события, вроде keydown.enter . Это внутренняя реализация Angular, и данный подход может измениться. Однако, с момента создания issue про переработку этого решения прошло уже 3 года, и никаких подвижек в этом направлении не произошло:
Что интересно в этом для нас? Несмотря на то, что эти классы внутренние и отнаследоваться от них нельзя, токен, отвечающий за внедрение зависимостей для плагинов — публичный. Это значит, мы можем писать свои плагины и расширять ими встроенный механизм обработки событий.
Если посмотреть исходный код EventManagerPlugin , можно заметить, что мы и не сможем от него отнаследоваться, в большинстве своём он абстрактный и реализовать свой класс, отвечающий его требованиям не составляет труда:
Грубо говоря, плагин должен уметь определять, работает ли он с данным событием и должен уметь добавлять обработчик события и глобальные обработчики (на body , window и document ). Нас будут интересовать модификаторы .filter , .prevent и .stop . Для привязки их к нашему плагину реализуем обязательный метод supports :
const FILTER = '.filter'; const PREVENT = '.prevent'; const STOP = '.stop'; class FilteredEventPlugin < supports(event: string): boolean < return ( event.includes(FILTER) || event.includes(PREVENT) || event.includes(STOP) ); >>
Так EventManager поймёт, что события, в имени которых присутствуют определённые модификаторы, нужно передать на обработку в наш плагин. Затем нам нужно реализовать добавление обработчиков на события. Глобальные обработчики нас не интересуют, в их случае необходимость в подобных инструментах встречается существенно реже, а реализация была бы сложнее. Поэтому мы просто уберём из имени события наши модификаторы и вернём его в EventManager , чтобы он подобрал правильный встроенный плагин для обработки:
class FilteredEventPlugin < supports(event: string): boolean < // . >addGlobalEventListener( element: string, eventName: string, handler: Function, ): Function < const event = eventName .replace(FILTER, '') .replace(PREVENT, '') .replace(STOP, ''); return this.manager.addGlobalEventListener(element, event, handler); >>
В случае с событием на обычном элементе, нам нужно написать свою логику. Для этого мы обернём обработчик в замыкание и передадим событие без наших модификаторов назад в EventManager , вызвав его вне ngZone , чтобы избежать запуск цикла проверки изменений:
class FilteredEventPlugin < supports(event: string): boolean < // . >addEventListener( element: HTMLElement, eventName: string, handler: Function, ): Function < const event = eventName .replace(FILTER, '') .replace(PREVENT, '') .replace(STOP, ''); // Обёртка над нашим обработчиком const filtered = (event: Event) =>< // . >; const wrapper = () => this.manager.addEventListener(element, event, filtered); return this.manager.getZone().runOutsideAngular(wrapper); > /* addGlobalEventListener(. ): Function < . >*/ >
На данном этапе у нас есть: имя события, само событие и элемент, на котором оно слушается. Обработчик, который попадает сюда — не исходный обработчик, назначенный на это событие, а конец цепочки замыканий, созданных Angular для своих целей.
Одним решением могло бы стать добавление атрибута на элемент, отвечающего за то, вызывать обработчик или нет. Иногда для принятия решения необходимо проанализировать само событие: было ли отменено действие по умолчанию, какой элемент является источником события и т.д. Для этого недостаточно атрибута, нам нужно найти способ задать функцию-фильтр, получающую на вход событие и возвращающую true или false . Тогда мы могли бы описать наш обработчик следующим образом:
const filtered = (event: Event) => < const filter = getOurHandler(some_arguments); if ( !eventName.includes(FILTER) || !filter || filter(event) ) < if (eventName.includes(PREVENT)) < event.preventDefault(); >if (eventName.includes(STOP)) < event.stopPropagation(); >this.manager.getZone().run(() => handler(event)); > >;
Решение
Решением может стать синглтон сервис, хранящий соответствия элементов парам событие/фильтр и вспомогательные сущности для задания этих соответствий. Разумеется, на одном элементе может быть несколько обработчиков на одно и то же событие, но, как правило, это могут быть одновременно заданные @HostListener и обработчик, установленный на этот компонент в шаблоне на уровень выше. Эту ситуацию мы предусмотрим, другие же случаи нас мало интересуют ввиду своей специфичности.
Основной сервис довольно простой и состоит из мапы и пары методов для задания, получения и очистки фильтров:
export type Filter = (event: Event) => boolean; export type Filters = <[key: string]: Filter>; class FilteredEventMainService < private elements: Map= new Map(); register(element: Element, filters: Filters) < this.elements.set(element, filters); >unregister(element: Element) < this.elements.delete(element); >getFilter(element: Element, event: string): Filter | null < const map = this.elements.get(element); return map ? map[event] || null : null; >>
Таким образом, мы можем внедрить этот сервис в плагин и получать фильтр, передавая элемент и имя события. Для использования в связке с @HostListener добавим ещё один небольшой сервис, который будет жить вместе с компонентом и очищать соответствующие фильтры при его удалении:
export class EventFiltersService < constructor( @Inject(ElementRef) private readonly elementRef: ElementRef, @Inject(FilteredEventMainService) private readonly mainService: FilteredEventMainService, ) <>ngOnDestroy() < this.mainService.unregister(this.elementRef.nativeElement); >register(filters: Filters) < this.mainService.register(this.elementRef.nativeElement, filters); >>
Для добавления фильтров на элементы можно сделать аналогичную директиву:
class EventFiltersDirective < @Input() set eventFilters(filters: Filters) < this.mainService.register(this.elementRef.nativeElement, filters); >constructor( @Inject(ElementRef) private readonly elementRef: ElementRef, @Inject(FilteredEventMainService) private readonly mainService: FilteredEventMainService, ) <> ngOnDestroy() < this.mainService.unregister(this.elementRef.nativeElement); >>
В случае, если внутри компонента присутствует сервис для фильтрации событий, мы не будем позволять вешать на него фильтры через директиву. В конце концов, это почти всегда можно сделать, просто обернув компонент элементом, на который будет назначена наша директива. Чтобы понять, что на данном элементе уже присутствует сервис, мы опционально внедрим его в директиву:
class EventFiltersDirective < // . constructor( @Optional() @Self() @Inject(FiltersService) private readonly filtersService: FiltersService | null, ) <>// . >
Если этот сервис присутствует, будем выводить сообщение о том, что директива к нему не применима:
class EventFiltersDirective < @Input() set eventFilters(filters: Filters) < if (this.eventFiltersService === null) < console.warn(ALREADY_APPLIED_MESSAGE); return; >this.mainService.register(this.elementRef.nativeElement, filters); > // . >

Применение на практике
Весь описанный код можно найти на Stackblitz:
В качестве примеров использования там показаны мнимый select — компонент внутри модального окна — и контекстное меню в роли его выпадашки. В случае с контекстным меню, если вы проверите любую реализацию, то увидите, что поведение всегда следующее: при наведении мыши на пункт, он фокусируется, при дальнейшем нажатии стрелочек на клавиатуре, фокус перемещается по пунктам, но если подвигать мышью, фокус возвращается в элемент, находящийся под указателем мыши. Казалось бы, это поведение несложно реализовать, однако, лишние реакции на событие mousemove могут запускать десятки бесполезных циклов проверки изменений. Установив в качестве фильтра проверку на сфокусированность target элемента события, мы можем отсечь эти лишние срабатывания, оставив только те, что реально переносят фокус.
Также, в этом select -компоненте есть фильтрация на @HostListener подписках. При нажатии клавиши Esc внутри попапа, он должен закрываться. Это должно происходить, только если это нажатие не было необходимо в каком-то вложенном компоненте и не было обработано в нём. В select нажатие Esc вызывает закрытие выпадашки и возврат фокуса в само поле, но если он уже закрыт, он не должен препятствовать всплытию события и последующему закрытию модального окна. Таким образом, обработку можно описать декоратором:
@HostListener(‘keydown.esc.filtered.stop’) , при фильтре: () => this.opened .
Поскольку select является компонентом с несколькими фокусируемыми элементами, отслеживание его общей сфокусированности возможно через всплывающие события focusout . Они будут происходить при всех изменениях фокуса, в том числе, не покидающих границы компонента. У этого события есть поле relatedTarget , отвечающее за то, куда перемещается фокус. Проанализировав его, мы можем понять, вызывать ли нам аналог события blur для нашего компонента:
class SelectComponent < // . @HostListener('focusout.filtered') onBlur() < this.opened = false; >// . >
Фильтр, при этом, выглядит так:
const focusOutFilter = (: FocusEvent) => !this.elementRef.nativeElement.contains(relatedTarget);
Вывод
К сожалению, встроенная обработка составных нажатий клавиш в Angular всё равно запустится в NgZone , а значит вызовет проверку изменений. При желании мы могли бы не прибегать к встроенной обработке, но выигрыш в производительности будет невелик, а углубления во внутреннюю “кухню” Angular чреваты поломками при обновлении. Поэтому мы либо должны отказаться от составного события, либо использовать фильтр аналогично граничному оператору и просто не вызывать обработчик там, где он не актуален.
Вклинивание во внутреннюю обработку событий Angular — затея авантюрная, так как внутренняя реализация может измениться в будущем. Это обязывает нас следить за обновлениями, в частности, за задачей на GitHub, приведённой во второй секции статьи. Зато теперь мы можем удобно фильтровать выполнение обработчиков и запуск проверки изменений, у нас появилась возможность удобно применять типичные для обработки событий методы preventDefault и stopPropagation прямо при объявлении подписки. Из заделов на будущее — было бы удобнее объявлять фильтры для @HostListener -ов прямо рядом с ними с помощью декораторов. В следующей статье я планирую рассказать о нескольких декораторах, которые мы у себя создали, и попробую реализовать это решение.
- Блог компании TINKOFF
- Веб-разработка
- JavaScript
- Angular
5 советов для прокачки своих навыков в Angular
Этим летом мы с Ромой запустили серию твитов с полезными советами и приемами по Angular. Сообщество тепло встретило эту инициативу, и я решил написать обобщающую статью. Вот мои 5 рекомендаций, которыми хочется поделиться с разработчиками. Эти советы будут подкреплены конкретными примерами из моего твиттера. Они помогут вам поднять свои навыки или как минимум дадут пару практических приемов.

1. Разберитесь в работе механизма проверки изменений
В интернете множество отличных углубленных статей про проверку изменений в Angular. Например, вот эта. Так что давайте просто освежим основы и перейдем к советам.
Основы
В Angular два режима проверки изменений: Default и OnPush . Первый запускает проверку на каждый tick внутри приложения. Этим управляет Zone.js, которая патчит все асинхронные операции вроде подписок на события и промисов. Второй режим помечает view для проверки, только если в нем случилось слушаемое событие или изменились входные данные.
Default vs OnPush
По правде говоря, вряд ли есть причины использовать режим Default . Просто пишите код так, как задумано принципами фреймворка, и вы не попадете в неприятности с OnPush . Это означает никогда не мутировать данные. Если ваши входные объекты или массивы немутабельные, OnPush подцепит изменения и обновит вью.
Подписки на события через @HostListener Angular заметит и в OnPush . Но что делать, если вы используете RxJS? Всегда можно заинжектить ChangeDetectorRef и вызвать markForCheck() , когда потребуется. Декларативным решением тут будет async пайп, если стрим в итоге доходит до шаблона. Он сам запустит проверку и возьмет на себя отписку.
Вам наверняка попадался такой паттерн:
Но что делать, если вам важны также falsy-результаты? Можно выкинуть всю логику на условие из ngIf и сделать свою простую структурную директиву. Она будет использоваться, только чтобы объявить контекст для вложенного вью:
@Directive(< selector: "[ngLet]" >) export class LetDirective < @Input() ngLet: T; constructor( @Inject(ViewContainerRef) container: ViewContainerRef, @Inject(TemplateRef) templateRef: TemplateRef ) < container.createEmbeddedView(templateRef, new LetContext(this)); > >
NgZone
Если у вас нет возможности полностью перейти на OnPush , можно провести оптимизации. Заинжектите NgZone и выполняйте нагруженные операции в .runOutsideAngular() . Таким образом не будет возникать лишних тиков в механизме проверки изменений. Даже компоненты в режиме Default не будут реагировать на эти операции. Это уместно делать для частых событий, таких как mousemove или scroll . Это можно сделать декларативно в RxJS-стримах с помощью двух операторов: один — для выхода из зоны, другой — для возврата в нее, чтобы запустить проверку изменений:
class ZonefreeOperator implements Operator < constructor(private readonly zone: NgZone) <>call(observer: Observer, source: Observable): TeardownLogic < return this.zone.runOutsideAngular( () =>source.subscribe(observer) ); > > export function zonefull(zone: NgZone): MonoTypeOperatorFunction < return map(value =>zone.run(() => value)); > export function zonefree(zone: NgZone): MonoTypeOperatorFunction < return source =>source.lift(new ZonefreeOperator(zone)); >
Еще один вариант, работающий с @HostListener , — создать свой EventManagerPlugin . Мы выпустили open-source-библиотеку под названием ng-event-plugins. Она позволяет отсеивать лишние проверки изменений. Подробнее об этом читайте в этой статье.
2. Хорошенько разберитесь в RxJS
RxJS — очень мощный инструмент. Мы все его используем в той или иной мере, но именно хорошее владение им очень сильно поможет вам. От простых стримов, которые позволят быстро перезагрузить компонент —
— до сложных комбинаций операторов. Например, чтобы понять, настоящий у вас скролл или инерционный:
Только посмотрите, как просто создать шапку, исчезающую при прокрутке сайта вниз. Совсем немного CSS и базовый RxJS:
@Directive(< selector: "[sticky]", providers: [DestroyService] >) export class StickyDirective < constructor( @Inject(DestroyService) destroy$: Observable, @Inject(WINDOW) windowRef: Window, renderer: Renderer2, < nativeElement >: ElementRef ) < fromEvent(windowRef, "scroll") .pipe( map(() =>windowRef.scrollY), pairwise(), map(([prev, next]) => next < THRESHOLD || prev >next), distinctUntilChanged(), startWith(true), takeUntil(destroy$) ) .subscribe(stuck => < renderer.setAttribute( nativeElement, "data-stuck", String(stuck) ); >); > >
Сложно переоценить знание RxJS для Angular-разработчика в долгосрочной перспективе. Простой на первый взгляд RxJS требует некоего смещения парадигмы, чтобы начать мыслить потоками. Но, когда вы это сделаете, ваш код станет аккуратнее и легче в поддержке.
Тут особо нечего советовать, кроме как больше практиковаться . Если видите ситуацию, которую можно решить через RxJS, — попытайтесь сделать это. Избегайте сайд-эффектов и вложенных подписок. Держите свои стримы в порядке и следите за утечками памяти (подробнее об этом дальше).
3. Выжимайте максимум из TypeScript
Мы все пишем Angular-приложения на TypeScript. Но, чтобы получить максимальную пользу, нужно задействовать его целиком. Я редко вижу проекты с включенным strict: true . Вам определенно следует сделать это. Оно спасет вас от множества cannot read property of null и undefined is not a function.
Дженерики
В TypeScript существуют дженерики — для случаев, когда тип, с которым мы работаем, неизвестен. Комбинация дженериков, перегрузок и сужения типов позволит вам сделать крутой API. Почти никогда не придется делать тайпкаст. Посмотрите на этот пример типизированного RxJS-метода fromEvent :
// Тип события с конкретным currentTarget export type EventWith < E extends Event, T extends FromEventTarget> = E & < readonly currentTarget: T; >; // Типизированный вариант fromEvent export function typedFromEvent< E extends keyof GlobalEventHandlersEventMap, T extends FromEventTarget> >( target: T, event: E, options: AddEventListenerOptions = <>, ): Observable>
С ним вы будете уверены, что событие имеет конкретный тип, а currentTarget — это элемент, на котором вы его слушаете.
API, построенные на дженериках, хороши тем, что они не завязаны на формат данных. А значит, их легко расширять и другим разработчикам не придется подстраиваться под определенный интерфейс.
Есть немало статей о продвинутой типизации и всевозможных трюках с TypeScript. Я очень советую расширять свои знания в этой области, это поможет вам писать надежный код. Мой последний совет: не используйте any . Если вы не можете применить дженерик, более безопасным выбором будет unknown .
Декораторы
Не забывайте про другие инструменты TypeScript, такие как декораторы. При умелом использовании они могут значительно улучшить ваш код. К примеру, бывают ситуации, когда тип переменной корректный, но значение не подходит: отрицательное или дробное число не подходит для обозначения количества, хотя тип и там, и там — number . TypeScript такое не отловит, но мы можем защитить компоненты в runtime с помощью декоратора:
export function assert( assertion: (input: T[K]) => boolean, messsage: string ): PropertyDecorator < return (target, key) =>< Object.defineProperty(target, key, < set(this: T, initialValue: T[K]) < let currentValue = initialValue; Object.defineProperty(this, key, < get(): T[K] < return currentValue; >, set(this: T, value: T[K]) < console.assert(assertion(value), messsage); currentValue = value; >>); > >); >; >
Вы знали, что декорированный абстрактный класс не нуждается в пробросе аргументов в super() ? Angular сам сделает это за вас, если не использовать конструктор в дочернем классе:
Еще один хороший пример для декораторов — переиспользуемая логика обработки. Посмотрите кусок кода из нашей библиотеки для Web Audio API в Angular, где мы превращаем декларативный байндинг в императивные нативные команды с помощью строго типизированного декоратора. Подробнее про саму библиотеку можно почитать тут.
4. Dependency Injection. Используйте его почаще
DI — одна из причин, почему Angular такой мощный фреймворк. Возможно, это даже главная причина. Но слишком часто его не используют по полной.
RxJS
Что касается практических советов, выше я уже говорил быть внимательными к утечкам памяти в RxJS. Главным образом это означает, что, если вы подписались на стрим руками, вам же и придется от него отписаться. Идиоматическим решение в Angular будет инкапсуляция подобной логики в сервис:
@Injectable() export class DestroyService extends Subject implements OnDestroy < ngOnDestroy() < this.next(); this.complete(); >>
Вы также можете создавать общие стримы и добавлять их в DI. Нет необходимости создавать отдельные потоки на базе requestAnimationFrame в вашем приложении. Создайте токен и переиспользуйте его. Вы даже можете заложить в него операторы для выхода из зоны, описанные выше:
Токены
DI — хороший инструмент для повышения абстрактности вашего кода. Если вы не зависите от глобальных объектов вроде window или navigator — ваше приложение готово к использование в Angular Universal в серверном окружении. Такой код просто тестируется, так как все его зависимости легко подменить на заглушки. Глобальные объекты без труда превращаются в токены. Внутри фабрики при объявлении токена нам доступен глобальный инжектор. Нам потребуется всего пара строк для создания WINDOW токена на базе встроенного DOCUMENT :
export const WINDOW = new InjectionToken( 'An abstraction over global window object', < factory: () => < const = inject(DOCUMENT); if (!defaultView) < throw new Error('Window is not available'); >return defaultView; >, >, );
Чтобы не тратить на это время, используйте нашу open-source-библиотеку, где мы уже реализовали многие такие токены. А для Angular Universal есть ее библиотека-сестра с качественными заглушками. Не стесняйтесь, пишите нам, если вам нужен еще какой-то токен.
Токены и фабрики дают массу возможностей. В сочетании с иерархической структурой DI они позволяют сделать приложение очень модульным. Можете почитать больше про использование провайдеров в этой статье.
5. Бросайте императора в бездну, как Вейдер
Angular со своим синтаксисом шаблонов и декораторами подталкивает нас к декларативному подходу. Я уже упоминал его. Не будем сейчас вдаваться в подробности о его преимуществах по сравнению с императивным кодом. Я просто дам вам четкий совет: пишите декларативный код. Когда вы к нему привыкнете, то заметите, насколько все стало лучше.
Геттеры
Что это означает — «писать декларативный код»? В первую очередь постарайтесь использовать ngOnChanges как можно реже. Это плохо типизированный сайд-эффект, который нужен, по сути, только когда надо выполнить какое-то действие при изменении нескольких входных значений.
Если есть необходимость отреагировать на изменение одного инпута — сеттер будет более прямолинейным решением. Если же вместо действия вы собирались обновить какое-то внутреннее состояние, убедитесь что его нельзя заменить вычисляемым геттером.
Производительность и геттеры — тема для отдельной статьи, до которой я надеюсь вскоре добраться. А пока возьмите за правило не создавать новых объектов или массивов внутри геттеров. Это существенно более затратная операция, чем арифметика или сложение строк. Если вам требуется пересоздать объект, воспользуйтесь техникой мемоизации — к примеру, чистым пайпом.
Это хороший пример для сеттера, который я опустил для краткости, но про который мне напомнил внимательный читатель.
Template reference variables
Вместо ручного запрашивания элементов из шаблона в Angular предусмотрен декоратор @ViewChild . Однако зачастую он не нужен, если можно завернуть передачу элемента в шаблоне:
Здесь мы передаем template reference variable непосредственно в метод, где она нужна. Так код нашего компонента остается чище. Думайте об этом как о подобии замыкания в шаблоне.
Но что, если мы хотим получить DOM-элемент, который является компонентом? Мы могли бы написать @ViewChild(MyComponent, ) , но мы обойдемся без поля класса, если создадим директиву с exportAs :
@Directive(< selector: '[element]', exportAs: 'elementRef', >) export class ElementDirective extends ElementRef < constructor(@Inject(ElementRef) : ElementRef) < super(nativeElement); >>
Динамический контент
Люди часто используют ComponentFactoryResolver для императивного создания динамических компонентов. Зачем, если есть директива ngComponentOutlet ? Потому что так мы получим доступ к экземпляру компонента и сможем передать в него данные. Хороший способ решения подобной задачи — опять же, Dependency Injection. ngComponentOutlet позволяет передать Injector , который мы создадим и подложим в него данные через токен.
По сути, для создания динамического контента у нас есть интерполяция, шаблоны и компоненты. И принципиальной разницы между ними нет. У нас есть контекст и есть его представление.
Мы уже давно используем этот подход, не зависящий от типа переданного контента. Мы вынесли его в крошечную open-source-библиотеку под названием ng-polymorpheus. Она не делает ничего, кроме передачи контента в соответствующий встроенный инструмент, ngTemplateOutlet , ngContentOutlet или же простую интерполяцию с вызовом функции. Когда привыкаешь к такому, обратной дороги уже нет! Подробнее читайте в этой статье.
На этом все. Надеюсь, мои советы будут вам полезны. Приятного кодинга!
- Блог компании TINKOFF
- JavaScript
- Angular
- TypeScript
Reuters узнал детали санкций ЕС против Тинькофф Банка и Альфа-банка

Еврокомиссия (ЕК) предложила странам ЕС исключить еще четыре российских банка, в том числе Альфа-банк, Тинькофф Банк и Росбанк, из глобальной системы обмена сообщениями SWIFT, сообщает Reuters со ссылкой на дипломатические источники. Четвертый банк агентство не называет. Ранее о том, что ЕС в рамках десятого пакета санкций намерен внести в санкционный список Альфа-банк, Тинькофф Банк, Росбанк, а также российский Фонд национального благосостояния (ФНБ), сообщал Bloomberg. По данным Reuters, под новые санкции могут попасть российская резина и асфальт, а также сталь и алюминий, используемые в строительстве. Помимо этого, страны ЕС намерены запретить вещание арабской службы телеканала RT, усилить ограничения на экспорт в Россию электронных схем, тепловизионных камер, транспортных средств и ужесточить контроль над продажей спутниковых данных в Китай, которые, по словам источников, рискуют быть переданными Москве.
ЕК также предложила ввести дополнительные санкции на европейские совместные предприятия с Россией и ввести дополнительные обязательства по отчетности, чтобы лучше отслеживать российские активы в Европе, рассказали источники Reuters. По их словам, к настоящему времени ЕС уже арестовал активы российского Центрального банка на сумму около €33,8 млрд.

После начала российской спецоперации на Украине западные страны ввели против Москвы санкции, в том числе против российского Центрального банка. Из-за этого замороженными оказались активы на сумму около $300 млрд, сообщал ранее глава Минфина Антон Силуанов. Помимо этого, в рамках ограничений от SWIFT были отключены Сбербанк, ВТБ, Россельхозбанк и другие банки. В то же время Альфа-банк и Росбанк под санкции ЕС не попали, хотя ранее были включены в санкционный список США. Тинькофф Банк также не находится под санкциями ЕС, однако основатель банка Олег Тиньков был внесен в санкционный список Великобритании весной прошлого года. «Интеррос» затем купил 35% в TCS Group у семьи Тинькова.
Глава Еврокомиссии Урсула фон дер Ляйен ранее сообщила, что десятый пакет санкций ЕС против России предполагает введение экспортных ограничений на сумму €11 млрд. Российская сторона считает санкции незаконными и отмечает, что заморозка активов Москвы сходна с ограблением. «Воровство чужих активов никогда до добра никого не доводило, прежде всего тех, кто занимается этим неблаговидным делом», — говорил президент Владимир Путин. Премьер-министр Михаил Мишустин счел, что Запад таким образом вернулся «к типичной пиратской практике».
Подключить и отключить автоследование в Тинькофф
Какие стратегии автоследования доступны в Тинькофф?
В приложении Тинькофф Инвестиций есть только рублевые стратегии автоследования. Портфели стратегий состоят из акций, облигаций, паев фондов или валюты — в зависимости от конкретной стратегии, которой придерживается ее автор. Использовать заемные средства брокера для следования стратегии не получится — маржинальная торговля в автоследовании не используется.
Чтобы попасть в каталог стратегий, на нижней панели нажмите на вкладку «Что купить» → «Готовые стратегии». Стратегии отсортированы по различным параметрам:
валюте — чтобы посмотреть список рублевых стратегий, на странице «Готовые стратегии» нажмите на кнопку «Рублевые» под поисковой строкой;
степени риска — наименее рискованные стратегии собраны в разделе «Менее рискованные»;
популярности — чтобы посмотреть стратегии с наибольшим числом подписчиков, прокрутите страницу до блока «Популярные»;
прибыльности — в конце страницы, в блоке «Прибыльные за всё время», собраны стратегии, которые показали наибольшую доходность.
Чтобы посмотреть сразу все стратегии, нажмите на кнопку «Все стратегии» — она находится в верхней части страницы под поисковой строкой.
Если вы не можете определиться, какая стратегия подходит именно вам, воспользуйтесь нашими подборками, которые находятся в верхней части экрана под кнопкой «Все стратегии»:
«Легкий старт» — чтобы подписаться на стратегию из этой подборки, достаточно вложить не больше 20 000 ₽;
«Выбор Тинькофф» — стратегии, которые отобрали аналитики Тинькофф;
«Недавно запустились» — подборка новых стратегий.
Надо ли открывать отдельный счет для автоследования?
Да, для подключения автоследования нужно будет открыть дополнительный брокерский счет — в Тинькофф Инвестициях можно бесплатно открыть до 10 брокерских счетов, не считая Инвесткопилки и ИИС. Для этого счета будет применяться специальный тариф «Автоследование»: там нет обычных брокерских комиссий за сделки, но есть комиссии за следование стратегии и за результат.
Подключить автоследование на действующий брокерский счет или на ИИС тоже можно, но понадобится выполнить ряд условий:
на счете должна быть отключена маржинальная торговля;
не подключать валютные стратегии на ИИС — в данный момент сделать это не получится по техническим причинам;
если на брокерском счете есть валюта, она должна совпадать с валютой выбранной стратегии;
на счете не должно быть внебиржевых активов;
активы на брокерском счете должны быть только в валюте выбранной стратегии;
на счете не должно быть лимитных заявок на бумаги в валюте, отличной от валюты стратегии;
не должно быть лимитных заявок на внебиржевые активы.
Если какое‑то из этих условий не будет выполняться, подключить автоследование не получится. В приложении вы увидите предупреждение об этом.
Важно помнить, что после подключения автоследования вы не сможете самостоятельно торговать на этом счете, пока не отключите услугу. Как отключить автоследование
Если к моменту подключения автоследования на вашем счете будут какие‑то активы, мы сами продадим их по текущей рыночной цене, чтобы синхронизировать ваш портфель с портфелем стратегии. Подробнее про синхронизацию
Как подписаться на стратегию?
Следуйте этой инструкции:
В приложении Тинькофф Инвестиций перейдите на вкладку «Что купить» и нажмите на плашку «Готовые стратегии» в верхней части экрана.
Выберите подходящую стратегию из каталога и внимательно изучите ее параметры: уровень риска, доходность, валюту, в которой совершаются сделки. После этого нажмите на кнопку «Следовать».
Если раньше вы не определяли свой инвестиционный профиль , в процессе подбора стратегии мы предложим вам заполнить соответствующую анкету. Инвестиционный профиль — очень важная составляющая автоследования: если ваш уровень риска не соответствует уровню риска стратегии, вы увидите предупреждение об этом. Что такое уровень риска стратегии
Откройте отдельный счет для автоследования или выберите существующий брокерский счет или ИИС. Какие есть ограничения для автоследования на действующем счете
Как вывести деньги со счета с автоследованием?
Сначала на счете нужно отключить автоследование. Как отключить автоследование
Но есть нюансы. Часть средств можно вывести только с брокерского счета, на котором было подключено автоследование. С ИИС нельзя вывести даже небольшую сумму — только сразу все деньги и ценные бумаги, которые на нем есть. Если так сделать, счет закроется. Если это произойдет до того, как пройдет три полных года с момента его открытия, вы потеряете право на оба вида налоговых вычетов, а полученные вычеты придется вернуть. Что будет, если ИИС закроется досрочно
С брокерского счета, на котором было подключено автоследование, вывести деньги можно так же, как и с обычного брокерского счета:
В приложении Тинькофф Инвестиций на вкладке «Главная» нажмите на нужный счет и продайте активы, которые есть на этом счете.
