I'm looking for a way to set the device-width for the server side render with Angular Universal so I control whether the prerendered page is in mobile or desktop layout.
I'm using the core ngExpressEngine to do the rendering (pretty much the same as the universal starter.
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
UPDATE: Gave up using jsdom
as explained before, because it executes the scripts on the rendered page, which is not intended. Possibly can be tuned with runScripts
option, still would receive performance hit. Regex replace on rendered strings is much faster and safer. Below sample updated to reflect it.
Today I came across same issue. Having Angular application with Universal support enabled and @angular/flex-layout
.
When this application is rendered on browser, ObservableMedia
of @angular/flex-layout
properly reports the media for instance:
// browser side MediaChange event
{
matches: true,
mediaQuery: "(min-width: 1280px) and (max-width: 1919px)",
mqAlias: "lg",
property: "",
suffix: "Lg"
}
When same application is rendered on server:
// server side MediaChange event
{
matches: true,
mediaQuery: "all",
mqAlias: "",
property: "",
suffix: ""
}
So basically, server side does not aware by default of client's media parameters, that is understandable.
If you have some mechanism of passing client's device width (for instance via cookies, personalization API etc.), then you can use regex string replace to modify rendered document. Roughly it will look like this:jsdom
// DON'T USE JSDOM, BECAUSE IT WILL EXECUTE SCRIPTS WHICH IS NOT INTENDED
// this probably may cache generated htmls
// because they are limited by the number of media queries
/*
function updateMetaViewport(html: string, deviceWidth?: number): string {
const dom = new JSDOM(html);
const metaViewport = dom.window.document.head.querySelector<HTMLMetaElement>('meta[name="viewport"]');
// if deviceWidth is not specified use default 'device-width'
// needed for both default case, and relaxing rendered html
metaViewport.content = `width=${deviceWidth ? deviceWidth : 'device-width'}, initial-scale=1`;
return dom.serialize();
}
*/
// INSTEAD REGEX WILL BE SIMPLIER AND FASTER FOR THIS TASK
// use regex string replace to update meta viewport tag
// can be optimized further by splitting html into two pieces
// and running regex replace over first part, and then concatenate
// replaced and remaining (if rendered html is large enough)
function updateMetaViewport(html: string, deviceWidth?: number, deviceHeight?: number): string {
const width = `width=${deviceWidth ? deviceWidth : 'device-width'}`;
const height = deviceHeight ? `, height=${deviceHeight}` : '';
const content = `${width}${height}, initial-scale=1`;
const replaced = html.replace(
/<head>((?:.|\n|\r)+?)<meta name="viewport" content="(.*)">((?:.|\n|\r)+?)<\/head>/i,
`<head>$1<meta name="viewport" content="${content}">$3</head>`
);
return replaced;
}
router.get('*', (req, res) => {
// where it is provided from is out of scope of this question
const userDeviceWidth = req.userDeviceWidth;
const userDeviceHeight = req.userDeviceHeight;
// then we need to set viewport width in html
const document = updateMetaViewport(indexHtmlDocument, userDeviceWidth, userDeviceHeight);
res.render('index.html', {
bootstrap: AppServerModuleNgFactory,
providers: [provideModuleMap(LAZY_MODULE_MAP)],
url: req.url,
document,
req,
res
}, (err, html) => {
if (err) {
res.status(500).send(`Internal Server Error: ${err.name}: ${err.message}`);
} else {
// once rendered, we need to refine the view port to default
// other wise viewport looses its responsiveness
const relaxViewportDocument = updateMetaViewport(html);
res.status(200).send(relaxViewportDocument);
}
});
});
Then server side rendering in terms of @angular/flex-layout
will be according:
{
matches: true,
mediaQuery: '(min-width: 600px) and (max-width: 959px)',
mqAlias: 'sm',
suffix: 'Sm',
property: ''
}
Which is correct and more advantageous, because styles, layouts of responsive components will be exactly as client expects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With