Created
September 27, 2024 19:36
-
-
Save renatoaraujoc/a8a694110414600bd599a0cd94609094 to your computer and use it in GitHub Desktop.
screen-size-observer.directive.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { injectScreenSizeObserver } from './screen-size-observer.service'; | |
import { | |
type AfterViewInit, | |
ChangeDetectorRef, | |
Directive, | |
type EmbeddedViewRef, | |
inject, | |
Input, | |
type OnDestroy, | |
TemplateRef, | |
ViewContainerRef | |
} from '@angular/core'; | |
import type { Subscription } from 'rxjs'; | |
import { Subject, switchMap, takeUntil } from 'rxjs'; | |
type InternalScreenSize = 'mobile' | 'tablet' | 'desktop'; | |
interface ScreenSizeObserverContext { | |
$implicit: InternalScreenSize; | |
} | |
@Directive({ | |
// eslint-disable-next-line @angular-eslint/directive-selector | |
selector: '[showIfScreenSizeIs]', | |
standalone: true | |
}) | |
export class ScreenSizeObserverDirective implements AfterViewInit, OnDestroy { | |
private readonly _screenSizeObserver = injectScreenSizeObserver(); | |
private readonly _onDestroy$ = new Subject<null>(); | |
private readonly _onAfterViewInit$ = new Subject<null>(); | |
private readonly _viewContainerRef = inject(ViewContainerRef); | |
private readonly _thenTemplateRef = | |
inject<TemplateRef<ScreenSizeObserverContext>>(TemplateRef); | |
private readonly _cdr = inject(ChangeDetectorRef); | |
private __screenSizeSubscription: Subscription | null = null; | |
private _wantedSizes: InternalScreenSize[] = []; | |
private _thenViewRef: EmbeddedViewRef<ScreenSizeObserverContext> | null = | |
null; | |
private _elseTemplate: TemplateRef<ScreenSizeObserverContext> | null = null; | |
private _elseViewRef: EmbeddedViewRef<ScreenSizeObserverContext> | null = | |
null; | |
@Input() | |
set showIfScreenSizeIs( | |
wantedScreenSizes: InternalScreenSize[] | InternalScreenSize | |
) { | |
this._wantedSizes = Array.isArray(wantedScreenSizes) | |
? wantedScreenSizes | |
: [wantedScreenSizes]; | |
// Clean thenViewRef | |
this._thenViewRef = null; | |
// If there's any view created, remove it. | |
this.cleanView(); | |
// Cancel any existing subscription | |
this.unsubscribe(); | |
// Subscribe | |
this.subscribe(); | |
} | |
@Input() | |
set showIfScreenSizeIsElse( | |
elseTemplate: TemplateRef<ScreenSizeObserverContext> | |
) { | |
this._elseTemplate = elseTemplate; | |
// Clean elseViewRef | |
this._elseViewRef = null; | |
// If there's any view created, remove it. | |
this.cleanView(); | |
// Cancel any existing subscription | |
this.unsubscribe(); | |
// Subscribe | |
this.subscribe(); | |
} | |
unsubscribe() { | |
if (this.__screenSizeSubscription) { | |
this.__screenSizeSubscription.unsubscribe(); | |
} | |
} | |
subscribe() { | |
this.__screenSizeSubscription = this._onAfterViewInit$ | |
.pipe( | |
switchMap(() => | |
this._screenSizeObserver.getValueForScreenSize<InternalScreenSize>( | |
{ | |
DEFAULT: 'mobile', | |
TABLET: 'tablet', | |
DESKTOP: 'desktop' | |
} | |
) | |
), | |
takeUntil(this._onDestroy$) | |
) | |
.subscribe((screenSize) => { | |
this.doWork(screenSize); | |
}); | |
} | |
doWork(screenSize: InternalScreenSize) { | |
// Then | |
if (this._wantedSizes.includes(screenSize)) { | |
if (this._thenViewRef) return; | |
if (this._elseViewRef) { | |
this._elseViewRef = null; | |
this._viewContainerRef.clear(); | |
} | |
this._thenViewRef = this._viewContainerRef.createEmbeddedView( | |
this._thenTemplateRef, | |
{ | |
$implicit: screenSize | |
} | |
); | |
this._cdr.detectChanges(); | |
return; | |
} | |
// Else | |
if (this._elseViewRef) return; | |
if (this._thenViewRef) { | |
this._thenViewRef = null; | |
this._viewContainerRef.clear(); | |
} | |
if (this._elseTemplate) { | |
this._elseViewRef = this._viewContainerRef.createEmbeddedView( | |
this._elseTemplate, | |
{ | |
$implicit: screenSize | |
} | |
); | |
this._cdr.detectChanges(); | |
} | |
} | |
ngAfterViewInit(): void { | |
this._onAfterViewInit$.next(null); | |
this._onAfterViewInit$.complete(); | |
} | |
ngOnDestroy(): void { | |
this._onDestroy$.next(null); | |
this._onDestroy$.complete(); | |
} | |
private cleanView() { | |
this._thenViewRef = null; | |
this._elseViewRef = null; | |
if (this._viewContainerRef.length) { | |
this._viewContainerRef.clear(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment