I'm rewriting an old directive I have to use signals instead of bloated RxJS but I'm facing an issue which I don't fully comprehend. This is the directive, which is a directly which listens for media query changes:
@Directive({
standalone: true,
})
export class SDKMediaQueriesMixin extends SDKSubscriptionsManager implements OnInit {
private readonly breakpointSignal = signal<Nullable<SDKBreakpoint>>(null);
public readonly breakpoints: LibBreakpointMap<string> = {};
public readonly breakpointNames: LibBreakpointName[] = [];
private matches$: Observable<(MediaQueryListEvent | Partial<MediaQueryListEvent>)[]> = EMPTY;
constructor(@Inject(DOCUMENT) private document: Document, private applicationManager: SDKApplicationManager) {
super();
if (this.applicationManager.isBrowser) {
this.breakpoints.mobile = getComputedStyle(this.document.documentElement).getPropertyValue('--mobile');
this.breakpoints.tablet = getComputedStyle(this.document.documentElement).getPropertyValue('--tablet');
this.breakpoints.desktop = getComputedStyle(this.document.documentElement).getPropertyValue('--desktop');
this.breakpoints.fullhd = getComputedStyle(this.document.documentElement).getPropertyValue('--fullhd');
this.breakpointNames = Object.keys(this.breakpoints) as LibBreakpointName[];
const bpValues = Object.values(this.breakpoints);
const queries = bpValues.map((bp) => `(min-width: ${bp})`);
const matches$ = queries.map((query) => {
const matchMedia = window.matchMedia(query);
return fromEventPattern<Partial<MediaQueryListEvent>>(
matchMedia.addListener.bind(matchMedia),
matchMedia.removeListener.bind(matchMedia)
).pipe(startWith({ matches: matchMedia.matches, media: matchMedia.media }));
});
this.matches$ = combineLatest(matches$);
}
}
ngOnInit() {
if (this.applicationManager.isBrowser && this.matches$) {
this.newSubscription = this.matches$.pipe(
map((events) => events.map((event) => event.matches)),
map((matches) => new SDKBreakpoint(this.breakpointNames[matches.lastIndexOf(true)]))
)
.subscribe((breakpoint) => {
console.log('breakpoint is changing', breakpoint);
this.breakpointSignal.set(breakpoint);
});
}
}
public get activeBreakpoint() {
return this.breakpointSignal();
};
}
In my AppComponent
I want to listen to changes to the active breakpoint, so I added an effect
:
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RouterModule, LibAnchorComponent],
hostDirectives: [SDKMediaQueriesMixin]
})
export class AppComponent {
constructor(private mediaQueriesMixin: SDKMediaQueriesMixin) {
effect(() => {
console.log('breakpoint changed', this.mediaQueriesMixin.activeBreakpoint);
});
}
}
This runs twice on load to log the following:
app.component.ts:21 breakpoint changed null
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'fullhd'}
When I change the size of the browser window it logs the following:
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'desktop'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'tablet'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'fullhd'}
However as you can see I do not get the logs in the effect to run. The funny thing is though, if I add an interval instead which just randomises a breakpoint then everything works as expected:
interval(1000).pipe(
map(() => new SDKBreakpoint(this.breakpointNames[Math.floor(Math.random() * this.breakpointNames.length)])),
)
.subscribe((breakpoint) => {
console.log('breakpoint is changing', breakpoint);
this.breakpointSignal.set(breakpoint);
});
With this code, I get the following logs:
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'mobile'}
app.component.ts:21 breakpoint changed SDKBreakpoint {name: 'mobile'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'fullhd'}
app.component.ts:21 breakpoint changed SDKBreakpoint {name: 'fullhd'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'tablet'}
app.component.ts:21
Since this.breakpointSignal.set(breakpoint);
runs the same in both scenarios I don't understand why it only works with an interval... What am I doing wrong?
{{this.mediaQueriesMixin.activeBreakpoint?.name}}
` to the template but it remains unchanged and no additional logs either. Same if I call the signal getter directly with `{{this.mediaQueriesMixin.breakpointSignal()?.name}}
`. – Chrillewoodz Aug 23 '23 at 12:56