Imperative Programming
Imperative Programming הוא תכנות מסורתי בו אנחנו אומרים לתוכנית מה לעשות צעד אחרי צעד. תביא מידע מה-DB, אחר כך תעשה איקס וכו'
Declarative Reactive Programming הוא תכנות בו אנחנו אומרים מה אנחנו רוצים לעשות, לא מה הצעדים. Declarative Programming משפר את הביצועים של התוכנית.
למשל, בדרך כלל כדי לקבל מידע מה-DB נעשה subscribe ואחר כל נצטרך לעשות ubsubscribe, זה האחריות של המפתח. ב-Declarative Programming זאת תהיה האחריות של התוכנית ולא של המפתח לעשות את זה. נעשה את זה על ידי async pipe שמטפל בזה.
נתחיל עם דוגמא של Imperative Programming.
לדוגמא אני אשתמש ב-DB עם json-server. כמו כן התקנתי את bootstrap.
פוסטים לדוגמא לקחתי מהאתר jsonplaceholder. למטה יש קישורים, לוחצים על posts ןמקבלים רשימה של פוסטים. אפשר גם להפנות ישירות את הבקשה לשרת שלהם ולמשוך משם את הפוסטים אם לא רוצים להתקין את ה-json-server.
יצירת קומפוננטות בסיס
ניצור כמה קומפוננטות כדי שנראה מידע על המסך.
ng g c --skip-tests=true components/header
ng g c --skip-tests=true components/pages/posts
נקח header בסיסי של bootstrap.
קובץ header.component.html
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" routerLink="/">Home</a>
</li>
</ul>
</div>
</div>
</nav>
נוסיף route לקומפוננטת הפוסטים שיצרנו.
קובץ app-routing.module.ts
const routes: Routes = [
{ path: '', component: PostsComponent }
];
נשים את קוד הקומפוננטות בעמוד הראשי.
קובץ app.component.html
<app-header></app-header>
<div class="container pt-5 pb-5">
<div class="row">
<div class="col-md-12">
<router-outlet></router-outlet>
</div>
</div>
</div>
זה מה שיש על המסך בשלב הזה:
יצירת ה-Service
כדי למשוך את הפוסטים, ניצור service ונייבא את HttpClientModule ל-app.module.
ng g s services/posts
ב-service נמשוך את הםוסטים:
קובץ posts.service.ts
export class PostsService {
baseUrl = 'http://localhost:3000';
constructor(private http: HttpClient) { }
getPosts(){
return this.http.get(`${this.baseUrl}/posts`);
}
}
אנחנו מקבלים מבפונקציה getPosts בחזרה observable. נעבור לקומפוננטת ה-posts ןנקרא לפונקציה getPosts של ה-service.
קובץ posts.component.ts
export class PostsComponent {
constructor(private postsService: PostsService){}
ngOnInit(){
this.postsService.getPosts().subscribe(data => {
console.log(data);
});
}
}
עכשיו אפשר לראות בקונסול את רשימת הפוסטים. עד עכשיו לא עשינו משהו שונה מקריאה רגילה מ-DB.
כדי להראות על המסך את הפוסטים, ניצור קובץ מודל לפוסט.
קובץ post.model.ts
export interface Post{
userId: string,
id: string,
title: string,
body: string
}
נעדכן את ה-service שהוא מקבל מערך של פוסטים:
קובץ posts.service.ts
getPosts(){
return this.http.get<Post[]>(`${this.baseUrl}/posts`);
}
ונכיל את רשימת הפוסטים בקומפוננטה כדי שנוכל להציג אותם.
קובץ posts.component.ts
export class PostsComponent {
posts: Post[] = [];
postsSubscription!: Subscription;
constructor(private postsService: PostsService){}
ngOnInit(){
this.postsSubscription = this.postsService.getPosts().subscribe(data => {
this.posts = data;
console.log(data);
});
}
ngOnDestroy(){
this.postsSubscription && this.postsSubscription.unsubscribe();
}
}
הצגת המידע
נציג את הפוסטים.
קובץ posts.component.html
<div class="row">
<div class="col-md-12">
<h3>Posts Data</h3>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Title</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let post of posts">
<td>{{ post.id }}</td>
<td>{{ post.title }}</td>
<td>{{ post.body }}</td>
</tr>
</tbody>
</table>
</div>
</div>
והמסך נראה ככה:
נסבך קצת את הקוד. נוסיף ל-DB טבלת קטגוריות עם id, title לכל קטגוריה. נוסיף לכל פוסט id לקטגוריה ונמשוך לטבלה את המספרים האלה.
עכשיו התצוגה נראית ככה:
כמובן שבעמודת הקטגוריה אנחנו לא רוצים לראות מספר אלא את שם הקטגוריה.
ניצור interface לקטגוריות וניצור service לקטגוריות שבו נקבל את רשימת הקטגוריות.
קובץ post.model.ts
export interface Category{
id: string,
title: string
}
קובץ categories.service.ts
export class CategoriesService {
baseUrl = 'http://localhost:3000';
constructor(private http: HttpClient) { }
getCategories(){
return this.http.get<Category[]>(`${this.baseUrl}/categories`);
}
}
עכשיו צריך לעשות כמה פעולות:
- נוסיף את categoryName למודל הפוסטים שלנו.
- נזריק ל-service של הפוסטים את ה-CategoriesService.
- ניצור ב-service פונקציה שמחזירה את הפוסטים עם הקטגוריות.
- נשנה את הפונקציה שקוראת לפוסטים לזו החדשה עם שמות הקטגוריות.
קובץ posts.model.ts
export interface Post{
userId: string,
id: string,
title: string,
body: string,
categoryId: string,
categoryName?: string
}
export interface Category{
id: string,
title: string
}
קובץ posts.service.ts
getPostsWithCategories(){
return this.getPosts().pipe(
mergeMap(posts => {
return this.categoriesService.getCategories()
.pipe(map(categories => {
return posts.map(post => {
return {
...post,
categoryName: categories.find(category => category.id == post.categoryId)?.title
}
})
}));
})
)
}
קובץ posts.component.ts
ngOnInit(){
this.postsSubscription = this.postsService.getPostsWithCategories().subscribe(data => {
this.posts = data;
});
}
קובץ posts.component.html
<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>
עכשיו טבלת הפוסטים נראית ככה:
עד עכשיו כל מה שעשינו נכנס בתוך Imperative Programming. אנחנו אומרים לתוכנית צעד אחרי צעד מה לעשות.