[Разработка веб-сайтов, JavaScript, Angular, TypeScript] RxJS Challenge: Неделя 1

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
26-Май-2021 15:34

Работая с Angular волей-неволей будешь использовать RxJS, ведь он лежит в основе фреймворка. Это очень мощный инструмент для обработки событий и не только. Однако далеко не каждый проект использует его по полной. Часто это просто запросы на бэк, нехитрые преобразования данных и подписка. Мы с Ромой очень любим RxJS и решили собрать несколько интересных кейсов из нашей практики. Мы сделали из этого что-то вроде челленджа на 20 задачек, которые мы предлагаем решить с помощью RxJS и попрактиковать свои навыки.
Каждая задачка будет иметь некий бойлерплейт, чтобы вам было просто начать. Под спойлером я положу ссылку на свое решение и небольшое пояснение к нему. В целом задачи будут идти от простого к сложному, а полное собрание с ответами и пояснениями на английском доступно на GitHub.​ #1. Создайте Observable для отслеживания фокуса на страницеВ этой задаче вам предлагается область страницы со всевозможными фокусируемыми элементами внутри. Нужно сделать стрим, который будет отслеживать фокус внутри этой области.StackBlitzРешениеДля отслеживания изменения фокуса нам понадобятся события focusin и focusout, поскольку focus/blur не всплывают. Мы будем получать элемент через target и relatedTarget, потому что в момент этих событий мы еще не можем проверить document.activeElement — в коллбэке событий там будет body. Если текущий элемент вне отслеживаемой зоны, будем выдавать null. Так же подобную логику можно вынести в сервис — его потом будет удобно использовать в директивах. Поскольку мы не знаем момент, когда пользователь подпишется на наш стрим, добавим такую конструкцию для получения начального значения при подписке: defer(() => of(documentRef.activeElement)). Остается только собрать все потоки в merge:
@Injectable()
export class FocusWithinService extends Observable<Element | null> {
  constructor(
    @Inject(DOCUMENT) documentRef: Document,
    { nativeElement }: ElementRef<HTMLElement>
  ) {
    const focusedElement$ = merge(
      defer(() => of(documentRef.activeElement)),
      fromEvent(nativeElement, "focusin").pipe(map(({ target }) => target)),
      fromEvent(nativeElement, "focusout").pipe(
        map(({ relatedTarget }) => relatedTarget)
      )
    ).pipe(
      map(element =>
        element && nativeElement.contains(element) ? element : null
      ),
      distinctUntilChanged(),
    );
    super(subscriber => focusedElement$.subscribe(subscriber));
  }
}
StackBlitz с готовым решением​ #2. Создайте поток видимости вкладкиХороший пример работы с событиями в RxJS — использование Page Visibility API. Подобный стрим также удобно завернуть в InjectionToken. Бойлерплейта для этой задачи нет ​РешениеТут ситуация довольно тривиальная. Единственный подвох — начальное значение. Если мы сразу проверим, видима страница или нет, это значение может оказаться неактуальным на момент подписки. Можно использовать defer, как в прошлый раз, а можно начать с произвольного значения, а реальное получать в map ниже:
export const PAGE_VISIBILITY = new InjectionToken<Observable<boolean>>(
  "Shared Observable based on `document visibility changed`",
  {
    factory: () => {
      const documentRef = inject(DOCUMENT);
      return fromEvent(documentRef, "visibilitychange").pipe(
        startWith(0),
        map(() => documentRef.visibilityState !== "hidden"),
        distinctUntilChanged(),
        shareReplay()
      );
    }
  }
);
В качестве бонуса в примере мы превратим этот поток в DI-токен для удобного использования. Этот токен есть в нашей микробиблиотеке токенов на глобальные сущности.StackBlitz с готовым решением​ #3. Покажите сообщение об ошибке на 5 секундДопустим, у нас есть кнопка логина. При нажатии на нее идет запрос на сервер, на это время кнопка будет заблокирована. При успешном логине мы выведем имя пользователя, в противном случае покажем ошибку на 5 секунд и разблокируем кнопку для повторной попытки. В примере ниже заготовлен муляж сервиса логина:StackBlitzРешениеДавайте посмотрим, как мы можем ветвить потоки, на примере данной задачи. В такой ситуации типовое начало будет Subject, который мы будем дергать по нажатию на кнопку и Observable, который перебрасывает эти нажатия на запрос на сервер:
readonly submit$ = new Subject<void>();
readonly request$ = this.submit$.pipe(
  switchMapTo(this.service.pipe(startWith(""))),
  share(),
);
Теперь давайте разведем запросы на нужные нам потоки. Обратите внимание на share в конце, который поможет нам избежать повторных запросов на сервер при подписках на эти витки.При ошибке сервис бросит реальную ошибку в поток. Таким образом, имя пользователя будет просто повторной попыткой запроса:
readonly user$ = this.request$.pipe(retry());
Сообщение об ошибке же будет как раз той ошибкой, которую бросит запрос, показанной в течение 5 секунд:
readonly error$ = this.request$.pipe(
  ignoreElements(),
  catchError(e => of(e)),
  repeat(),
  switchMap(e => timer(5000).pipe(startWith(e)))
);
Мы игнорируем элементы и показывает только ошибки. На этом примере хорошо видна разница между repeat и retry: первый перезапускает поток, который успешно завершился, второй перезапускает поток, закончившийся ошибкой. Аналогичным образом мы можем отвести поток, отвечающий за блокировку кнопки.StackBlitz с готовым решением​ #4. Отобразите состояние загрузки в виде полосы прогрессаЕсли ваш сервис репортит прогресс загрузки, удобно было бы отобразить это для пользователя. Например, тут я прикручивал RxJS и прогресс к нативному fetch:Извините, данный ресурс не поддреживается. :( Попробуем применить похожую технику ветвления стрима для отображения прогресса.StackBlitzРешениеНачнем мы, как и в прошлый раз, с Subject и общего потока. Общий поток мы разведем на два — прогресс и результат. Для первого мы используем фильтрацию:
readonly progress$ = this.response$.pipe(filter(Number.isFinite));
А для второго — преобразование, чтобы можно было перезапускать процесс:
readonly result$ = this.response$.pipe(
  map(response => typeof response === "string" ? response : null),
  distinctUntilChanged()
);
StackBlitz с готовым решением ​ #5. Сделайте обратный отсчет с перезапускомПредставьте, что вам надо сделать таймер, ведущий отсчет перед повторной отправкой кода. Отличная микрозадача на RxJS. В качестве бонуса в ответе я приведу еще и решение на CSS, которое позволяет форме подтверждения платежа по SMS в Тинькофф работать даже с отключенным JavaScript ​StackBlitzРешениеДля обратного отсчета можно сделать простую утилитную функцию, где мы воспользуемся малоизвестным вторым аргументом takeWhile:
function countdownFrom(start: number): Observable<number> {
  return timer(0, 1000).pipe(
    map(index => start - index),
    takeWhile(Boolean, true)
  );
}
Благодаря второму аргументу, 0, который нарушит условие, тоже провалится дальше. Сам стрим будет просто использовать switchMapTo от Subject, который мы запускаем по кнопке, как в прошлых примерах. Использовать можно так:
<ng-container *ngIf="countdown$ | async as value else resend">
  Resend code in {{ value }} sec.
</ng-container>
<ng-template #resend>
  <button (click)="resend$.next()">Resend code</button>
</ng-template>
Заключительный 0 не пройдет ngIf и переключит шаблон назад на кнопку.CSS-решение будет полагаться на анимацию псевдоэлементов и перезапуск ее с помощью :active состояния нажатой кнопки. Загляните в CSS-файл странички с решением:StackBlitz с готовым решениемЗаключениеЭто была первая неделя нашего челленджа. Впереди вас ждут еще 15 более сложных задач с использованием RxJS. Ссылки будут добавлены ниже по мере публикации.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_angular, #_typescript, #_rxjs, #_angular, #_typescript, #_observable, #_blog_kompanii_tinkoff (
Блог компании TINKOFF
)
, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
, #_javascript, #_angular, #_typescript
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 19-Май 00:20
Часовой пояс: UTC + 5