Declarative Programming – Filter Posts

בשלב הבא אנחנו רוצים לראות איך אפשר לבצע filter על הטבלה. נניח שאנחנו רוצים לתת למשתמש לבחור פוסטים לפי קטגוריה. בתור התחלה נבחר את הקטגוריה עם id=1.

שימוש ב-filter

קובץ declarative-posts.component.ts

export class DeclarativePostsComponent {
  selectedCategoryId = '1';
  posts$ = this.postService.postsWithCategory$;

  filteredPosts$ = this.posts$.pipe(
    map(posts => {
      return posts.filter(
        (post) => post.categoryId == this.selectedCategoryId
    )
  }));

  constructor(private postService: DeclarativePostsService){}
}

ובתצוגה נקרא ל-filteredPosts. נקבל את הפוסטים ששייכים ל-id 1.

השלב הבא הוא לתת למשתמש לבחור את הקטגוריה שהוא רוצה ולעדכן את הרשימה בהתאם.

כדי לקבל את רשימת הקטגוריות נזריק את שירות הקטגוריות.

constructor(private postService: DeclarativePostsService,
  private categoryService: DeclarativeCategoriesService){}

נוסיף משתנה שיקבל את רשימת הקטגוריות.

categories$ = this.categoryService.categories$;

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

קובץ declarative-posts.component.html

<div class="row">
  <div class="col-md-4 py-3">
    <select class="form-select">
      <option value="">Select Category</option>
      <option [value]="category.id"
      *ngFor="let category of categories$ | async">
      {{ category.title }}
    </option>
    </select>
  </div>
</div>

עכשיו צריך לסנן את הרשימה לפי הבחירה של המשתמש.

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

קובץ declarative-posts.component.ts

filteredPosts$ = this.posts$.pipe(
    map(posts => {
      return posts.filter(
        (post) => this.selectedCategoryId
          ? post.categoryId == this.selectedCategoryId
          : true
    )
}));

נוסיף פונקציה לטיפול בבחירה של המשתמש.

onCategoryChange(event: Event){
    let selectedCategoryId = (event.target as HTMLSelectElement).value;
    this.selectedCategoryId = selectedCategoryId;
}

ונקבל את הערך מתוך אלמנט ה-html.

קובץ declarative-posts.component.html

<div class="row">
      <div class="col-md-4 py-3">
        <select class="form-select" (change)="onCategoryChange($event)">
          <option value="">Select Category</option>
          <option [value]="category.id"
          *ngFor="let category of categories$ | async">
          {{ category.title }}
        </option>
        </select>
      </div>
</div>

נשמור את הקוד, ונראה שהוא לא מבצע את הפעולה שאנחנו רוצים. השינוי מתבצע במשתנה selectedCategoryId ואנחנו לא עוקבים אחרי השינויים שלו. אנחנו רוצים להפוך אותו ל-observable כדי לעקוב אחרי השינויים שלו.

Data Stream vs Action Stream

אנחנו צריכים לפלטר את הפוסטים לפי בחירת המשתמש. אנחנו מקבלים את המידע על הפוסטים מתוך פעולת get. הוא עובדת פעם אחת, מביאה את הפוסטים ומסיימת את הפעולה שלה. אנחנו צריכים לקרוא לפונקציה שוב. זה נקרא Data Stream.

Action Stream יטריג את המידע בכל פעם כשתהיה פעולה. במקרה שלנו אנחנו רוצים שתהיה תגובה בכל פעם כשהמשתמש בוחר קטגוריה. ה-Stream הזה תמיד מקשיב לשינויים. אנחנו יכולים להשתמש ב-CombineLatest כדי להמשיך ולהקשיב למידע.

אנחנו צריכים להפוך את פעולת הבחירה של המשתמש ל-observable שנוכל להקשיב לו.

observable הוא unicast, כל subscriber מקבל מופע משלו. subject הוא multicast, יכולים להיות לי כמה נרשמים לאותו subject שיחלקו את המידע ביניהם. נשתמש ב-subject כדי לקבל את הבחירה של המשתמש.

Create Action Stream using BehaviorSubject

נייצר את המשתנה שיחזיק את שינוי הקטגוריה.

קובץ declarative-posts.component.ts

selectedCategorySubject = new Subject<string>();

בכל פעם שהמשתמש משנה את המידע, אנחנו צריכים להעביר את זה דרך ה-subject.

onCategoryChange(event: Event){
    let selectedCategoryId = (event.target as HTMLSelectElement).value;
    this.selectedCategorySubject.next(selectedCategoryId);
}

עכשיו אנחנו צריכים מישהו שיאזין לשינוי הזה.

selectedCategoryAction$ = this.selectedCategorySubject.asObservable();

עכשיו בכל פעם שיהיה שינוי ב-selectedCategorySubject יהיה טריגר ל-selectedCategoryAction$. יש לנו את המידע, ויש לנו את הפעולה. צריך לחבר אותם.

combineLatest מקבל מערך של observables.

filteredPosts$ = combineLatest([this.posts$, this.selectedCategoryAction$]).
    pipe(map(([posts, selectedCategoryId]) => {
      return posts.filter(
        (post) => post.categoryId == selectedCategoryId
      )
  }));

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

הקוד המלא:

קובץ declarative-posts.component.ts

export class DeclarativePostsComponent {
  selectedCategorySubject = new BehaviorSubject<string>('');
  selectedCategoryAction$ = this.selectedCategorySubject.asObservable();

  posts$ = this.postService.postsWithCategory$;
  categories$ = this.categoryService.categories$;

  filteredPosts$ = combineLatest([this.posts$, this.selectedCategoryAction$]).
    pipe(map(([posts, selectedCategoryId]) => {
      return posts.filter(
        (post) => selectedCategoryId ? post.categoryId == selectedCategoryId : true
      )
    }));

  constructor(private postService: DeclarativePostsService,
    private categoryService: DeclarativeCategoriesService){}

  onCategoryChange(event: Event){
    let selectedCategoryId = (event.target as HTMLSelectElement).value;
    this.selectedCategorySubject.next(selectedCategoryId);
  }
}

ניווט במאמר

מאמרים אחרונים

Weekly Tutorial