Declarative Programming

חלק 1: Imperative Programming | חלק 2: Change Detection Strategy & Unsubscribing

בחלקים הקודמים השתמשנו ב-Imperative Programming כדי למשוך את רשימת הפוסטים ואת רשימת הקטגוריות. נראה את אותה פונקציונליות עם Declarative Programming. כדי להשאיר את מה שכבר יש לנו ניצור קומפוננטה חדשה עבור הפוסטים וכן service חדש.

ng g c components/pages/declaratove-posts
ng g s services/declarative-posts

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

קובץ app-routing.module.ts

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

נוסיף ל-header קישור לקומפוננטה החדשה.

קובץ header.component.html

<ul class="navbar-nav">
    <li class="nav-item">
        <a class="nav-link active" aria-current="page" 
            routerLink="/">Posts</a>
    </li>
    <li class="nav-item">
        <a class="nav-link active" aria-current="page" 
            routerLink="decposts">Declarative Posts</a>
    </li>
</ul>

נוסיף את changeDetection: ChangeDetectionStrategy.OnPush לקומפוננטה. עכשיו נלך להביא את המידע על הפוסטים.

שימוש ב-Declarative Programming

ב-service ניצור משתנה posts$ ונקרא ישירות אליו את קריאת ה-http. המשתנה הזה יהיה מסוג Observable של Post.

קובץ declarative-posts.service.ts

export class DeclarativePostsService {
  baseUrl = 'http://localhost:3000';
  posts$ = this.http.get<Post[]>(`${this.baseUrl}/posts`);

  constructor(private http: HttpClient) { }
}

בקומפוננטה נזריק את ה-service. נחבר את המשתנה של ה-service למשתנה מקומי שנייצר.

קובץ declarative-posts.component.ts

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

כאן אנחנו לא עושים את פעולת ה-subscribe. זה יקרה בקובץ ה-html.

נעתיק את הקוד של קובץ ה-html של הצגת הפוסטים. כאן, במקום משתנה ה-posts יש לנו observable של הפוסטים. שימוש ב-pipe ה-async יבצע את פעולת ה-subscribe על מנת להפעיל את ה-observable. ה-async גם יטפל כבר בפעולת ה-unsubscribe.

אנחנו גם נגיד לפעולת ה-async איך לקרוא למשתנה שחוזר מה-observable.

קובץ declarative-posts.component.html

<div class="row">
  <div class="col-md-12">
    <h3>Posts Data</h3>

    <table class="table" *ngIf="posts$ | async as posts">
      <thead>
        <tr>
          <th>Id</th>
          <th>Title</th>
          <th>Body</th>
          <th>Category</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let post of posts">
          <td>{{ post.id }}</td>
          <td>{{ post.title }}</td>
          <td>{{ post.body }}</td>
          <td>{{ post.categoryName }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

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

אופרטור combineLatestWith

האופרטור combineLatestWith יוצר observable שמחבר בין ערכים של observables שונים. השימוש בו הוא כשיש לנו observables שמסתמכים האחד על השני בדרך כלשהי.

האופרטור ForkJoin

האופרטור ForkJoin עובד בצורה דומה ל-combineLatestWith, אבל הוא מבצע את הפעולה שלו ושולח תוצאה רק כשכל ה-observables סיימו לשלוח את הערכים שלהם.

ניצור service עבור הבאת הקטגוריות.

ng g s services/declarative-categories

קובץ declarative-categories.service.ts

export class DeclarativeCategoriesService {
  baseUrl = 'http://localhost:3000';
  categories$ = this.http.get<Category[]>(`${this.baseUrl}/categories`);

  constructor(private http: HttpClient) { }
}

יש לנו observable שהביא את הפוסטים ועכשיו יש לנו את זה שמביא את הקטגוריות. נשתמש ב-combineLatest כדי לאחד אותם.

קובץ declarative-posts.service.ts

postsWithCategory$ = combineLatest([
    this.posts$,
    this.categoriesService.categories$
  ]).pipe(
      map( ([posts, categories]) => {
      return posts.map((post) => {
        return {
          ...post,
          categoryName: categories.find(category => category.id === post.categoryId)?.title
        } as Post
      })
}));

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

קובץ declarative-posts.component.ts

posts$ = this.postService.postsWithCategory$;

ניווט במאמר

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

Weekly Tutorial