Flex-layout: Universal pre-rendered styles are not correct

Created on 9 Aug 2017  路  47Comments  路  Source: angular/flex-layout

With last nightly build, there are no more blocking errors with Flex Layout in Universal, but the pre-rendered styles are not correct.

Repro :

  • git clone https://github.com/cyrilletuzi/nguniversal-expressengine-httpinterceptor.git
  • git checkout -b flexlayoutissue
  • npm install
  • npm start
  • go to localhost:3000 : rendered correctly with JavaScript (vertical in small screens, horizontal in desktop screens with 3 stretched items by row)
  • disable JavaScript to test what the server is pre-rendering and refresh : not rendered correctly (horizontal with the 4 items on the same line, on all screen sizes)

The only flex relative code is in src/app/app.component (the other files are just Universal boilerplate, and innerHTML is just to quickly test different heights) :

<div fxLayout.xs="column" fxLayoutWrap.xs="nowrap">
  <div class="test" fxFlex.gt-xs="calc(100% / 3)" *ngFor="let item of list" [innerHTML]="item"></div>
</div>

Javascript-Enabled:

Mobile Viewport

screen shot 2017-08-09 at 10 53 16 am

Desktop Viewport

screen shot 2017-08-09 at 10 24 07 am


Javscript-Disabled (prerender version only):

Mobile Viewport

screen shot 2017-08-09 at 10 53 01 am

Desktop Viewport

screen shot 2017-08-09 at 10 29 35 am

P1 has pr ssr

Most helpful comment

I just published angular-css-flex-layout

All 47 comments

This is a known issue.

Issue:

Current SSR must handcraft the flexbox CSS for the initial rendered page.

Reason:

The flex-layout directives use the browser viewport size to determine the active breakpoint and generate the inline styles. Current SSR does not have access to remote viewport size.

@vikerman - We should discuss possbile solutions to this.

Isn't it possible the render classic/static responsive CSS ? I mean something like this, according to the template above :

@media all {
  display: flex;
  flex-wrap: wrap;
}

@media all and (max-width: 599px) {
  flex-direction: column;
  flex-wrap: nowrap;
}

Why is the real viewport size needed ? The browser knows how to take care of that by itself.

@cyrilletuzi - angular/flex-layout does not generate CSS stylesheet. Instead, it dynamically generates and injects inline CSS based on breakpoint activation.

I see two (2) issues (based on snapshots of your sample):

  • The layouts for desktop versions [for javascript ON or OFF] should the same.
  • The layout for mobile PRE-RENDER is obviously not correct (as expected with known issue).

The layouts for desktop versions [for javascript ON or OFF] should the same.

Could it be related to how default values are managed in Flex Layout ? Because when I first tried this package I was quite surprised.

First, I tried to do this : as the default CSS behavior (vertical display) was fine for mobile, I tried this :

<div fxLayout.gt-xs="row"></div> 

I expected only desktop screens to be impacted, but the result of this code is to generate display: flex by default, and as flex-direction default CSS value is row, then in mobile the content was horizontal too ! That's why I inverted the logic (like in the issue initial message).

It's the same for all other flex layout properties (it's why I need to do fxLayoutWrap.xs="nowrap" despite it is the default value in CSS).

It is possible to predict screen information by using user-agent. However, it requires another implementation in platform-server, because it just gives "Fake user agent" in platform-server.
On the other hand, matchMedia and getComputedStyle mocks can be used for SSR instead of using user-agent without waiting for platform-server update.

@ardatan - matchMedia + getComputedStyle on SSR will not be using the viewport size of the remote client so the generated, injected inline styles will mostly likely be wrong for mobile.

@ThomasBurleson Sorry, you are right. We still need some information about screen sizes from somewhere like user-agent. Unfortunately, it needs a change in platform-server, does it?

I think the platform server needs to seed the remote client with micro-script to report the viewport size (and perhaps other information).

Anything new on this thread?
We've widely used this library and currently it's the only big issue preventing our server-side rendering :)

Since https://github.com/angular/flex-layout/pull/353 has been merged, I assume that responsive utilities (media-query) should now work with server side rendering.

But below doesn't work with latest version of flex-layout:

media.isActive('xs')
media.isActive('gt-xs')
fxHide.md
fxLayoutGap.gt-xs="15px"
etc

Happy to see in-progress label 馃憤

Issue (1) identified by @ThomasBurleson here may be fixed by #466. The other issue seems to require a more creative solution

@cyrilletuzi #466 has been merged, can you check to see if your desktop view is the same both Universal and browser-side? That would greatly narrow the scope of this issue (ie we could change the name to bug(universal): SSR doesn't properly retrieve viewport size).

Also, as a side note, fxLayoutWrap has been deprecated, please move to integrating it with the fxLayout API (ie instead of fxLayout.xs="row" fxLayoutWrap.xs="nowrap" do fxLayout.xs="row nowrap"

Default styles (meaning the ones corresponding to a directive without .xs or else) are OK. But yes, the viewport size is still not retrieved, so responsive directives are not OK.

To be clear, here is the updated example :

<div fxLayout="row wrap" fxLayout.xs="column">
  <div class="test" fxFlex="calc(100% / 3)" fxFlex.xs="100%" *ngFor="let item of list" [innerHTML]="item"></div>
</div>

So as this example uses desktop values as default, SSR in desktop is OK, meaning styles are :

  • on the wrapper : style="display:flex;box-sizing:border-box;flex-direction:row;flex-wrap:wrap;-webkit-flex-direction:row;-webkit-flex-wrap:wrap;"
  • on the items : style="flex:1 1 calc(100% / 3);box-sizing:border-box;-webkit-flex:1 1 calc(100% / 3);min-width:calc(100% / 3);"

But on mobile, SSR is not OK as styles are the same as in desktop SSR.

By the way, I spotted another issue : prefixed styles (with -webkit) are put last. They should be first (in Safari, it results in applying prefixed properties instead of standard ones).

@cyrilletuzi Can you open a separate issue for the vendor prefixes? (though as far as I know this is intentional -- vendor prefixes should be preferred if available, no?)

As for the media-query issue, I see two possible solutions:

  1. Implement the fx directives to use straight media-query injection only, and turn the media service into a no-op on the server (lest there be conflicting styles). This would mean the same (no-op) for ngClass.xx and ngStyle.xx (and *ngIf.xx when it gets here)
  2. Have the Universal team implement a mediaQuery solution that involves picking viewport size from the user-agent (not ideal, but there is precedence for this behavior on the web)

The two options could be selected by user preference at bootstrap as follows:
FlexLayoutModule.withServerMedia() or something similar (where default is to disable with SSR)

In my opinion, TB's comment here is not viable, since (a) we don't know if the user supports javascript at all, and (b) it's a waste of network resources and loading time, which is what SSR, among other things, is trying to minimize.

@vikerman Do you see an alternative solution? For option 2, we could finally implement the WINDOW token (similar to DOCUMENT), and patch the window.mediaQuery function call on the server using a library like DeviceAtlas

Also cc @gdi2290 @MarkPieszak @alxhub @Toxicable Thoughts?

I don't know the exact reasons why the library is designed to be pure js, (unlike the similar flex-layout library in [email protected], which uses css media queries). But, it seems using css media queries is simpler approach and more importantly, it doesn't introduce any issue for SSR, as there is no need to know media size on the server.

@alirezamirian The Flex Layout library is about a lot more than setting css styles based on preset breakpoints. It also allows for custom breakpoint creation, MediaQuery detection, interpolated value processing, etc. Almost all of these require a pure-JS approach, or at least the viewport size at runtime. If you鈥檙e not satisfied with this, you are more than welcome to pull the CSS style sheet from AngularJS Material and insert it in your application instead.

These things are awesome and quite useful. However, a pure js approach has inherent issues with SSR, as the media size are not available in the backend and at best it can be estimated via client agent http header, which by the way, is not so reliable. because lots of platforms provide ability to change browser size, and if so, it's impossible to know the actual media size in backend via client agent headers, and this will prevent smooth transition from server-side rendered content.

I think a css based approach can be used for directives (like AngularJS Material) while at the same time, other features like MediaQuery detection can be provided in pure JS, with the reasonable caveat of not being able to use them in server platform. By the way, the css based solution can be in form of scss mixins and functions which enables parameterizing breakpoints.

I'm thinking of creating a module that does the exact same thing that AngularJS Material layout directive does (which is basically adding required classes), with the exact same syntax of @angular/flex-layout directives and using that along with AngularJS Material layout css, as a kind of polyfill for server-side rendering.
As the pure js approach of @angular/flex-layout adds inline style, it should be safe to use this module and @angular/flex-layout side-by-side.

@CaerusKaru thanks for trying to kick-start a conversation about how best to solve this. It doesn't seem like this issue has much engagement though from other contributors. Is there any sense on what priority this issue has internally? My project is getting close to complete, and this is one of the last roadblocks. I REALLY don't want to pull out flex-layout as it's been super helpful. Thanks in advance!

@rkajbaf Unfortunately I have no idea what the real priority is. @ThomasBurleson is the main contributor and has a lot of other responsibilities in addition to maintaining this project. I remember reading somewhere that Flex Layout was going to be integrated with the Angular CDK at some point, so maybe they're prioritizing that right now. Either way, this is an issue that I'm personally looking into, possibly blending parts of the ideas @alirezamirian suggested with some of my own, but I have no ETA for you.

Thanks @CaerusKaru, let me know if there is anything I can do to help!

I created a css-only version of FlexLayoutModule with the same directives as @angular/flex-layout which adds required classes to work with AngularJS flex layout css. It's a drop-in replacement for FlexLayoutModule of @angular/flex-layout with some limitations (ngClass., ngStyle., src.* are not available for example because they need js).
For my own use case, it did a pretty good job and I'm going to put it on github in next few days. I hope it helps others as a temporary solution for SSR.

BTW, using this css based FlexLayoutModule doesn't mean you can't use other @angular/flex-layout modules like MediaQueriesModule (currently it's the only other module).

I just published angular-css-flex-layout

With reference to discussion in comments:

https://github.com/angular/flex-layout/issues/430#issuecomment-362006930

and

https://github.com/angular/flex-layout/issues/430#issuecomment-362010145

It would be really great if the classes for the common styles could be combined. I just check a page on my site and it had 212 occurrences of inline style -webkit-flex:1 1 0.000000001px;box-sizing:border-box;flex:1 1 0.000000001px; which is roughly 16kb; that's just 1 inline style combining others could offer more saving.

@naveedahmed1 - inline styles have the highest css specificity. Since these styles are injected at runtime and we want the high specificity, do you see NOT having classes as a performance issue?

@ThomasBurleson you are right inline styles have highest specificity, but does inline styles offers better performance than css class, I am not sure about it but if we have repeated inline styles I believe combining them in a class should be better in terms of performance since browser will have to evaluate those styles only once (again my thinking, not backed by any tests).

I think with SSR prime objective is to show the website contents on client screen as quickly as possible. Repeated inline styles increase the page size, for example as I mentioned above, I check a page on my site and which had 212 occurrences of inline style -webkit-flex:1 1 0.000000001px;box-sizing:border-box;flex:1 1 0.000000001px; which is roughly 16kb; that's just 1 inline style combining others could offer more saving.

So my point is, at least on server, combining same styles in a single class should reduce overall page size, hence reducing, initial load time and should offer bandwidth saving.

@naveedahmed1 We've discussed this offline, and we've come to the following conclusion. It's not feasible/practical to combine styles in Flex Layout and still maintain the versatility that each individual set of styles offers. If you have a use case where you have a lot of repeated styles, you are more than welcome to package those into CSS classes yourself (along with setting media queries manually to maintain the responsive layout).

When the fix for this issue is launched, there will be an option to omit the SSR module, which will result in no styles generated on the server (this is one of three options: accurate SSR styling, inline SSR styling (current implementation), and no SSR styling). This will be covered in the documentation when finalized. Hopefully this can cover your use case.

Thank you @CaerusKaru for the detailed reply. Is the fix available in nightly builds?

Should omitting the SSR module, also affect things like ObservableMedia?

ObservableMedia is going to be no-op on the server, for obvious reasons, regardless of how SSR is setup for Flex Layout. The fix is in PR #567, which is still undergoing design and review. It's slated to be included in beta.14 at the beginning of March.

Actually what I wanted to ask was, should we expect any major changes to the templates/components to disable SSR styling?

Everything is still being finalized, but there will be a detailed writeup about everything closer to the release.

Sound good!

May be you could also give a second thought to combining the classes for same styles, at least for SSR only.

It's very unlikely to change. If you want to come up with an independent solution, we would be happy to consider it.

Hi there. These days I faced the same problem and tried to come up with a solution using stylesheets instead. Here you can see the results: https://github.com/greg-md/ng-flex-css-layout

I was trying to use the same logic of generating styles as it is right now in flex-layout sources. So, you can give it a try.

I just saw that there is already a work in progress into that direction, which is good.

The guide for using SSR (for those of you using it in the nightly builds) can be found here

@CaerusKaru thanks for the awesome work.

I tired instructions mentioned in SSR guide but receiving below error:

window is not defined
    at MatchMedia.a.../../../flex-layout/bundles/flex-layout.umd.js.MatchMedia._buildMQL (D:\MyProject\ClientApp\dist-server\main.bundle.js:165453:42)
    at D:\MyProject\ClientApp\dist-server\main.bundle.js:165426:33
    at Array.forEach (<anonymous>)
    at MatchMedia.a.../../../flex-layout/bundles/flex-layout.umd.js.MatchMedia.registerQuery (D:\MyProject\ClientApp\dist-server\main.bundle.js:165417:18)
    at MediaService.a.../../../flex-layout/bundles/flex-layout.umd.js.MediaService._registerBreakPoints (D:\MyProject\ClientApp\dist-server\main.bundle.js:170663:27)
    at new MediaService (D:\MyProject\ClientApp\dist-server\main.bundle.js:170592:14)
    at OBSERVABLE_MEDIA_PROVIDER_FACTORY (D:\MyProject\ClientApp\dist-server\main.bundle.js:170900:29)
    at _callFactory (D:\MyProject\ClientApp\dist-server\main.bundle.js:155403:20)
    at _createProviderInstance$1 (D:\MyProject\ClientApp\dist-server\main.bundle.js:155351:26)
    at resolveNgModuleDep (D:\MyProject\ClientApp\dist-server\main.bundle.js:155333:17)

I have FlexLayoutModule imported in my shared module and FlexLayoutServerModule imported in server module. I also tried Option 3: Generate no Flex Layout stylings on the server even that didn't work. Can you please guide whats missing?

@naveedahmed1 please link a repo so I can take a look

Also, I just realized Option 3 is likely broken for now. I鈥檒l submit a fix for that ASAP before the next release

Thanks for the quick reply.

Sharing a repo would be little difficult since its based on .net core angular cli template. Let me share the setup.

I have AppBrowserModule , AppServerModule and CustomMaterialModule which imports and exports FlexLayoutModule along with the Material modules I am using in my app.

As suggested in guide AppServerModule imports FlexLayoutServerModule.

Do you see any issue with this setup?

Where does CustomMaterialModule get imported?

In all modules where I am using any of the Material component, so used in both sever and client side modules.

Please open a separate issue for this, and I don't understand your reasoning for not being able to provide a sample repo. Without one, we cannot adequately investigate the issue since this works in our internal testing and example apps.

Some feedback, I was finally able to successfully run Option 1, though all elements were shown as block i.e. occupying 100% width unless the clientside code took over control. It seems that the order of imports matters here, I moved FlexLayoutServerModule to the last in imports list and it worked. This option saved me 50kb on the initially rendered page when compared with previous version of Flex-Layout.

Option 2 and Option 3 still doesn't work and throw same error. For options 3, I added SERVER_TOKENto the providers list of AppServerModule but its throwing same error.

@naveedahmed1 Please provide an example repo to document your issues with Option 1.

Options 2 and 3 will be fixed by #624, and I've updated the SSR guide to reflect the import ordering requirement. This may be patched in a future release, but for now it's a hard requirement.

Here are the steps to reproduce the issue:

  1. Use the Universal Starter repo https://github.com/angular/universal-starter
  2. Add FlexLayoutModule to the imports list of AppModule.
  3. Add FlexLayoutServerModule to the imports list of AppServerModule.
  4. Add FlexLayoutModule to the imports list of LazyModule.
  5. Update the template of LazyComponent to the following:
<div fxLayout="row wrap">
            <div fxFlex.xs="50%" fxFlex="20%">
                Item 1
            </div>
            <div fxFlex.xs="50%" fxFlex="30%">
                Item 1 Value
            </div>

            <div fxFlex.xs="50%" fxFlex="20%">
                Item 2
            </div>
            <div fxFlex.xs="50%" fxFlex="30%">
                Item 2 Value
            </div>

            <div fxFlex.xs="50%" fxFlex="20%">
                Item 3
            </div>
            <div fxFlex.xs="50%" fxFlex="30%">
                Item 3 Value
            </div>

            <div fxFlex.xs="50%" fxFlex="20%">
                Item 4
            </div>
            <div fxFlex.xs="50%" fxFlex="30%">
                Item 4 Value
            </div>

            <div fxFlex.xs="50%" fxFlex="20%">
                Item 5
            </div>
            <div fxFlex.xs="50%" fxFlex="30%">
                Item 5 Value
            </div>

            <div fxFlex.xs="50%" fxFlex="20%">
                Item 6
            </div>
            <div fxFlex.xs="50%" fxFlex="30%">
                Item 6 Value
            </div>
</div>

Visit Lazy url and try refreshing the page, you will notice that all DIVs are shown as block level elements before the client side code takes over.

@CaerusKaru - Could you please tell me do you have any updates about issue related to SSR and lazy modules? I mean issue from @naveedahmed1 with reproduction repo above?

@atmman9001 Thanks for opening a separate issue for that. I have a good idea of why it's happening and should have a fix soon (it'll be fixed regardless in v6 by #642) for 5.x.

I'm going to lock this issue since this has been resolved for the most part. Any other issues with SSR should be filed separately.

Was this page helpful?
0 / 5 - 0 ratings