diff --git a/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.html b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.html
new file mode 100644
index 0000000000..5f97828202
--- /dev/null
+++ b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.html
@@ -0,0 +1,37 @@
+
Static
+
+
+
+
+
+
+Async
+
+
+
+
+
+Inverse attribute ordering
+
+
+
+
+
+
+
+
+
diff --git a/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.less b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.less
new file mode 100644
index 0000000000..80f9c2e115
--- /dev/null
+++ b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.less
@@ -0,0 +1,3 @@
+.margin-right {
+ margin-right: 24px;
+}
diff --git a/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.ts b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.ts
new file mode 100644
index 0000000000..5d5d10c399
--- /dev/null
+++ b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.ts
@@ -0,0 +1,68 @@
+import { Component, ChangeDetectionStrategy } from '@angular/core';
+import { PrizmSwitcherItem } from '@prizm-ui/components';
+import { PrizmDestroyService } from '@prizm-ui/helpers';
+import { BehaviorSubject, switchMap, takeUntil, tap, timer } from 'rxjs';
+
+@Component({
+ selector: 'prizm-switcher-async-example',
+ templateUrl: './switcher-async-example.component.html',
+ styleUrls: ['./switcher-async-example.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [PrizmDestroyService],
+})
+export class SwitcherAsyncExampleComponent {
+ public selectedIndex = 1;
+ public testIndex = this.selectedIndex;
+ public readonly switchersSetA: PrizmSwitcherItem[] = [
+ {
+ title: 'Таблицы',
+ },
+ {
+ title: 'Графики',
+ },
+ {
+ title: 'Мнемосхемы',
+ disabled: true,
+ },
+ {
+ title: 'Дашборды',
+ },
+ ];
+
+ public readonly switchersSetB: PrizmSwitcherItem[] = [
+ {
+ title: 'Москва',
+ },
+ {
+ title: 'Санкт-Петербург',
+ },
+ ];
+
+ public switchers: PrizmSwitcherItem[] = this.switchersSetA;
+
+ public readonly switchers$ = timer(100).pipe(
+ switchMap(() => this.switchers$$),
+ tap(value => (this.switchers = value)),
+ takeUntil(this.destroy$)
+ );
+
+ private readonly switchers$$: BehaviorSubject = new BehaviorSubject<
+ PrizmSwitcherItem[]
+ >(this.switchersSetA);
+
+ constructor(private readonly destroy$: PrizmDestroyService) {}
+
+ public toggleIndex(): void {
+ this.selectedIndex === 1 ? (this.selectedIndex = 2) : (this.selectedIndex = 1);
+ }
+
+ public toggleSwitchers(): void {
+ this.switchers === this.switchersSetA
+ ? this.switchers$$.next(this.switchersSetB)
+ : this.switchers$$.next(this.switchersSetA);
+ }
+
+ public updateIdx(idx: number) {
+ this.selectedIndex = idx;
+ }
+}
diff --git a/apps/doc/src/app/components/switcher/switcher-example.component.html b/apps/doc/src/app/components/switcher/switcher-example.component.html
index 2143af03b4..e647dda883 100644
--- a/apps/doc/src/app/components/switcher/switcher-example.component.html
+++ b/apps/doc/src/app/components/switcher/switcher-example.component.html
@@ -7,6 +7,10 @@
+
+
+
+
diff --git a/apps/doc/src/app/components/switcher/switcher-example.component.ts b/apps/doc/src/app/components/switcher/switcher-example.component.ts
index 18f44943ec..cbece1f33c 100644
--- a/apps/doc/src/app/components/switcher/switcher-example.component.ts
+++ b/apps/doc/src/app/components/switcher/switcher-example.component.ts
@@ -39,6 +39,11 @@ export class SwitcherExampleComponent {
HTML: import('./examples/switcher-basic-example/switcher-basic-example.component.html?raw'),
};
+ public readonly exampleAsyncSwitcher: TuiDocExample = {
+ TypeScript: import('./examples/switcher-async-example/switcher-async-example.component?raw'),
+ HTML: import('./examples/switcher-async-example/switcher-async-example.component.html?raw'),
+ };
+
public readonly exampleInnerLSwitcher: TuiDocExample = {
TypeScript: import('./examples/switcher-inner-l-example/switcher-inner-l-example.component?raw'),
HTML: import('./examples/switcher-inner-l-example/switcher-inner-l-example.component.html?raw'),
diff --git a/apps/doc/src/app/components/switcher/switcher-example.module.ts b/apps/doc/src/app/components/switcher/switcher-example.module.ts
index 059f4f2b7c..6274b0e957 100644
--- a/apps/doc/src/app/components/switcher/switcher-example.module.ts
+++ b/apps/doc/src/app/components/switcher/switcher-example.module.ts
@@ -4,7 +4,7 @@ import { SwitcherExampleComponent } from './switcher-example.component';
import { prizmDocGenerateRoutes, PrizmAddonDocModule } from '@prizm-ui/doc';
import { RouterModule } from '@angular/router';
import { SwitcherBasicExampleComponent } from './examples/switcher-basic-example/switcher-basic-example.component';
-import { PrizmSwitcherModule } from '@prizm-ui/components';
+import { PrizmButtonComponent, PrizmSwitcherModule } from '@prizm-ui/components';
import { SwitcherInnerLExampleComponent } from './examples/switcher-inner-l-example/switcher-inner-l-example.component';
import { SwitcherInnerMExampleComponent } from './examples/switcher-inner-m-example/switcher-inner-m-example.component';
import { SwitcherOuterMExampleComponent } from './examples/switcher-outer-m-example/switcher-outer-m-example.component';
@@ -13,6 +13,7 @@ import { SwitcherOuterSExampleComponent } from './examples/switcher-outer-s-exam
import { SwitcherWithIconExampleComponent } from './examples/switcher-with-icon-example/switcher-with-icon-example.component';
import { SwitcherOnlyIconExampleComponent } from './examples/switcher-only-icon-example/switcher-only-icon-example.component';
import { ReactiveFormsModule } from '@angular/forms';
+import { SwitcherAsyncExampleComponent } from './examples/switcher-async-example/switcher-async-example.component';
@NgModule({
declarations: [
@@ -25,6 +26,7 @@ import { ReactiveFormsModule } from '@angular/forms';
SwitcherOuterSExampleComponent,
SwitcherWithIconExampleComponent,
SwitcherOnlyIconExampleComponent,
+ SwitcherAsyncExampleComponent,
],
imports: [
CommonModule,
@@ -32,6 +34,7 @@ import { ReactiveFormsModule } from '@angular/forms';
RouterModule.forChild(prizmDocGenerateRoutes(SwitcherExampleComponent)),
PrizmSwitcherModule,
ReactiveFormsModule,
+ PrizmButtonComponent,
],
})
export class SwitcherExampleModule {}
diff --git a/libs/components/src/lib/components/switcher/switcher.component.ts b/libs/components/src/lib/components/switcher/switcher.component.ts
index 8f27b30245..5274d71f88 100644
--- a/libs/components/src/lib/components/switcher/switcher.component.ts
+++ b/libs/components/src/lib/components/switcher/switcher.component.ts
@@ -5,6 +5,7 @@ import {
EventEmitter,
HostBinding,
Input,
+ OnInit,
Optional,
Output,
Self,
@@ -13,10 +14,12 @@ import { PrizmSwitcherItem, PrizmSwitcherSize, PrizmSwitcherType } from './switc
import { prizmDefaultProp } from '@prizm-ui/core';
import { PrizmAbstractTestId } from '../../abstract/interactive';
import { ControlValueAccessor, NgControl } from '@angular/forms';
-import { noop } from 'rxjs';
+import { BehaviorSubject, combineLatestWith, filter, noop, takeUntil, tap } from 'rxjs';
import { CommonModule } from '@angular/common';
import { PrizmSwitcherItemComponent } from './components/switcher-item/switcher-item.component';
import { PrizmSwitcherHintDirective } from './directives/switcher-hint.directive';
+import { INITIAL_SWITHCER_INDEX } from './swithcer.const';
+import { PrizmDestroyService } from '@prizm-ui/helpers';
@Component({
selector: 'prizm-switcher',
@@ -25,12 +28,9 @@ import { PrizmSwitcherHintDirective } from './directives/switcher-hint.directive
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, PrizmSwitcherHintDirective, PrizmSwitcherItemComponent],
+ providers: [PrizmDestroyService],
})
-export class PrizmSwitcherComponent extends PrizmAbstractTestId implements ControlValueAccessor {
- onChange: (v: number) => void = noop;
- onTouched: () => void = noop;
- private selectedSwitcherIdx_ = 0;
-
+export class PrizmSwitcherComponent extends PrizmAbstractTestId implements ControlValueAccessor, OnInit {
@Input()
@prizmDefaultProp()
public size: PrizmSwitcherSize = 'l';
@@ -39,17 +39,26 @@ export class PrizmSwitcherComponent extends PrizmAbstractTestId implements Contr
@prizmDefaultProp()
public type: PrizmSwitcherType = 'inner';
+ private switchers$: BehaviorSubject = new BehaviorSubject([]);
+
@Input()
@prizmDefaultProp()
- public switchers: PrizmSwitcherItem[] = [];
+ public set switchers(value: PrizmSwitcherItem[]) {
+ if (value) this.switchers$.next(value);
+ }
+ get switchers(): PrizmSwitcherItem[] {
+ return this.switchers$.value;
+ }
+
+ private selectedSwitcherIdx$: BehaviorSubject = new BehaviorSubject(INITIAL_SWITHCER_INDEX);
+ public selectedSwitcherIdx_ = INITIAL_SWITHCER_INDEX;
@Input()
@prizmDefaultProp()
- public set selectedSwitcherIdx(idx: number) {
- const item = this.switchers[idx];
- if (item) this.selectSwitcher(item, idx);
+ public set selectedSwitcherIdx(value: number) {
+ this.selectedSwitcherIdx$.next(value);
}
- get selectedSwitcherIdx() {
+ get selectedSwitcherIdx(): number {
return this.selectedSwitcherIdx_;
}
@@ -62,20 +71,29 @@ export class PrizmSwitcherComponent extends PrizmAbstractTestId implements Contr
override readonly testId_ = 'ui_switcher';
+ onChange: (v: number) => void = noop;
+ onTouched: () => void = noop;
+
constructor(
public readonly cdRef: ChangeDetectorRef,
- @Optional() @Self() public readonly ngControl: NgControl
+ @Optional() @Self() public readonly ngControl: NgControl,
+ private readonly destroy$: PrizmDestroyService
) {
super();
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
+
+ ngOnInit(): void {
+ this.handleSwitchersUpdate();
+ }
+
public selectSwitcher(item: PrizmSwitcherItem, idx: number): void {
if (this.ngControl?.disabled) return;
if (item.disabled) return;
if (this.selectedSwitcherIdx === idx) return;
- this.selectedSwitcherIdxChange.emit((this.selectedSwitcherIdx_ = idx));
+ this.selectedSwitcherIdxChange.emit((this.selectedSwitcherIdx = idx));
this.onChange(this.selectedSwitcherIdx);
}
@@ -92,4 +110,40 @@ export class PrizmSwitcherComponent extends PrizmAbstractTestId implements Contr
public setDisabledState(isDisabled: boolean): void {
this.cdRef.markForCheck();
}
+
+ private handleSwitchersUpdate() {
+ this.switchers$
+ .pipe(
+ filter((switchers: PrizmSwitcherItem[]) => !!switchers.length),
+ tap(switchers => {
+ if (!this.isIndexValid(this.selectedSwitcherIdx_, switchers)) {
+ this.selectSwitcher(switchers[INITIAL_SWITHCER_INDEX], INITIAL_SWITHCER_INDEX);
+ this.logIndexValidationError(
+ `selectedSwitcherIdx out of bound. Index has been reset to ${INITIAL_SWITHCER_INDEX}`
+ );
+ }
+ }),
+ combineLatestWith(this.selectedSwitcherIdx$),
+ tap(([switchers, selectedSwitcherIdx]) => {
+ if (!this.isIndexValid(selectedSwitcherIdx, switchers)) {
+ this.logIndexValidationError('selectedSwitcherIdx out of bound');
+ return;
+ }
+
+ this.selectedSwitcherIdx_ = selectedSwitcherIdx;
+
+ this.selectSwitcher(switchers[selectedSwitcherIdx], selectedSwitcherIdx);
+ }),
+ takeUntil(this.destroy$)
+ )
+ .subscribe();
+ }
+
+ private isIndexValid(idx: number, switchers: PrizmSwitcherItem[]): boolean {
+ return !!switchers[idx];
+ }
+
+ private logIndexValidationError(errorMsg: string) {
+ console.warn(errorMsg);
+ }
}
diff --git a/libs/components/src/lib/components/switcher/swithcer.const.ts b/libs/components/src/lib/components/switcher/swithcer.const.ts
new file mode 100644
index 0000000000..aa7c41daad
--- /dev/null
+++ b/libs/components/src/lib/components/switcher/swithcer.const.ts
@@ -0,0 +1 @@
+export const INITIAL_SWITHCER_INDEX = 0;