Skip to content

Instantly share code, notes, and snippets.

@renatoaraujoc
Created September 27, 2024 19:36
Show Gist options
  • Save renatoaraujoc/a8a694110414600bd599a0cd94609094 to your computer and use it in GitHub Desktop.
Save renatoaraujoc/a8a694110414600bd599a0cd94609094 to your computer and use it in GitHub Desktop.
screen-size-observer.directive.ts
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