Skip to content

Commit

Permalink
fix(primeng/p-tabview): show the `forward' button when necessary
Browse files Browse the repository at this point in the history
Fixes #11684.
  • Loading branch information
volvachev committed Sep 23, 2023
1 parent 396297a commit b709ac3
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 14 deletions.
72 changes: 58 additions & 14 deletions src/app/components/tabview/tabview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { CommonModule, isPlatformBrowser, DOCUMENT } from '@angular/common';
import {
AfterContentInit,
AfterViewChecked,
Expand All @@ -22,7 +22,9 @@ import {
ViewContainerRef,
ViewEncapsulation,
forwardRef,
signal
AfterViewInit,
NgZone,
Self
} from '@angular/core';
import { BlockableUI, PrimeTemplate, SharedModule } from 'primeng/api';
import { DomHandler } from 'primeng/dom';
Expand All @@ -31,7 +33,10 @@ import { ChevronRightIcon } from 'primeng/icons/chevronright';
import { TimesIcon } from 'primeng/icons/times';
import { RippleModule } from 'primeng/ripple';
import { TooltipModule } from 'primeng/tooltip';
import { Subscription } from 'rxjs';
import { filter, fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { OnDestroyService } from '../utils/on-destroy.service';
import { outsideZone } from '../utils/outside-zone-operator';
import { TabViewChangeEvent, TabViewCloseEvent } from './tabview.interface';
import { UniqueComponentId } from 'primeng/utils';
import { Nullable } from 'primeng/ts-helpers';
Expand Down Expand Up @@ -328,9 +333,10 @@ export class TabPanel implements AfterContentInit, OnDestroy {
styleUrls: ['./tabview.css'],
host: {
class: 'p-element'
}
},
providers: [OnDestroyService]
})
export class TabView implements AfterContentInit, AfterViewChecked, OnDestroy, BlockableUI {
export class TabView implements AfterContentInit, AfterViewInit, AfterViewChecked, OnDestroy, BlockableUI {
/**
* Inline style of the component.
* @group Props
Expand Down Expand Up @@ -432,8 +438,6 @@ export class TabView implements AfterContentInit, AfterViewChecked, OnDestroy, B

forwardIsDisabled: boolean = false;

private tabChangesSubscription!: Subscription;

nextIconTemplate: TemplateRef<any> | undefined;

previousIconTemplate: TemplateRef<any> | undefined;
Expand All @@ -444,12 +448,20 @@ export class TabView implements AfterContentInit, AfterViewChecked, OnDestroy, B

@ViewChild('elementToObserve') elementToObserve: ElementRef;

constructor(@Inject(PLATFORM_ID) private platformId: any, public el: ElementRef, public cd: ChangeDetectorRef, private renderer: Renderer2) {}
constructor(
@Inject(PLATFORM_ID) private platformId: any,
public el: ElementRef,
public cd: ChangeDetectorRef,
private renderer: Renderer2,
@Self() private destroy$: OnDestroyService,
@Inject(DOCUMENT) private documentRef: Document,
private zone: NgZone
) {}

ngAfterContentInit() {
this.initTabs();

this.tabChangesSubscription = (this.tabPanels as QueryList<TabPanel>).changes.subscribe((_) => {
(this.tabPanels as QueryList<TabPanel>).changes.pipe(takeUntil(this.destroy$)).subscribe((_) => {
this.initTabs();
});

Expand All @@ -466,10 +478,13 @@ export class TabView implements AfterContentInit, AfterViewChecked, OnDestroy, B
});
}

ngAfterViewInit() {
ngAfterViewInit(): void {
if (isPlatformBrowser(this.platformId)) {
this.bindResizeObserver();
}

this.initButtonState();
this.listenWindowResize();
}

bindResizeObserver() {
Expand Down Expand Up @@ -502,10 +517,6 @@ export class TabView implements AfterContentInit, AfterViewChecked, OnDestroy, B
}

ngOnDestroy(): void {
if (this.tabChangesSubscription) {
this.tabChangesSubscription.unsubscribe();
}

if (this.resizeObserver) {
this.unbindResizeObserver();
}
Expand Down Expand Up @@ -784,6 +795,39 @@ export class TabView implements AfterContentInit, AfterViewChecked, OnDestroy, B

content.scrollLeft = pos >= lastPos ? lastPos : pos;
}

private initButtonState(): void {
if (this.scrollable) {
// We have to wait for the rendering and then retrieve the actual size element from the DOM.
// in future `Promise.resolve` can be changed to `queueMicrotask` (if ie11 support will be dropped)
Promise.resolve().then(() => {
this.updateButtonState();
this.cd.markForCheck();
});
}
}

private listenWindowResize(): void {
if (!isPlatformBrowser(this.platformId)) {
return;
}

fromEvent(this.documentRef.defaultView, 'resize', { passive: true })
.pipe(
outsideZone(this.zone),
filter(() => this.scrollable),
takeUntil(this.destroy$)
)
.subscribe(() => {
const prevBackwardIsDisabled = this.backwardIsDisabled;
const prevForwardIsDisabled = this.forwardIsDisabled;
this.updateButtonState();

if (this.forwardIsDisabled !== prevForwardIsDisabled || this.backwardIsDisabled !== prevBackwardIsDisabled) {
this.cd.detectChanges();
}
});
}
}

@NgModule({
Expand Down
33 changes: 33 additions & 0 deletions src/app/components/utils/on-destroy.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';

/**
* Observable abstraction over ngOnDestroy to use with takeUntil
* @example
*@Component({
* selector: 'app-example',
* templateUrl: './example.component.html',
* styleUrls: [ './example.component.scss' ],
* providers: [ OnDestroyService ] <=== add to the element providers section
*})
*export class ExampleComponent implements OnInit, OnDestroy {
* private stream$ = interval(200);
*
* constructor(@Self() private readonly destroy$: OnDestroyService) { <=== Inject, using @Self()
* }
*
* public ngOnInit(): void {
* this.stream$.pipe(
* map((i: number) => i * 2),
* takeUntil(this.destroy$) <=== Use in takeUntil() operator
* ).subscribe();
* }
*}
*/
@Injectable()
export class OnDestroyService extends Subject<null> implements OnDestroy {
public ngOnDestroy(): void {
this.next(null);
this.complete();
}
}
21 changes: 21 additions & 0 deletions src/app/components/utils/outside-zone-operator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NgZone } from '@angular/core';
import { Observable } from 'rxjs';

/**
* RxJs operator, that run subscription function outside NgZone (This operator should be first in the pipe() method
*
* @param {NgZone} zone - injected ngZone reference
*
* @returns {Observable<T>} Input Observable
*
* @example
* fromEvent<MouseEvent>(this.elementRef.nativeElement, 'click')
* .pipe(
* outsideZone(this.zone),
* ...other operators,
* takeUntil(this.destroy$)
* )
*/
export function outsideZone<T>(zone: NgZone): (source: Observable<T>) => Observable<T> {
return (source) => new Observable((subscriber) => zone.runOutsideAngular(() => source.subscribe(subscriber)));
}

0 comments on commit b709ac3

Please sign in to comment.