From b0b04a1d1e2fea54e7c48ee2ab3b918313552f93 Mon Sep 17 00:00:00 2001 From: saithis <1547453+saithis@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:19:23 +0100 Subject: [PATCH 1/2] ViewdataSource signals experiments --- .../components/view/src/view-data-source.ts | 91 +++++++++++++------ .../components/view/src/view.component.html | 10 +- .../components/view/src/view.component.scss | 3 - .../components/view/src/view.component.ts | 6 +- 4 files changed, 73 insertions(+), 37 deletions(-) diff --git a/projects/components/view/src/view-data-source.ts b/projects/components/view/src/view-data-source.ts index 9d0f7a4..e2a0597 100644 --- a/projects/components/view/src/view-data-source.ts +++ b/projects/components/view/src/view-data-source.ts @@ -1,13 +1,13 @@ -import { computed, signal, Signal } from '@angular/core'; -import { IZvException } from '@zvoove/components/core'; -import { Observable, Subscription, first } from 'rxjs'; +import { computed, Resource, ResourceStatus, signal, Signal } from '@angular/core'; +import { first, Observable, Subscription } from 'rxjs'; export interface IZvViewDataSource { readonly contentVisible: Signal; readonly contentBlocked: Signal; - readonly exception: Signal; - connect(): void; - disconnect(): void; + readonly error: Signal; + readonly errorIcon: Signal; + connect?(): void; + disconnect?(): void; } export interface ZvViewDataSourceOptions { @@ -16,42 +16,48 @@ export interface ZvViewDataSourceOptions { keepLoadStreamOpen?: boolean; } -export class ZvViewDataSource implements IZvViewDataSource { - private loading = signal(false); +export class ZvViewDataSource implements IZvViewDataSource, Resource { private blockView = signal(false); private connected = false; private params: TParams | null = null; private loadingSub = Subscription.EMPTY; - private connectSub = Subscription.EMPTY; + private loadtriggerSub = Subscription.EMPTY; constructor(private options: ZvViewDataSourceOptions) {} - public result = signal(null); - public exception = signal(null); + public status = signal(ResourceStatus.Idle); + public value = signal(null!); + public error = signal(null); + public errorIcon = signal('sentiment_very_dissatisfied'); public contentVisible = signal(false); - public contentBlocked = computed(() => this.loading() || this.blockView()); + public contentBlocked = computed(() => this.isLoading() || this.blockView()); + public readonly isLoading = computed(() => this.status() === ResourceStatus.Loading || this.status() === ResourceStatus.Reloading); + public hasValue(): this is Resource> { + return this.value() !== undefined; + } public connect() { if (this.connected) { throw new Error('ViewDataSource is already connected.'); } - this.connectSub = this.options.loadTrigger$.subscribe((params) => { + this.loadtriggerSub = this.options.loadTrigger$.subscribe((params) => { this.connected = true; this.params = params; this.loadData(params); }); } - public updateData() { + public reload() { if (!this.connected) { throw new Error('ViewDataSource is not connected.'); } this.loadData(this.params!); + return true; } public disconnect(): void { - this.connectSub.unsubscribe(); + this.loadtriggerSub.unsubscribe(); this.loadingSub.unsubscribe(); } @@ -61,9 +67,9 @@ export class ZvViewDataSource implements IZvViewDataSource { private loadData(params: TParams) { this.loadingSub.unsubscribe(); - this.loading.set(true); + this.status.set(ResourceStatus.Loading); this.contentVisible.set(true); - this.exception.set(null); + this.error.set(null); let load$ = this.options.loadFn(params); if (!this.options.keepLoadStreamOpen) { @@ -71,19 +77,52 @@ export class ZvViewDataSource implements IZvViewDataSource { } this.loadingSub = load$.subscribe({ next: (result) => { - this.loading.set(false); - this.result.set(result); + this.status.set(ResourceStatus.Resolved); + this.value.set(result); }, error: (err) => { - this.loading.set(false); - this.result.set(null); + this.status.set(ResourceStatus.Error); + this.value.set(undefined!); this.contentVisible.set(false); - this.exception.set({ - errorObject: err, - alignCenter: true, - icon: 'sentiment_very_dissatisfied', - }); + this.error.set(err); }, }); } } + +export interface SignalZvViewDataSourceOptions { + resource: Resource; +} + +export class SignalZvViewDataSource implements IZvViewDataSource, Resource { + public readonly resource: Resource; + public readonly contentVisible = computed(() => this.status() == ResourceStatus.Error); + public readonly contentBlocked = computed(() => this.isLoading() || this.blockView()); + public readonly errorIcon = signal('sentiment_very_dissatisfied'); + + public readonly value: Signal; + public readonly status: Signal; + public readonly error: Signal; + public readonly isLoading: Signal; + public hasValue(): this is Resource> { + return this.resource.hasValue(); + } + + private blockView = signal(false); + + constructor(options: SignalZvViewDataSourceOptions) { + this.resource = options.resource; + this.value = this.resource.value.bind(this.resource); + this.status = this.resource.status.bind(this.resource); + this.error = this.resource.error.bind(this.resource); + this.isLoading = this.resource.isLoading.bind(this.resource); + } + + public reload() { + return this.resource.reload(); + } + + public setViewBlocked(value: boolean) { + this.blockView.set(value); + } +} diff --git a/projects/components/view/src/view.component.html b/projects/components/view/src/view.component.html index 48be5f3..486ca20 100644 --- a/projects/components/view/src/view.component.html +++ b/projects/components/view/src/view.component.html @@ -6,12 +6,12 @@ } - @if (dataSource.exception()) { - - @if (dataSource.exception()?.icon) { - {{ dataSource.exception()?.icon }} + @if (dataSource.error()) { + + @if (dataSource.errorIcon()) { + {{ dataSource.errorIcon() }} } - {{ dataSource.exception()?.errorObject | zvErrorMessage }} + {{ dataSource.error() | zvErrorMessage }} } diff --git a/projects/components/view/src/view.component.scss b/projects/components/view/src/view.component.scss index 1e865b5..05782f9 100644 --- a/projects/components/view/src/view.component.scss +++ b/projects/components/view/src/view.component.scss @@ -9,9 +9,6 @@ mat-card.zv-view__error-container { color: var(--zv-components-error); -} - -mat-card.zv-view__error-container--center { display: grid; justify-items: center; } diff --git a/projects/components/view/src/view.component.ts b/projects/components/view/src/view.component.ts index 5eb28c9..687b318 100644 --- a/projects/components/view/src/view.component.ts +++ b/projects/components/view/src/view.component.ts @@ -16,7 +16,7 @@ import { IZvViewDataSource } from './view-data-source'; export class ZvView implements OnDestroy { @Input({ required: true }) public set dataSource(value: IZvViewDataSource) { if (this._dataSource) { - this._dataSource.disconnect(); + this._dataSource.disconnect?.(); } this._dataSource = value; @@ -32,7 +32,7 @@ export class ZvView implements OnDestroy { public ngOnDestroy() { if (this._dataSource) { - this._dataSource.disconnect(); + this._dataSource.disconnect?.(); } } @@ -40,6 +40,6 @@ export class ZvView implements OnDestroy { if (!this._dataSource) { return; } - this._dataSource.connect(); + this._dataSource.connect?.(); } } From c1d55824788417ab1ef6c8567444e2b33eaf1009 Mon Sep 17 00:00:00 2001 From: saithis <1547453+saithis@users.noreply.github.com> Date: Fri, 7 Mar 2025 19:14:05 +0100 Subject: [PATCH 2/2] BC --- .../components/view/src/view-data-source.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/projects/components/view/src/view-data-source.ts b/projects/components/view/src/view-data-source.ts index e2a0597..aa6e277 100644 --- a/projects/components/view/src/view-data-source.ts +++ b/projects/components/view/src/view-data-source.ts @@ -1,5 +1,6 @@ import { computed, Resource, ResourceStatus, signal, Signal } from '@angular/core'; -import { first, Observable, Subscription } from 'rxjs'; +import { IZvException } from '@zvoove/components/core'; +import { first, Observable, of, Subscription } from 'rxjs'; export interface IZvViewDataSource { readonly contentVisible: Signal; @@ -10,13 +11,13 @@ export interface IZvViewDataSource { disconnect?(): void; } -export interface ZvViewDataSourceOptions { - loadTrigger$: Observable; +export interface ZvViewDataSourceOptions { + loadTrigger$?: Observable; loadFn: (params: TParams) => Observable; keepLoadStreamOpen?: boolean; } -export class ZvViewDataSource implements IZvViewDataSource, Resource { +export class ZvViewDataSource implements IZvViewDataSource, Resource { private blockView = signal(false); private connected = false; @@ -24,7 +25,7 @@ export class ZvViewDataSource implements IZvViewDataSource, Reso private loadingSub = Subscription.EMPTY; private loadtriggerSub = Subscription.EMPTY; - constructor(private options: ZvViewDataSourceOptions) {} + constructor(private options: ZvViewDataSourceOptions) {} public status = signal(ResourceStatus.Idle); public value = signal(null!); @@ -37,11 +38,22 @@ export class ZvViewDataSource implements IZvViewDataSource, Reso return this.value() !== undefined; } + /** @deprecated Use value() */ + public result = computed(() => this.value()); + + /** @deprecated Use error() */ + public exception = computed(() => (this.error() ? { errorObject: this.error(), icon: this.errorIcon() } : null)); + + /** @deprecated Use reload() */ + public updateData() { + this.reload(); + } + public connect() { if (this.connected) { throw new Error('ViewDataSource is already connected.'); } - this.loadtriggerSub = this.options.loadTrigger$.subscribe((params) => { + this.loadtriggerSub = (this.options.loadTrigger$ ?? of(null!)).subscribe((params) => { this.connected = true; this.params = params; this.loadData(params); @@ -90,10 +102,6 @@ export class ZvViewDataSource implements IZvViewDataSource, Reso } } -export interface SignalZvViewDataSourceOptions { - resource: Resource; -} - export class SignalZvViewDataSource implements IZvViewDataSource, Resource { public readonly resource: Resource; public readonly contentVisible = computed(() => this.status() == ResourceStatus.Error); @@ -110,7 +118,7 @@ export class SignalZvViewDataSource implements IZvViewDataSource, Resourc private blockView = signal(false); - constructor(options: SignalZvViewDataSourceOptions) { + constructor(options: { resource: Resource }) { this.resource = options.resource; this.value = this.resource.value.bind(this.resource); this.status = this.resource.status.bind(this.resource);