How to Avoid multiple async for observables

נסתכל על עמוד Alt Posts שבו יש לנו רשימה של פוסטים ולחיצה על כותרת הפוסט מציגה את פרטי הפוסט בצד ימין.

ערך ראשוני לתצוגה

כרגע, בזמן הכניסה לדף, אין לנו שום פוסט נבחר. לכן צד ימים של הדף ריק. נרצה להציג בכניסה לדף את הפוסט הראשון ברשימה.

נסתכל על משיכת הפוסט הנבחר.

קובץ alt-posts.component.ts

posts$ = this.postService.postsWithCategory$;
selectedPost$ = this.postService.post$;

את הערך של הפוסט הנבחר אנחנו מקבלים מ-selectedPost$. אנחנו רוצים לשים שם את הפוסט הראשון. נשתמש באופרטור tap כי אנחנו לא רוצים לשנות את הנתונים, רק לייצר פעולה חד פעמית. מייד כשאנחנו נכנסים, נפעיל את הפונקציה שמשנה את הפוסטים עם ה-ID של הפוסט הראשון.

קובץ alt-posts.component.ts

export class AltPostsComponent {
  posts$ = this.postService.postsWithCategory$.pipe(
    tap(posts => {
      posts[0].id && this.postService.selectPost(posts[0].id);
    })
  );
  selectedPost$ = this.postService.post$;

  constructor(private postService: DeclarativePostsService){}

  onSelectPost(post: Post, event: Event){
    event.preventDefault();
    post.id && this.postService.selectPost(post.id);
  }
}

עכשיו נקבל את הפוסט הראשון מוצג הכניסה, אבל אנחנו לא מקבלים את סימון הצבע על הכותרת של הפוסט. למה זה קורה? מכיוון שיש קריאה אסינכרונית של הפוסטים, בחירת הפוסט מתרחשת אחרי הקריאה לתצוגה של העמוד. ומכיוון שההגדרה של הפוסט הנבחר ב-service היא כ-subject, אין עדכן של העמוד לערכים קודמים. נשנה את ההגדרה ל-BehaviorSubject.

קובץ declarative-posts.service.ts

private selectedPostSubject = new BehaviorSubject<string>('');
selectedPostAction$ = this.selectedPostSubject.asObservable();

שיפור ביצועים async

מכיוון שיש לנו קריאה ל-pipe אסינכרוני בלולאה, יכולה להיות לנו בעיית ביצועים, במיוחד אם יש לנו רשימת פוסטים ארוכה. מכיוון שבפועל כשאנחנו מעלים את העמוד, יש לנו מספר פעולות subscribe כמספר הפעמים שאנחנו קוראים ללולאה, בכל פעם שיש בדיקה של ה-selected Post.

קובץ alt-posts.component.html

<ul class="list-group">
      <a href="#"
        [class]="{active: post.id === (selectedPost$ | async)?.id}"
        class="list-group-item list-group-item-action"
        (click)="onSelectPost(post, $event)"
        *ngFor="let post of posts$ | async"
        >
          {{ post.title }} ({{ post.categoryName }})
      </a>
</ul>

אפשר לעקוב אחרי הפעולה עם שליחה של טקסט לקונסול על ידי tap בתוך משתנה selectedPost$.

דרך אחת להתגבר על זה היא בשימוש של view model שבו אנחנו יוצרים משתנה אחד ומחברים בתוכו את ה-observables שאנחנו מקבלים. ואז יהיה לנו async אחד בכל האפליקציה.

קובץ alt-posts.component.ts

posts$ = this.postService.postsWithCategory$.pipe(
    tap(posts => {
      posts[0].id && this.postService.selectPost(posts[0].id);
    })
);
selectedPost$ = this.postService.post$;

vm$ = combineLatest([this.posts$, this.selectedPost$]).pipe(
    map(([posts, selectedPost]) => {
      return {posts, selectedPost};
    })
);

קובץ alt-posts.component.html

<ul class="list-group" *ngIf="vm$ | async as vm">
      <a href="#"
        [class]="{active: post.id === vm.selectedPost?.id}"
        class="list-group-item list-group-item-action"
        (click)="onSelectPost(post, $event)"
        *ngFor="let post of vm.posts"
        >
          {{ post.title }} ({{ post.categoryName }})
      </a>
</ul>

עכשיו הטעינה של הדף תהיה מהירה יותר.