Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit 1c136bc

Browse files
CaerusKaruThomasBurleson
authored andcommitted
feat(server): add ability to specify breakpoints for MediaObserver (#999)
On the server, there is no conception of "browser window," because Angular's browser implementation doesn't implement it. This means MatchMedia breakpoint activations will not work on the server. Layout SSR will generate CSS Stylesheets for all known breakpoints. And for the static, delivered-first HTML pages the directives are replaced with their associated, generated CSS classnames. But for programmatic usages of `MediaObserver`, we don't have a fallback mechanism to *announce* breakpoint activations. This PR allows a user to specify which activations they want to trigger via the MediaObserver for programmatic view listeners. This can be done using the following configuration: ```ts FlexLayoutModule.withConfig({serverBreakpoints: ['xs', 'lt-md']}) ``` Fixes #991 .
1 parent 66e7463 commit 1c136bc

File tree

2 files changed

+37
-13
lines changed

2 files changed

+37
-13
lines changed

src/lib/core/tokens/library-config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface LayoutConfigOptions {
1717
useColumnBasisZero?: boolean;
1818
printWithBreakpoints?: string[];
1919
mediaTriggerAutoRestore?: boolean;
20+
ssrObserveBreakpoints?: string[];
2021
}
2122

2223
export const DEFAULT_CONFIG: LayoutConfigOptions = {
@@ -27,7 +28,8 @@ export const DEFAULT_CONFIG: LayoutConfigOptions = {
2728
serverLoaded: false,
2829
useColumnBasisZero: true,
2930
printWithBreakpoints: [],
30-
mediaTriggerAutoRestore: true
31+
mediaTriggerAutoRestore: true,
32+
ssrObserveBreakpoints: [],
3133
};
3234

3335
export const LAYOUT_CONFIG = new InjectionToken<LayoutConfigOptions>(

src/lib/server/server-provider.ts

+34-12
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import {
1515
BreakPoint,
1616
ɵMatchMedia as MatchMedia,
1717
StylesheetMap,
18-
sortAscendingPriority
18+
sortAscendingPriority,
19+
LayoutConfigOptions,
20+
LAYOUT_CONFIG,
1921
} from '@angular/flex-layout/core';
2022

2123
import {ServerMatchMedia} from './server-match-media';
@@ -27,10 +29,12 @@ import {ServerMatchMedia} from './server-match-media';
2729
* element
2830
* @param mediaController the MatchMedia service to activate/deactivate breakpoints
2931
* @param breakpoints the registered breakpoints to activate/deactivate
32+
* @param layoutConfig the library config, and specifically the breakpoints to activate
3033
*/
3134
export function generateStaticFlexLayoutStyles(serverSheet: StylesheetMap,
32-
mediaController: MatchMedia,
33-
breakpoints: BreakPoint[]) {
35+
mediaController: ServerMatchMedia,
36+
breakpoints: BreakPoint[],
37+
layoutConfig: LayoutConfigOptions) {
3438
// Store the custom classes in the following map, that way only
3539
// one class gets allocated per HTMLElement, and each class can
3640
// be referenced in the static media queries
@@ -43,14 +47,29 @@ export function generateStaticFlexLayoutStyles(serverSheet: StylesheetMap,
4347

4448
[...breakpoints].sort(sortAscendingPriority).forEach((bp, i) => {
4549
serverSheet.clearStyles();
46-
(mediaController as ServerMatchMedia).activateBreakpoint(bp);
50+
mediaController.activateBreakpoint(bp);
4751
const stylesheet = new Map(serverSheet.stylesheet);
4852
if (stylesheet.size > 0) {
4953
styleText += generateCss(stylesheet, bp.mediaQuery, classMap);
5054
}
51-
(mediaController as ServerMatchMedia).deactivateBreakpoint(breakpoints[i]);
55+
mediaController.deactivateBreakpoint(breakpoints[i]);
5256
});
5357

58+
const serverBps = layoutConfig.ssrObserveBreakpoints;
59+
if (serverBps) {
60+
serverBps
61+
.reduce((acc: BreakPoint[], serverBp: string) => {
62+
const foundBp = breakpoints.find(bp => serverBp === bp.alias);
63+
if (!foundBp) {
64+
console.warn(`FlexLayoutServerModule: unknown breakpoint alias "${serverBp}"`);
65+
} else {
66+
acc.push(foundBp);
67+
}
68+
return acc;
69+
}, [])
70+
.forEach(mediaController.activateBreakpoint);
71+
}
72+
5473
return styleText;
5574
}
5675

@@ -59,14 +78,16 @@ export function generateStaticFlexLayoutStyles(serverSheet: StylesheetMap,
5978
* components and attach it to the head of the DOM
6079
*/
6180
export function FLEX_SSR_SERIALIZER_FACTORY(serverSheet: StylesheetMap,
62-
matchMedia: MatchMedia,
81+
mediaController: ServerMatchMedia,
6382
_document: Document,
64-
breakpoints: BreakPoint[]) {
83+
breakpoints: BreakPoint[],
84+
layoutConfig: LayoutConfigOptions) {
6585
return () => {
6686
// This is the style tag that gets inserted into the head of the DOM,
6787
// populated with the manual media queries
6888
const styleTag = _document.createElement('style');
69-
const styleText = generateStaticFlexLayoutStyles(serverSheet, matchMedia, breakpoints);
89+
const styleText = generateStaticFlexLayoutStyles(serverSheet, mediaController, breakpoints,
90+
layoutConfig);
7091
styleTag.classList.add(`${CLASS_NAME}ssr`);
7192
styleTag.textContent = styleText;
7293
_document.head!.appendChild(styleTag);
@@ -85,6 +106,7 @@ export const SERVER_PROVIDERS = [
85106
MatchMedia,
86107
DOCUMENT,
87108
BREAKPOINTS,
109+
LAYOUT_CONFIG,
88110
],
89111
multi: true
90112
},
@@ -117,7 +139,8 @@ export type ClassMap = Map<HTMLElement, string>;
117139
function generateCss(stylesheet: StyleSheet, mediaQuery: string, classMap: ClassMap) {
118140
let css = '';
119141
stylesheet.forEach((styles, el) => {
120-
let keyVals = '', className = getClassName(el, classMap);
142+
let keyVals = '';
143+
let className = getClassName(el, classMap);
121144

122145
styles.forEach((v, k) => {
123146
keyVals += v ? format(`${k}:${v};`) : '';
@@ -138,13 +161,13 @@ function generateCss(stylesheet: StyleSheet, mediaQuery: string, classMap: Class
138161
function format(...list: string[]): string {
139162
let result = '';
140163
list.forEach((css, i) => {
141-
result += IS_DEBUG_MODE ? formatSegment(css, i != 0) : css;
164+
result += IS_DEBUG_MODE ? formatSegment(css, i !== 0) : css;
142165
});
143166
return result;
144167
}
145168

146169
function formatSegment(css: string, asPrefix: boolean = true): string {
147-
return asPrefix ? '\n' + css : css + '\n';
170+
return asPrefix ? `\n${css}` : `${css}\n`;
148171
}
149172

150173
/**
@@ -162,4 +185,3 @@ function getClassName(element: HTMLElement, classMap: Map<HTMLElement, string>)
162185

163186
return className;
164187
}
165-

0 commit comments

Comments
 (0)