Skip to content

Commit

Permalink
feat(cdk): add tuiTakeUntilDestroyed helper (#7381)
Browse files Browse the repository at this point in the history
Signed-off-by: waterplea <[email protected]>
  • Loading branch information
waterplea authored May 7, 2024
1 parent 84218ac commit 19b4012
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import type {OnDestroy} from '@angular/core';
import {Directive, ElementRef, HostListener, inject, Input, NgZone} from '@angular/core';
import {
DestroyRef,
Directive,
ElementRef,
HostListener,
inject,
Input,
NgZone,
} from '@angular/core';
import type {AbstractControl, Validator} from '@angular/forms';
import {NG_VALIDATORS} from '@angular/forms';
import {tuiZonefree} from '@taiga-ui/cdk/observables';
import {tuiTakeUntilDestroyed, tuiZonefree} from '@taiga-ui/cdk/observables';
import {tuiProvide} from '@taiga-ui/cdk/utils';
import {ReplaySubject, takeUntil, timer} from 'rxjs';
import {timer} from 'rxjs';

@Directive({
standalone: true,
selector: '[tuiNativeValidator]',
providers: [tuiProvide(NG_VALIDATORS, TuiNativeValidatorDirective, true)],
})
export class TuiNativeValidatorDirective implements Validator, OnDestroy {
export class TuiNativeValidatorDirective implements Validator {
private readonly destroyRef = inject(DestroyRef);
private readonly zone = inject(NgZone);
private readonly host: HTMLInputElement = inject(ElementRef).nativeElement;
private control?: AbstractControl;
private readonly destroy$ = new ReplaySubject<void>(1);

@Input()
public tuiNativeValidator = 'Invalid';
Expand All @@ -24,23 +31,12 @@ export class TuiNativeValidatorDirective implements Validator, OnDestroy {
this.control = control;

timer(0)
.pipe(
tuiZonefree(this.zone),
// NOTE: takeUntilDestroyed and DestroyRef doesn't work,
// NG0911: View has already been destroyed
// https://github.com/angular/angular/issues/54527
takeUntil(this.destroy$),
)
.pipe(tuiZonefree(this.zone), tuiTakeUntilDestroyed(this.destroyRef))
.subscribe(() => this.handleValidation());

return null;
}

public ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}

@HostListener('blur')
protected handleValidation(): void {
this.el.setCustomValidity?.(
Expand Down
1 change: 1 addition & 0 deletions projects/cdk/observables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './pressed-observable';
export * from './prevent-default';
export * from './scroll-from';
export * from './stop-propagation';
export * from './take-until-destroyed';
export * from './typed-from-event';
export * from './watch';
export * from './zone-free';
22 changes: 22 additions & 0 deletions projects/cdk/observables/take-until-destroyed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type {DestroyRef} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import type {MonoTypeOperatorFunction} from 'rxjs';
import {catchError, defaultIfEmpty, EMPTY, NEVER, pipe, takeUntil} from 'rxjs';

// NOTE: takeUntilDestroyed and DestroyRef can cause error:
// NG0911: View has already been destroyed
// https://github.com/angular/angular/issues/54527
export function tuiTakeUntilDestroyed<T>(
destroyRef?: DestroyRef,
): MonoTypeOperatorFunction<T> {
return pipe(
takeUntil(
NEVER.pipe(
// eslint-disable-next-line rxjs/no-unsafe-takeuntil
takeUntilDestroyed(destroyRef),
catchError(() => EMPTY),
defaultIfEmpty(null),
),
),
);
}
4 changes: 2 additions & 2 deletions projects/core/services/position.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export class TuiPositionService extends Observable<TuiPoint> {

constructor() {
const animationFrame$ = inject(ANIMATION_FRAME);
const destroy$ = inject(NgZone);
const zone = inject(NgZone);

super(subscriber =>
animationFrame$
.pipe(
map(() => this.el.getBoundingClientRect()),
map(rect => this.accessor.getPosition(rect)),
tuiZonefree(destroy$),
tuiZonefree(zone),
finalize(() => this.accessor.getPosition(EMPTY_CLIENT_RECT)),
)
.subscribe(subscriber),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<tui-doc-demo>
<button
tuiButton
[tuiAppearance]="appearance"
[appearance]="appearance"
[tuiAppearanceFocus]="focus"
[tuiAppearanceState]="state"
>
Expand All @@ -53,7 +53,13 @@
[documentationPropertyValues]="appearances"
[(documentationPropertyValue)]="appearance"
>
Appearance
Appearance (use
<code>appearance</code>
selector instead of
<code>tuiAppearance</code>
when used on a component that has such input due to
<code>hostDirectives</code>
)
</ng-template>
<ng-template
documentationPropertyMode="input"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {TuiDocExample} from '@taiga-ui/addon-doc';
import {TuiTableBarsService} from '@taiga-ui/addon-tablebars';
import type {TuiBrightness} from '@taiga-ui/core';
import type {PolymorpheusContent} from '@tinkoff/ng-polymorpheus';
import {Subject, Subscription, takeUntil} from 'rxjs';
import {Subscription} from 'rxjs';

@Component({
selector: 'example-tui-table-bar',
Expand All @@ -16,8 +16,6 @@ import {Subject, Subscription, takeUntil} from 'rxjs';
export class ExampleTuiTableBarComponent implements OnDestroy {
private readonly tableBarsService = inject(TuiTableBarsService);

private readonly destroy$ = new Subject<void>();

@ViewChild('tableBarTemplate')
protected readonly tableBarTemplate: PolymorpheusContent;

Expand Down Expand Up @@ -50,24 +48,21 @@ export class ExampleTuiTableBarComponent implements OnDestroy {
protected subscription = new Subscription();

public ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
this.subscription.unsubscribe();
}

protected showTableBar(): void {
this.subscription.unsubscribe();

this.subscription = this.tableBarsService
.open(this.tableBarTemplate || '', {
adaptive: this.adaptive,
mode: this.mode,
hasCloseButton: this.hasCloseButton,
})
.pipe(takeUntil(this.destroy$))
.subscribe();
}

protected destroy(): void {
this.destroy$.next();
this.subscription.unsubscribe();
}
}

0 comments on commit 19b4012

Please sign in to comment.