Commit 9a3f9ce1 authored by Julian's avatar Julian
Browse files

refactor to blog example

parent 8c0b7311
......@@ -42,13 +42,40 @@ If we understand every of those "broken parts" we are intuitively able to unders
## Combination Operators - Exercise walk through
In order to showcase the different capabilities and constraints of the combination operators we will create a very
simple Blog application. `Post` & `Comment` are the actual entities which can be fetched from service endpoints.
The combination operators will be used to combine `Post` & `Comment` to `BlogPost`.
```Typescript
// entity
interface Post {
id: string;
title: string;
content: string;
}
// entity
interface Comment {
id: string;
postId: string;
text: string;
}
// derivation
interface BlogPost {
id: string;
title: string;
comments: Comment[];
commentCount: number;
}
```
Within this set of lessons we will walk through the following exercises:
- [ ] `forkJoin` -> `combineLatest`
- We start with a simple setup where we derive data in our component directly over HTTP requests by using `forkJoin`
- We notice that `forkJoin` results in too many http calls
- Rebuild
- [ ] To solve it we refactor the give HTTP service to get more control over when we fetch the data
- [ ] `forkJoin` -> `http-service-v1`
- We start with a very simple list example where we derive data in our component directly over HTTP requests by using `forkJoin`
- List is not "reactive" in terms of "adding/updating" -> build a refetch
- We notice that this architecture results in HTTP over-fetching -> introduce simple state -> forkJoin vs. combineLatest
- [ ] `combineLatest`
- To solve it we refactor the give HTTP service to get more control over when we fetch the data
- this reviles one of the special behaviours of `forkJoin` and we need to rethink it usage
- http-service-v1
- [ ] We learn the difference of `forkJoin` and `combineLatest`
......@@ -63,26 +90,5 @@ Within this set of lessons we will walk through the following exercises:
- [ ] With a fresh and open mine we think about those concepts in combination with a UX Pattern called `opt-in updates`
- to give a better experience to our users we implement this pattern in our example
## Domain
```Typescript
interface Post {
id: string;
title: string;
}
interface BlogPost {
id: string;
title: string;
comments: Comment[];
commentCount: number;
}
interface Comment {
id: string;
postId: string;
text: string;
}
```
# Theory of `combineLatest`
# `combineLatest` Theory
## Intro
In the previous example we implemented a self-refetching list of `BlogPost`. We noticed that we were calling problem of over-fetching http calls. To come across this issue
you typically want to introduce some sort of state management.
......@@ -10,9 +10,9 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
<mat-form-field>
<label>Name</label>
<input matInput name="text" [(ngModel)]="text"/>
<input matInput name="text" [(ngModel)]="title"/>
</mat-form-field>
<button mat-raised-button color="primary" (click)="listService.addComment({'text': text, 'postId': 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="addPost()">Add Post</button>
<div *ngIf="blog$ | async as blog">
<mat-list>
......@@ -27,7 +27,8 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
encapsulation: ViewEncapsulation.None
})
export class SolutionCombineLatestComponent {
text: string = '';
title: string = '';
blog$: Observable<BlogPost[]> = combineLatest([
this.listService.posts$,
this.listService.comments$
......@@ -40,4 +41,14 @@ export class SolutionCombineLatestComponent {
this.listService.fetchPosts();
this.listService.fetchComments();
}
addPost() {
this.listService.addPost({title: this.title});
this.refetch();
}
refetch() {
this.listService.fetchPosts();
this.listService.fetchComments();
}
}
import { Component } from '@angular/core';
import { forkJoin, Observable } from 'rxjs';
import { combineLatest, forkJoin, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
......@@ -9,9 +9,9 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
<h3>combineLatest</h3>
<mat-form-field>
<label>Title</label>
<input matInput name="comment" [(ngModel)]="comment"/>
<input matInput name="title" [(ngModel)]="title"/>
</mat-form-field>
<button mat-raised-button color="primary" (click)="listService.addPost({'title': comment})">Add Post</button>
<button mat-raised-button color="primary" (click)="addPost()">Add Post</button>
<div *ngIf="blog$ | async as blog">
<mat-list>
<mat-list-item *ngFor="let post of blog">
......@@ -23,18 +23,30 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
`
})
export class StartCombineLatestComponent {
comment: string = '';
title: string = '';
blog$: Observable<BlogPost[]> = forkJoin([
blog$: Observable<BlogPost[]>;
constructor(public listService: BlogBasicService) {
this.refetch();
}
addPost() {
this.listService.addPost({title: this.title});
this.refetch();
}
refetch() {
this.blog$ = this.getBlogList();
}
private getBlogList(): Observable<BlogPost[]> {
return forkJoin([
this.listService.posts$,
this.listService.comments$
])
.pipe(
map(([posts, comments]) => mergeListsAndItems(posts, comments))
);
constructor(public listService: BlogBasicService) {
this.listService.fetchPosts();
this.listService.fetchComments();
}
}
# `forkJoin` Exercise
## Intro
We want to build a simple Blog application.
To get things started let's display a simple list of `BlogPost`.
There are HTTP Endpoints which provide us with `Comment` & `Post` data.
In addition, we want to add new `BlogPost` entries to the list without having to manually reload the data.
## Exercise
- Use `forkJoin` operator to combine the http calls
- Implement automatic re-fetching after adding a `BlogPost`
## Interfaces
```Typescript
// entity
interface Post {
id: string;
title: string;
content: string;
}
// entity
interface Comment {
id: string;
postId: string;
text: string;
}
// derivation
interface BlogPost {
id: string;
title: string;
comments: Comment[];
commentCount: number;
}
```
# `forkJoin` Solution
## Combining Http Calls
Combining the http calls with the `forkJoin` operator:
```Typescript
forkJoin([
this.listService.httpGetPosts(),
this.listService.httpGetComments()
])
.pipe(
map(([posts, comments]) => mergeListsAndItems(posts, comments))
)
```
## Refetch
Refetching data with `forkJoin` only works if every source emits new values. We have to re-call all
involved HTTP Request in order to update any information.
```html
<button mat-raised-button color="primary" (click)="addPost()">Add Post</button>
```
```Typescript
addPost() {
this.listService.addPost({title: 'new post'});
this.refetch();
}
refetch() {
this.blog$ = this.getBlogList();
}
private getBlogList(): Observable<BlogPost[]> {
return forkJoin([
this.listService.httpGetPosts(),
this.listService.httpGetComments()
])
.pipe(
map(([posts, comments]) => mergeListsAndItems(posts, comments))
);
}
```
......@@ -8,7 +8,7 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
selector: 'solution-custom-http-service-v1',
template: `<h3>(Solution) custom-http-service-v1</h3>
<button mat-raised-button color="primary" (click)="listService.addPost({title: 'new post'}); refetch();">Add Post</button>
<button mat-raised-button color="primary" (click)="addPost()">Add Post</button>
<div *ngIf="blog$ | async as blog">
<mat-list>
......@@ -24,10 +24,15 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
})
export class SolutionHttpServiceV1Component {
blog$: Observable<BlogPost[]> = this.getBlogList();
blog$: Observable<BlogPost[]>;
constructor(public listService: BlogBasicService) {
this.refetch();
}
addPost() {
this.listService.addPost({title: 'new post'});
this.refetch();
}
refetch() {
......
......@@ -7,7 +7,7 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
selector: 'custom-http-service-v1',
template: `<h3>custom-http-service-v1</h3>
<button mat-raised-button color="primary" (click)="listService.addPost({title: 'new post'})">Add Post</button>
<button mat-raised-button color="primary">Add Post</button>
<div *ngIf="blog$ | async as list">
<mat-list>
......
......@@ -2,7 +2,8 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { Comment, Post, upsertEntities } from 'shared';
import { upsertEntities } from 'shared/lib/list-resource/operations/list-operations';
import { Post, Comment } from 'shared/lib/list-resource/models';
interface BlogBasicServiceState {
posts: Post[];
......@@ -59,7 +60,7 @@ export class BlogBasicService {
this.httpPostPost(post)
.subscribe((v) => {
console.log(v);
this.fetchPosts();
// this.fetchPosts();
}, console.log);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment