Commit f6549f82 authored by Michael Hladky's avatar Michael Hladky
Browse files

add flattening examples

parent 7180f455
import {HttpClient, HttpParams} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable, of} from 'rxjs';
import {catchError, map, tap} from 'rxjs/operators';
import {Post} from 'shared';
interface BlogServiceState {
postsSearchResult: Post[];
}
@Injectable({
providedIn: 'root'
})
export class MergeAllBlogService {
private readonly baseUrl = 'api';
private readonly postUrl = [this.baseUrl, 'post'].join('/');
private readonly state$ = new BehaviorSubject<BlogServiceState>({
postsSearchResult: [] as Post[]
});
constructor(private http: HttpClient) {
}
searchPosts(searchString: string): Observable<Post[]> {
return this.httpGetPosts({title: searchString});
}
httpGetPosts(params?: HttpParams | {
[param: string]: string | string[];
}): Observable<Post[]> {
console.log('httpGetPosts')
return this.http.get<Post[]>(this.postUrl, {params}).pipe(
tap(v => console.log('result', v)),
catchError(() => of([] as Post[]))
);
}
}
import {NgModule} from '@angular/core';
import {CommonModule} from "@angular/common";
import {RouterModule} from "@angular/router";
import {ROUTES} from "./mergeAll.routes";
import {MatButtonModule} from "@angular/material/button";
import {MatListModule} from "@angular/material/list";
import {MatFormFieldModule} from "@angular/material/form-field";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatInputModule} from "@angular/material/input";
import {StartMergeAllComponent} from "./start.mergeAll.component";
import {SolutionMergeAllComponent} from "./solution.mergeAll.component";
@NgModule({
declarations: [
SolutionMergeAllComponent,
StartMergeAllComponent
],
imports: [
CommonModule,
MatButtonModule,
MatListModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
RouterModule.forChild(ROUTES)
]
})
export class MergeAllModule {
}
import {StartMergeAllComponent} from "./start.mergeAll.component";
import {SolutionMergeAllComponent} from "./solution.mergeAll.component";
export const ROUTES = [
{
path: '',
children: [
{
path: '',
component: StartMergeAllComponent
},
{
path: 'solution',
component: SolutionMergeAllComponent
}
]
}
];
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
import {MergeAllBlogService} from './mergeAll-blog.service';
import {from, Observable, Subject} from 'rxjs';
import {Post} from 'shared';
import {debounceTime, distinctUntilChanged, map, mergeAll, skip, switchMap, tap} from "rxjs/operators";
import {FormBuilder} from "@angular/forms";
@Component({
selector: 'solution-switchMap',
template: `
<h1>(Solution) switchMap</h1>
<form [formGroup]="editForm">
<mat-form-field>
<label>Title</label>
<input matInput name="post" formControlName="title"/>
</mat-form-field>
<br/>
<p>Last change to field: {{lastChange$ | async | json}}</p>
<mat-form-field>
<label>Description</label>
<textarea
matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"
name="post"
formControlName="description">
</textarea>
</mat-form-field>
</form>
`
})
export class SolutionMergeAllComponent {
editForm = this.fb.group({
title: [],
description: []
});
arrayOfChangesByFieldObservables = Object.entries(this.editForm.controls).map(
([name, fc]) => fc.valueChanges.pipe(
this.applyInputBehaviour,
map(v => ({[name]: v})))
);
lastChange$ = from(this.arrayOfChangesByFieldObservables).pipe(
mergeAll(),
skip(1)
);
constructor(public blogPostService: MergeAllBlogService, private fb: FormBuilder) {
this.updateForm(3);
}
updateForm(id) {
this.blogPostService.httpGetPosts({id}).subscribe(
posts => this.editForm.patchValue(posts.pop())
);
}
applyInputBehaviour(o$) {
return o$.pipe(
distinctUntilChanged(),
debounceTime(250)
);
}
}
import {Component} from '@angular/core';
import {from, of} from 'rxjs';
import {MergeAllBlogService} from "./mergeAll-blog.service";
import {FormBuilder} from "@angular/forms";
import {debounceTime, distinctUntilChanged, map, mergeAll, skip} from "rxjs/operators";
@Component({
selector: 'mergeAll',
template: `
<h1>mergeAll</h1>
<form [formGroup]="editForm">
<mat-form-field>
<label>Title</label>
<input matInput name="post" formControlName="title"/>
</mat-form-field>
<br/>
<p>Last change to field: {{lastChange$ | async | json}}</p>
<mat-form-field>
<label>Description</label>
<textarea
matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"
name="post"
formControlName="description">
</textarea>
</mat-form-field>
</form>
`,
})
export class StartMergeAllComponent {
editForm = this.fb.group({
title: [],
description: []
});
// Create a stream that contains the name of the field that changed lastly
lastChange$ = of({title: 'Post 3'});
constructor(public blogPostService: MergeAllBlogService, private fb: FormBuilder) {
this.updateForm(3);
}
updateForm(id) {
this.blogPostService.httpGetPosts({id}).subscribe(
posts => this.editForm.patchValue(posts.pop())
);
}
}
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable, of} from 'rxjs';
import {catchError, map} from 'rxjs/operators';
import {Post} from 'shared';
interface BlogServiceState {
posts: Post[];
}
@Injectable({
providedIn: 'root'
})
export class MergeMapBlogService {
private readonly baseUrl = 'api';
private readonly postUrl = [this.baseUrl, 'post'].join('/');
private readonly state$ = new BehaviorSubject<BlogServiceState>({
posts: [] as Post[],
});
readonly posts$ = this.state$.pipe(map(s => s.posts));
constructor(private http: HttpClient) {
this.fetchPosts();
}
fetchPosts() {
this.httpGetPosts()
.subscribe(posts => {
this.state$.next({
...this.state$.getValue(),
posts
});
});
}
httpGetPosts(): Observable<Post[]> {
return this.http.get<Post[]>(this.postUrl).pipe(
catchError(() => of([] as Post[]))
);
}
httpDeletePosts(params: {
id: number;
}): Observable<Post[]> {
return this.http.delete<Post[]>([this.postUrl, params.id].join('/')).pipe(
catchError(() => of([] as Post[]))
);
}
}
import {NgModule} from '@angular/core';
import {CommonModule} from "@angular/common";
import {RouterModule} from "@angular/router";
import {ROUTES} from "./mergeMap.routes";
import {MatButtonModule} from "@angular/material/button";
import {MatListModule} from "@angular/material/list";
import {MatFormFieldModule} from "@angular/material/form-field";
import {FormsModule} from "@angular/forms";
import {MatInputModule} from "@angular/material/input";
import {StartMergeMapComponent} from "./start.mergeMap.component";
import {SolutionMergeMapComponent} from "./solution.mergeMap.component";
@NgModule({
declarations: [
SolutionMergeMapComponent,
StartMergeMapComponent
],
imports: [
CommonModule,
MatButtonModule,
MatListModule,
MatFormFieldModule,
MatInputModule,
FormsModule,
RouterModule.forChild(ROUTES)
]
})
export class MergeMapModule {
}
import {StartMergeMapComponent} from "./start.mergeMap.component";
import {SolutionMergeMapComponent} from "./solution.mergeMap.component";
export const ROUTES = [
{
path: '',
children: [
{
path: '',
component: StartMergeMapComponent
},
{
path: 'solution',
component: SolutionMergeMapComponent
}
]
}
];
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
import {MergeMapBlogService} from './mergeMap-blog.service';
import {Observable, Subject} from 'rxjs';
import {Post} from 'shared';
import {exhaustMap, mergeMap, tap} from "rxjs/operators";
@Component({
selector: 'solution-exhaustMap',
template: `
<h1>(Solution) exhaustMap</h1>
<mat-list>
<mat-list-item *ngFor="let item of posts$ | async">
<span mat-line>{{item.title}}</span>
<button mat-raised-button color="accent" (click)="delete(item.id)">Delete</button>
</mat-list-item>
</mat-list>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class SolutionMergeMapComponent {
posts$: Observable<Post[]> = this.blogPostService.posts$;
deletePostIds$ = new Subject<number>();
constructor(public blogPostService: MergeMapBlogService) {
this.deletePostIds$
.pipe(
mergeMap(id => this.blogPostService.httpDeletePosts({id}))
)
.subscribe(
() => this.blogPostService.fetchPosts()
);
}
delete(id: number) {
console.log('post delete fired')
this.deletePostIds$.next(id);
}
}
import {Component} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {Post} from 'shared';
import {MergeMapBlogService} from "./mergeMap-blog.service";
import {mergeMap} from "rxjs/operators";
@Component({
selector: 'mergeMap',
template: `
<h1>mergeMap</h1>
<mat-list>
<mat-list-item *ngFor="let item of posts$ | async">
<span mat-line>{{item.title}}</span>
<button mat-raised-button color="accent" (click)="delete(item.id)">Delete</button>
</mat-list-item>
</mat-list>
`
})
export class StartMergeMapComponent {
posts$: Observable<Post[]> = this.blogPostService.posts$;
deletePostIds$ = new Subject<number>();
constructor(public blogPostService: MergeMapBlogService) {
this.deletePostIds$
.pipe(
mergeMap(id => this.blogPostService.httpDeletePosts({id}))
)
.subscribe(
() => this.blogPostService.fetchPosts()
);
}
delete(id: number) {
this.deletePostIds$.next(id);
}
}
import {HttpClient, HttpParams} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable, of, Subject} from 'rxjs';
import {
catchError,
distinctUntilChanged,
endWith,
map,
share,
startWith,
switchMap,
switchMapTo,
tap
} from 'rxjs/operators';
import {Post} from 'shared';
interface BlogServiceState {
postsSearchResult: Post[];
searchPending: boolean;
}
@Injectable({
providedIn: 'root'
})
export class StartWithBlogService {
private readonly baseUrl = 'api';
private readonly postUrl = [this.baseUrl, 'post'].join('/');
private readonly searchTrigger$ = new Subject<any>();
private readonly state$ = new BehaviorSubject<BlogServiceState>({
postsSearchResult: [] as Post[],
searchPending: false
});
searchPending$ = this.state$.pipe(map(s => s.searchPending), distinctUntilChanged(), share())
postsSearchResult$ = this.state$.pipe(map(s => s.postsSearchResult), distinctUntilChanged(), share())
constructor(private http: HttpClient) {
this.searchTrigger$.pipe(
switchMap(query => this.httpGetPosts(query).pipe(
map(res => ({postsSearchResult: res})),
startWith({searchPending: true}),
endWith({searchPending: false})
))
).subscribe(
stateSlice => this.state$.next({...this.state$.getValue(), ...stateSlice})
);
}
searchPosts(searchString: string): void {
this.searchTrigger$.next({title: searchString})
}
httpGetPosts(params?: {
[param: string]: string;
}): Observable<Post[]> {
return this.http.get<Post[]>(this.postUrl, {params}).pipe(
catchError(() => of([] as Post[])),
);
}
}
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