Défi RxJS: Semaine 1

En travaillant avec Angular bon gré mal gré, vous utiliserez RxJS, car il est au cœur du framework. C'est un outil très puissant pour gérer les événements et plus encore. Cependant, tous les projets ne l’utilisent pas pleinement. Souvent, ce ne sont que des demandes de sauvegarde, de simples transformations de données et des abonnements. Nous, les Roms, sommes très friands de RxJS et avons décidé de rassembler quelques études de cas intéressantes de notre pratique. Nous avons fait quelque chose comme un défi pour 20 problèmes, que nous proposons de résoudre avec RxJS et de mettre en pratique vos compétences.





Chaque puzzle aura un passe-partout pour vous faciliter la tâche. Sous le spoiler, je vais mettre un lien vers ma solution et une petite explication. En général, les tâches vont du simple au complexe, et une collection complète avec des réponses et des explications en anglais est disponible sur 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. .








All Articles