Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Stéphane Roucheray
Reactive-Architecture-and-UX-Patterns_Angular
Commits
9a3f9ce1
Commit
9a3f9ce1
authored
May 13, 2020
by
Julian
Browse files
refactor to blog example
parent
8c0b7311
Changes
10
Show whitespace changes
Inline
Side-by-side
projects/combining-streams/src/lib/combining-streams.intro.md
View file @
9a3f9ce1
...
...
@@ -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;
}
```
projects/combining-streams/src/lib/exercises/combineLatest/docs/combineLatest.theroy.md
View file @
9a3f9ce1
# 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.
projects/combining-streams/src/lib/exercises/combineLatest/solution.combineLatest.component.ts
View file @
9a3f9ce1
...
...
@@ -10,9 +10,9 @@ import { BlogBasicService, BlogPost, mergeListsAndItems } from 'shared';
<mat-form-field>
<label>Name</label>
<input matInput name="text" [(ngModel)]="t
ext
"/>
<input matInput name="text" [(ngModel)]="t
itle
"/>
</mat-form-field>
<button mat-raised-button color="primary" (click)="
listService.addComment({'text': text, 'postId': 1}
)">Add
Item
</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
();
}
}
projects/combining-streams/src/lib/exercises/combineLatest/start.combineLatest.component.ts
View file @
9a3f9ce1
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
();
}
}
projects/combining-streams/src/lib/exercises/http-service-v1/docs/http-service-v1.exercise.md
View file @
9a3f9ce1
# `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;
}
```
projects/combining-streams/src/lib/exercises/http-service-v1/docs/http-service-v1.solution.md
View file @
9a3f9ce1
# `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))
);
}
```
projects/combining-streams/src/lib/exercises/http-service-v1/docs/http-service-v1.theory.md
View file @
9a3f9ce1
# `forkJoin` Theory
## Intro
projects/combining-streams/src/lib/exercises/http-service-v1/solution.http-service-v1.component.ts
View file @
9a3f9ce1
...
...
@@ -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
()
{
...
...
projects/combining-streams/src/lib/exercises/http-service-v1/start.http-service-v1.component.ts
View file @
9a3f9ce1
...
...
@@ -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>
...
...
projects/shared/src/lib/list-resource/blog-basic.service.ts
View file @
9a3f9ce1
...
...
@@ -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
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment