Commit 09bab894 authored by micha's avatar micha
Browse files

add code for opt-in-updates

parent ca67033d
......@@ -13,7 +13,7 @@ import {combineLatestListService} from "combining-streams/lib/exercises/combineL
<label>Name</label>
<input matInput name="iName" [(ngModel)]="iName"/>
</mat-form-field>
<button (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<div *ngIf="list$ | async as list">
<mat-list>
......@@ -36,7 +36,7 @@ export class SolutionCombineLatestComponent {
map(([list, items]) => mergeListsAndItems(list, items))
);
constructor(private listService: combineLatestListService) {
constructor(public listService: combineLatestListService) {
this.listService.refetchLists();
this.listService.refetchItems();
}
......
......@@ -12,7 +12,7 @@ import {combineLatestListService} from "combining-streams/lib/exercises/combineL
<label>Name</label>
<input matInput name="iName" [(ngModel)]="iName"/>
</mat-form-field>
<button (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<div *ngIf="list$ | async as list">
<mat-list>
......@@ -33,7 +33,7 @@ export class StartCombineLatestComponent {
map(([list, items]) => mergeListsAndItems(list, items))
);
constructor(private listService: combineLatestListService) {
constructor(public listService: combineLatestListService) {
this.listService.refetchLists();
this.listService.refetchItems();
}
......
......@@ -9,7 +9,7 @@ import {combineLatestListService} from "combining-streams/lib/exercises/combineL
selector: 'solution-custom-http-service-v1',
template: `<h3>(Solution) custom-http-service-v1</h3>
<button (click)="listService.addItem({iName: 'new item', lId: 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="listService.addItem({iName: 'new item', lId: 1})">AddItem</button>
<div *ngIf="list$ | async as list">
<mat-list>
......@@ -32,7 +32,7 @@ export class SolutionHttpServiceV1Component {
map(([list, items]) => mergeListsAndItems(list, items))
);
constructor(private listService: combineLatestListService) {
constructor(public listService: combineLatestListService) {
this.listService.refetchLists();
this.listService.refetchItems();
}
......
......@@ -8,7 +8,7 @@ import {map} from "rxjs/operators";
selector: 'custom-http-service-v1',
template: `<h3>custom-http-service-v1</h3>
<button (click)="listService.addItems({iName: 'new item', lId: 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="listService.addItems({iName: 'new item', lId: 1})">AddItem</button>
<div *ngIf="list$ | async as list">
<mat-list>
......@@ -31,7 +31,7 @@ export class StartHttpServiceV1Component {
map(([lists, items]) => mergeListsAndItems(lists, items))
);
constructor(private listService: StartHttpV1Service) {
constructor(public listService: StartHttpV1Service) {
}
......
import {Injectable} from "@angular/core";
import {map, withLatestFrom} from "rxjs/operators";
import {Observable} from "rxjs";
import {RxState} from "@rx-angular/state";
import {Item, List} from "shared";
export interface BasicState {
lists: List[];
items: Item[];
}
@Injectable({providedIn: 'root'})
export class BasicStoreService extends RxState<BasicState> {
initState() {
this.set({
items: [
{iId: '1', iName: 'item1', lId: '1'},
{iId: '2', iName: 'item2', lId: '1'},
{iId: '3', iName: 'item3', lId: '2'}
],
lists: [
{lId: '1', lName: 'list1'},
{lId: '2', lName: 'list2'}
]
});
}
connectUpsertManyItems(entities$: Observable<Item[]>) {
this.connect(
entities$
.pipe(
withLatestFrom(this.select('items')),
map(([entities, items]) => {
return {items: items.concat(entities)}
})
)
);
}
connectUpsertManyLists(entities$: Observable<List[]>) {
this.connect(
entities$
.pipe(
withLatestFrom(this.select('lists')),
map(([entities, lists]) => {
return {lists: lists.concat(entities)}
})
)
);
}
constructor() {
super();
this.set({lists: [], items: []});
}
}
import {Injectable} from '@angular/core';
import {BehaviorSubject, combineLatest, Observable, of} from 'rxjs';
import {catchError, map} from 'rxjs/operators';
import {Item, JoinedItem, List, mergeListsAndItems, upsertEntities} from "shared";
import {StartHttpV1Service} from "combining-streams/lib/exercises/http-service-v1/start.http-v1.service";
import {HttpClient} from "@angular/common/http";
interface ListServiceState {
lists: List[];
items: Item[];
}
@Injectable({
providedIn: 'root'
})
export class optInUpdatesV1ListService {
private readonly baseUrl = 'api';
private readonly itemUrl = [this.baseUrl, 'item'].join('/');
private readonly listUrl = [this.baseUrl, 'list'].join('/');
private readonly state$ = new BehaviorSubject<ListServiceState>({
lists: [] as List[],
items: [] as Item[]
});
lists$ = this.state$.pipe(map(s => s.lists));
items$ = this.state$.pipe(map(s => s.items));
constructor(private http: HttpClient) {
}
refetchLists() {
this.httpGetLists()
.subscribe(lists => {
console.log('upsert:', upsertEntities(this.state$.getValue().lists, lists, 'id'));
this.state$.next({
...this.state$.getValue(),
lists: upsertEntities(this.state$.getValue().lists, lists, 'id')
});
});
}
httpGetLists(): Observable<List[]> {
return this.http.get<List[]>(this.listUrl).pipe(
catchError(() => of([] as List[]))
);
}
httpPostItems(item: Pick<Item, 'iName' | 'lId'>): Observable<Item[]> {
return this.http.post<Item[]>(this.itemUrl, item);
}
addItem(item: Pick<Item, 'iName' | 'lId'>) {
this.httpPostItems(item)
.subscribe((v) => {
console.log(v);
this.refetchItems()
}, console.log);
}
refetchItems() {
this.httpGetItems()
.subscribe(items => {
this.state$.next({
...this.state$.getValue(),
items: upsertEntities(this.state$.getValue().items, items, 'id')
});
});
}
httpGetItems(): Observable<Item[]> {
return this.http.get<Item[]>(this.itemUrl).pipe(
catchError(() => of([] as Item[]))
);
}
}
......@@ -8,6 +8,8 @@ import {RouterModule} from "@angular/router";
import {ROUTES} from "./opt-in-updates-v1.routes";
import {NgModule} from "@angular/core";
import {MatFormFieldModule} from "@angular/material/form-field";
import {FormsModule} from "@angular/forms";
import {MatInputModule} from "@angular/material/input";
@NgModule({
declarations: [
......@@ -16,9 +18,11 @@ import {MatFormFieldModule} from "@angular/material/form-field";
],
imports: [
CommonModule,
FormsModule,
MatButtonModule,
MatListModule,
MatFormFieldModule,
MatInputModule,
HttpClientModule,
RouterModule.forChild(ROUTES)
]
......
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
import {combineLatest, concat, Observable, Subject} from "rxjs";
import {map, take, withLatestFrom} from "rxjs/operators";
import {Component} from '@angular/core';
import {combineLatest, concat, Observable, of, Subject} from "rxjs";
import {filter, map, shareReplay, take, withLatestFrom} from "rxjs/operators";
import {JoinedItem, mergeListsAndItems} from "shared";
import {BasicStoreService} from "combining-streams/lib/exercises/opt-in-updates-v1/basic.store";
import {optInUpdatesV1ListService} from "combining-streams/lib/exercises/opt-in-updates-v1/opt-in-updates-v1-list.service";
@Component({
selector: 'solution-opt-in-updates-basic',
template: `<h3>(Solution) Opt-in Updates</h3>
<mat-form-field>
<label>Name</label>
<input matInput (input)="nameInput.next($event)"/>
<input matInput name="iName" [(ngModel)]="iName"/>
</mat-form-field>
<button mat-raised-button color="primary" (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="saveClick.next($event)">
Save
</button>
<ng-container *ngIf="(numNewItems$ | async)+'' as numItems">
<ng-container *ngIf="(numNewItems$ | async) as numItems">
<button mat-raised-button color="accent"
*ngIf="numItems > 0"
(click)="optInListClick.next($event)">
(click)="optInListClick$.next($event)">
Update List ({{(numItems)}})
</button>
</ng-container>
<div *ngIf="joinedItemList$ | async as list">
<div *ngIf="acceptedItems$ | async as list">
<mat-list>
<mat-list-item *ngFor="let item of list">
{{item.iName}}
</mat-list-item>
</mat-list>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
`
})
export class SolutionOptInUpdatesV1Component {
items$ = this.s.select('items');
lists$ = this.s.select('lists');
iName = 'my new item';
optInListClick$ = new Subject();
nameInput = new Subject<Event>();
nameValue$ = this.nameInput.pipe(map((e: any) => e.target.value));
saveClick = new Subject<Event>();
optInListClick = new Subject<Event>();
joinedList$ = combineLatest([
this.listService.lists$.pipe(filter(l => !!l.length)),
this.listService.items$.pipe(filter(l => !!l.length))
]).pipe(
map(([list, items]) => mergeListsAndItems(list, items)),
shareReplay(1)
);
acceptedItems$ = concat(
this.items$.pipe(take(1)),
this.optInListClick.pipe(
withLatestFrom(this.items$),
this.joinedList$.pipe(take(1)),
this.optInListClick$.pipe(
withLatestFrom(this.joinedList$),
map(([_, items]) => items)
)
);
numNewItems$: Observable<number> = combineLatest(
this.items$,
numNewItems$: Observable<number> = combineLatest([
this.joinedList$,
this.acceptedItems$
)
])
.pipe(
map(([a, b]) => Math.abs(a.length - b.length))
);
joinedItemList$: Observable<JoinedItem[]> = combineLatest([
this.lists$,
this.acceptedItems$
])
.pipe(
map(([lists, items]) => mergeListsAndItems(lists, items))
);
constructor(private s: BasicStoreService) {
this.s.initState();
this.s.connectUpsertManyItems(this.saveClick.pipe(
withLatestFrom(this.nameValue$),
map(([_, iName]) => ([{iId: ~~(Math.random() * 100) + '', iName, lId: '1'}]))
))
constructor(private listService: optInUpdatesV1ListService) {
this.listService.refetchLists();
this.listService.refetchItems();
}
}
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
import {combineLatest, Observable, Subject} from "rxjs";
import {map, withLatestFrom} from "rxjs/operators";
import {JoinedItem, ListService, mergeListsAndItems} from "shared";
import {Component} from '@angular/core';
import {combineLatest, concat, of, Subject} from "rxjs";
import {filter, map, shareReplay, take, withLatestFrom} from "rxjs/operators";
import {mergeListsAndItems} from "shared";
import {optInUpdatesV1ListService} from "combining-streams/lib/exercises/opt-in-updates-v1/opt-in-updates-v1-list.service";
@Component({
selector: 'opt-in-updates',
template: `<h3>Opt-in Updates</h3>
selector: 'opt-in-updates',
template: `<h3>Opt-in Updates</h3>
<mat-form-field>
<label>Name</label>
<input matInput (input)="nameInput.next($event)"/>
</mat-form-field>
<button mat-raised-button color="primary" (click)="saveClick.next($event)">
Save
</button>
<mat-form-field>
<label>Name</label>
<input matInput name="iName" [(ngModel)]="iName"/>
</mat-form-field>
<button mat-raised-button color="primary" (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<ng-container *ngIf="(numNewItems$ | async) as numItems">
<button mat-raised-button color="accent"
(click)="optInListClick.next($event)">
Update List (optional number of items)
*ngIf="numItems > 0"
(click)="optInListClick$.next($event)">
Update List ({{(numItems)}})
</button>
<!--
DEBUG:
joinedItemList$: {{numNewItems$ | async | json}}<br/>
-->
<div *ngIf="joinedItemList$ | async as list">
<mat-list>
<mat-list-item *ngFor="let item of list">
{{item.iName}} - {{item.lName}}
</mat-list-item>
</mat-list>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
</ng-container>
<div *ngIf="joinedList$ | async as list">
<mat-list>
<mat-list-item *ngFor="let item of list">
{{item.iName}}
</mat-list-item>
</mat-list>
</div>
`
})
export class StartOptInUpdatesV1Component {
nameInput = new Subject<Event>();
nameValue$ = this.nameInput.pipe(map((e: any) => e.target.value));
saveClick = new Subject<Event>();
optInListClick = new Subject<Event>();
joinedItemList$: Observable<JoinedItem[]> = combineLatest([
this.s.lists$,
this.s.items$
])
.pipe(
map(([lists, items]) => mergeListsAndItems(lists, items))
);
constructor(private s: ListService) {
}
iName = 'my new item';
optInListClick$ = new Subject();
numNewItems$ = of(0);
joinedList$ = combineLatest([
this.listService.lists$.pipe(filter(l => !!l.length)),
this.listService.items$.pipe(filter(l => !!l.length))
]).pipe(
map(([list, items]) => mergeListsAndItems(list, items)),
shareReplay(1)
);
constructor(private listService: optInUpdatesV1ListService) {
this.listService.refetchLists();
this.listService.refetchItems();
}
}
......@@ -12,7 +12,7 @@ import {zipListService} from "combining-streams/lib/exercises/zip/zip-list.servi
<label>Name</label>
<input matInput name="iName" [(ngModel)]="iName"/>
</mat-form-field>
<button (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<p><b>renders: {{renders()}}</b></p>
<p><b>processJoinedList: {{processJoinedList()}}</b></p>
......@@ -90,7 +90,7 @@ export class SolutionZipComponent {
tap(v => ++this.numProcessLikedList)
);
constructor(private listService: zipListService) {
constructor(public listService: zipListService) {
this.listService.refetchLists();
this.listService.refetchItems();
}
......
......@@ -12,7 +12,7 @@ import {zipListService} from "combining-streams/lib/exercises/zip/zip-list.servi
<label>Name</label>
<input matInput name="iName" [(ngModel)]="iName"/>
</mat-form-field>
<button (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<button mat-raised-button color="primary" (click)="listService.addItem({'iName': iName, 'lId': 1})">AddItem</button>
<p><b>renders: {{renders()}}</b></p>
<p><b>processJoinedList: {{processJoinedList()}}</b></p>
......@@ -88,7 +88,7 @@ export class StartZipComponent {
tap(v => ++this.numProcessLikedList)
);
constructor(private listService: zipListService) {
constructor(public listService: zipListService) {
this.listService.refetchLists();
this.listService.refetchItems();
}
......
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