Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!--<div>-->
<!-- <h1>Case 1</h1>-->
<!-- <div>-->
<!-- <button (click)="refresh$$.next(null)">refresh</button>-->
<!-- <div>-->
<!-- @if(case1$ | async ; as data){-->
<!-- <rx-stateful-state-visualizer [state]="data"/>-->
<!-- }-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->


<div>
<h1>Bugfix Reproduction</h1>
<div>
<button (click)="deleteAction$.next(1)">trigger delete</button>
<div>
@if(delete$ | async ; as data){
<rx-stateful-state-visualizer [state]="data"/>
}
</div>
</div>
</div>




<div>
<h1>Bugfix Reproduction Normal Case</h1>
<div>
<button (click)="refresh$.next(null)">trigger refresh</button>
<div>
@if(two$ | async ; as data){
<rx-stateful-state-visualizer [state]="data"/>
}
</div>
</div>
</div>

<demo-non-flicker/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
button{
padding: 8px 16px;
border-radius: 9999px;
font-weight: bold;
border: 2px solid deepskyblue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AllUseCasesComponent } from './all-use-cases.component';

describe('AllUseCasesComponent', () => {
let component: AllUseCasesComponent;
let fixture: ComponentFixture<AllUseCasesComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AllUseCasesComponent],
}).compileComponents();

fixture = TestBed.createComponent(AllUseCasesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
151 changes: 151 additions & 0 deletions apps/demo-rx-stateful/src/app/all-use-cases/all-use-cases.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import {Component, inject, Injectable} from '@angular/core';
import { CommonModule } from '@angular/common';
import {HttpClient, HttpErrorResponse} from "@angular/common/http";
import {delay, Observable, of, OperatorFunction, scan, Subject, switchMap, timer} from "rxjs";
import {RxStateful, rxStateful$, withAutoRefetch, withRefetchOnTrigger} from "@angular-kit/rx-stateful";
import {Todo} from "../types";
import {RxStatefulStateVisualizerComponent} from "./rx-stateful-state-visualizer.component";
import {NonFlickerComponent} from "./non-flicker/non-flicker.component";

type Data = {
id: number;
name: string
}

const DATA: Data[] = [
{id: 1, name: 'ahsd'},
{id: 2, name: 'asdffdsa'},
{id: 3, name: 'eeasdf'},
]

@Injectable({providedIn: 'root'})
export class DataService {
private readonly http = inject(HttpClient)


getData(opts?: {delay?: number}){
return timer(opts?.delay ?? 1000).pipe(
switchMap(() => of(DATA))
)
}

getById(id: number, opts?: {delay?: number}){
return timer(opts?.delay ?? 1000).pipe(
switchMap(() => of(DATA.find(v =>v.id === id)))
)
}
}

@Component({
selector: 'demo-all-use-cases',
standalone: true,
imports: [CommonModule, RxStatefulStateVisualizerComponent, NonFlickerComponent],
templateUrl: './all-use-cases.component.html',
styleUrl: './all-use-cases.component.scss',
})
export class AllUseCasesComponent {
private readonly http = inject(HttpClient)
private readonly data = inject(DataService)
readonly refresh$$ = new Subject<null>()
refreshInterval = 10000
/**
* Für alle Use Cases eine demo machen
*/

/**
* Case 1
* Basic Usage with automatic refetch and a refreshtrigger
*/
case1$ = rxStateful$<Data[], Error>(
this.data.getData(),
{
refetchStrategies: [
withRefetchOnTrigger(this.refresh$$),
//withAutoRefetch(this.refreshInterval, 1000000)
],
suspenseThresholdMs: 0,
suspenseTimeMs: 0,
keepValueOnRefresh: false,
keepErrorOnRefresh: false,
errorMappingFn: (error) => error.message,
}
).pipe(
collectState()
)

/**
* Case Basic Usage non flickering
*/

/**
* Case Basic Usage flaky API
*/
//case2$

/**
* Case - sourcetrigger function
*/


/**
* Case - sourcetrigger function non flickering
*/

/**
* Case - sourcetrigger function flaky api
*/

/**
* Case Bug Reproduction https://github.com/mikelgo/angular-kit/issues/111
*/

deleteAction$ = new Subject<number>()

delete$ = rxStateful$(
// id => this.http.get(`https://jsonplaceholder.typicode.com/posts/${id}`),
id => timer(1000).pipe(
switchMap(() => of(null))
),
{
suspenseTimeMs: 0,
suspenseThresholdMs: 0,
sourceTriggerConfig: {
operator: 'switch',
trigger: this.deleteAction$
}
}
).pipe(
collectState()
)

/**
* Case Normal for Bug repro
*/
refresh$ = new Subject<null>()
two$ = rxStateful$(
timer(1000).pipe(
switchMap(() => of(null))
),
{
refetchStrategies: [withRefetchOnTrigger(this.refresh$)]
}
).pipe(
collectState()
)
}


function collectState(): OperatorFunction<RxStateful<any>, {
index: number;
value: RxStateful<any>
}[]>{
return scan<RxStateful<any>, {
index: number;
value: RxStateful<any>
}[]>((acc, value, index) => {
// @ts-ignore
acc.push({ index, value });

return acc;
}, [])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {Component, inject} from '@angular/core';
import { CommonModule } from '@angular/common';
import {HttpClient} from "@angular/common/http";
import {ActivatedRoute} from "@angular/router";
import {BehaviorSubject, concatAll, delay, map, scan, Subject, switchMap, tap, toArray} from "rxjs";
import {provideRxStatefulClient, RxStatefulClient, withConfig} from "@angular-kit/rx-stateful/experimental";
import {rxStateful$, withRefetchOnTrigger} from "@angular-kit/rx-stateful";

@Component({
selector: 'demo-non-flicker',
standalone: true,
imports: [CommonModule],
template: `
<h1>DemoRxStatefulComponent</h1>
<!-- <div>-->
<!-- <button (click)="refresh$$.next(null)">refresh</button>-->
<!-- </div>-->
<!-- <div>-->
<!-- <h4>State</h4>-->
<!-- <div *ngIf="state$ | async as state">-->
<!-- <div *ngIf="state.value">{{ state.value | json }}</div>-->
<!-- <div *ngIf="state.isSuspense">loading</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div>-->
<!-- <h4>State Accumulated</h4>-->
<!-- <ul *ngFor="let v of stateAccumulated$ | async">-->
<!-- <li>{{ v | json }}</li>-->
<!-- </ul>-->
<!-- </div>-->
<!-- <div>-->
<!-- <h4>Query Params</h4>-->
<!-- <div>{{ query$ | async | json }}</div>-->
<!-- <div>{{ value$ | async | json }}</div>-->
<!-- </div>-->

<!-- <br>-->
<div>
<button mat-button color="primary" (click)="page$$.next(-1)"> previous page </button>
<button mat-button color="primary" (click)="page$$.next(1)"> next page </button>
<button mat-button color="primary" (click)="refresh$$.next(null)"> Refresh current page </button>
<div>
<h4>State Accumulated</h4>
<ul *ngFor="let v of state2Accumulated$ | async">
<li>{{ v | json }}</li>
</ul>
</div>
</div>
`,
styles: `
:host {
display: block;
}
`,
providers: [
provideRxStatefulClient(
withConfig({ keepValueOnRefresh: false, errorMappingFn: (e) => e})
),
// provideRxStatefulConfig({keepValueOnRefresh: true, errorMappingFn: (e) => e})
],
})
export class NonFlickerComponent {
private http = inject(HttpClient);
private route = inject(ActivatedRoute);
refresh$$ = new Subject<any>();

client = inject(RxStatefulClient);

query$ = this.route.params;

value$ = this.query$.pipe(switchMap(() => this.client.request(this.fetch()).pipe(
map(v => v.value)
)));

// instance = this.client.request(this.fetch(), {
// keepValueOnRefresh: false,
// keepErrorOnRefresh: false,
// refreshTrigger$: this.refresh$$,
// refetchStrategies: [withAutoRefetch(10000, 20000)],
// });
// state$ = this.instance;
// stateAccumulated$ = this.state$.pipe(
// tap(console.log),
// scan((acc, value, index) => {
// @ts-ignore
// acc.push({ index, value });
//
// return acc;
// }, [])
// );


state$ = rxStateful$(this.fetch(450), {
keepValueOnRefresh: false,
keepErrorOnRefresh: false,
refreshTrigger$: this.refresh$$,
suspenseTimeMs: 3000,
suspenseThresholdMs: 500
});

stateAccumulated$ = this.state$.pipe(
tap(x => console.log({state: x})),
scan((acc, value, index) => {
// @ts-ignore
acc.push({ index, value });

return acc;
}, [])
);
readonly page$$ = new BehaviorSubject(0)
readonly page$ = this.page$$.pipe(
scan((acc, curr) => acc + curr, 0)
)

state2$ = rxStateful$(
(page) => this.fetchPage({
page,
delayInMs: 5000
}).pipe(

),
{
suspenseThresholdMs: 500,
suspenseTimeMs: 2000,
sourceTriggerConfig: {
trigger: this.page$
},
refetchStrategies: withRefetchOnTrigger(this.refresh$$)
}
)
state2Accumulated$ = this.state2$.pipe(
tap(x => console.log({state: x})),
scan((acc, value, index) => {
// @ts-ignore
acc.push({ index, value });

return acc;
}, [])
);

fetch(delayInMs = 800) {
return this.http.get<any>('https://jsonplaceholder.typicode.com/todos/1').pipe(
delay(delayInMs),
map((v) => v?.title),
// tap(console.log)
);
}

fetchPage(params: {
delayInMs:number,
page: number
}) {

return this.http.get<any>(`https://jsonplaceholder.typicode.com/todos?_start=${params.page * 5}&_limit=5`).pipe(
delay(params.delayInMs),
concatAll(),
// @ts-ignore
map((v) => v?.id),
toArray()
);
}

constructor() {
this.state$.subscribe();
this.state$.subscribe();
}
}
Loading