Declarative Programming – Action Stream

אם יש לנו 2 קומפוננטות שאנחנו רוצים לתקשר ביניהן, אנחנו יכולים לשתף את ה-Action Stream בין שתיהן.

Share the Action Stream

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

ng g c components/pages/AltPosts

קובץ app-routing.module.ts

const routes: Routes = [
  { path: '', component: PostsComponent },
  { path: 'decposts', component: DeclarativePostsComponent },
  { path: 'altposts', component: AltPostsComponent }
];

נעבור לקומפוננטה החדשה, נקרא ל-service של הפוסטים ונקבל את הפוסטים.

קןבץ alt-posts.component.ts

export class AltPostsComponent {
  posts$ = this.postService.postsWithCategory$;
  
  constructor(private postService: DeclarativePostsService){}
}

נעבור להצגה של הפוסטים בתבנית של הקומפוננטה. בצד שמאל של העמוד נציג את רשימת הפוסטים.

קןבץ alt-posts.component.html

<div class="row">
  <div class="col-md-12">
    <h3>Post Details</h3>
  </div>
</div>
<div class="row">
  <div class="col-md-4">
    <ul class="list-group">
      <a href="#" class="list-group-item list-group-item-action"
        *ngFor="let post of posts$ | async">
        {{ post.title }} ({{ post.categoryName }})
      </a>
    </ul>
  </div>
  <div class="col-md-8"></div>
</div>

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

ng g c components/pages/SinglePost

את הקומפוננטה השאת נרצה לשים בצד ימין של המסך.

קןבץ alt-posts.component.html

<div class="col-md-8">
    <app-single-post></app-single-post>
</div>

אנחנו צריכים להעביר את המידע מקומפוננטת alt-posts ל-single-post לגבי הפוסט הנבחר. רשימת הפוסטים היא DataStream ובחירת הפוסט היא ActionStream.

נוסיף את אירוע ה-click לקישור.

קןבץ alt-posts.component.html

<div class="col-md-4">
    <ul class="list-group">
      <a href="#" class="list-group-item list-group-item-action"
        (click)="onSelectPost(post, $event)"
        *ngFor="let post of posts$ | async">
        {{ post.title }} ({{ post.categoryName }})
      </a>
    </ul>
</div>

אם ניצור את ה-actionStrean בקובץ ה-alt-posts, לא תהיה לו גישה מה-single-post. לכן ניצור אותו ב-service, כדי שיהיה זמין לשתי הקומפוננטות.

קובץ declarative-posts.service.ts

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

selectPost(postId: string){
    this.selectedPostSubject.next(postId);
}

את הערך נשלח כאשר יש אירוע click על כותרת הפוסט.

קןבץ alt-posts.component.ts

export class AltPostsComponent {
  posts$ = this.postService.postsWithCategory$;

  constructor(private postService: DeclarativePostsService){}

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

קבלתי את ה-id של הפוסט והוא נמצא עכשיו ב-service, נשלח את ה-service להביא את הפוסט הזה.

קובץ declarative-posts.service.ts

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

post$ = combineLatest([
    this.postsWithCategory$,
    this.selectedPostAction$
  ]).pipe(map(([posts, selectedPostId]) => {
    return posts.find(post => post.id == selectedPostId);
}));

selectPost(postId: string){
    this.selectedPostSubject.next(postId);
}

אנחנו מקבלים לתוך $post את הםוסט שנבחר. עכשיו צריך לקבל אותו ב-single-post. נזריק את ה-service ונקבל את הפוסט.

קובץ single-post.component.ts

export class SinglePostComponent {
  post$ = this.postService.post$;

  constructor(private postService: DeclarativePostsService){}
}

ואפשר להציג אותו.

קובץ single-post.component.html

<div *ngIf="post$ | async as post">
  <div>Id: {{ post.id }}</div>
  <div>Title: {{ post.title }}</div>
  <div>Content: {{ post.body }}</div>
  <div>Category: {{ post.categoryName }}</div>
</div>

כשיש שינוי בבחירה של הפוסט, יש טריגר ל-combineLatest והפוסט מתעדכן. נוכל להוסיף את changeDetection כך שהקומפוננטה תתעדכן רק כשיש שינוי בבחירת הפוסט.

קובץ single-post.component.ts

@Component({
  selector: 'app-single-post',
  templateUrl: './single-post.component.html',
  styleUrls: ['./single-post.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SinglePostComponent {
  post$ = this.postService.post$;

  constructor(private postService: DeclarativePostsService){}
}