Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] Occasionally cannot find CSS rules when using ElementQueries.listen() in an Angular app #286

Open
henrahmagix opened this issue Jan 15, 2020 · 4 comments

Comments

@henrahmagix
Copy link

henrahmagix commented Jan 15, 2020

About 10% of the time in Safari in an Angular 8 app, there are 0 stylesheets when DOMContentLoaded is triggered, which means ElementQueries cannot find any CSS rules to polyfill and basically does not work for us. The above was gleaned from local testing: refreshing and hard-refreshing multiple times until the page loaded and a known container query wasn't applied; debugging the source revealed that document.styleSheets.length was 0 when DOMContentLoaded was triggered, so we must wait a little bit longer to initialise.

Basically, if document.styleSheets.length === 0 when init() is called, this loop does not run, no css rules are detected, and the library doesn't keep checking so future stylesheet additions are not parsed:

for (var i = 0, j = document.styleSheets.length; i < j; i++) {
try {
if (document.styleSheets[i].href && 0 === document.styleSheets[i].href.indexOf('file://')) {
console.warn("CssElementQueries: unable to parse local css files, " + document.styleSheets[i].href);
}
readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText);
} catch (e) {
}
}

Our solution is to call ElementQueries.init() when document.styleSheets.length > 0. We wait by using requestAnimationFrame() after DOMContentLoaded, which unfortunately means we're not getting the nice browser compatibility of this library's domLoaded function.

// polyfills.ts
import { ElementQueries } from 'css-element-queries';
document.addEventListener('DOMContentLoaded', initElementQueriesWhenDefinitelyReady, false);
function initElementQueriesWhenDefinitelyReady() {
  if (document.styleSheets.length === 0) {
    // Don't impose a time or iteration limit: if there are no stylesheets, then the page cannot reasonably be ready, so we don't need to
    // worry about looping forever.
    requestAnimationFrame(initElementQueriesWhenDefinitelyReady);
    return;
  }
  ElementQueries.init();
}

I'm not exactly sure how this library could work around this. In our app, we're fine with looping infinitely with requestAnimationFrame until stylesheets become available, because we know there will be some; I'm guessing this library doesn't want to assume that in all cases, i.e. if you added this library to an empty page and it had the above solution by default, it would loop indefinitely rather than just do nothing.

Maybe there could be an additional option to use this solution for developers of SPA apps? Perhaps ElementQueries.listenForMinimumStylesheets(1)?

P.S. I love this library! It's a really smart solution 💎

@marcj
Copy link
Owner

marcj commented Jan 16, 2020

That's interesting. I was working lately with a solution that used MutationObserver on the <head> and triggers a ElementQueries.init every time a new style node is found. I think making such an implementation available behind a flag (because MutationObserver costs performance) would solve it for all kind of frameworks.

@henrahmagix
Copy link
Author

That sounds great! Some fab explanations of MutationObserver performance here, for those (like me) who aren't aware: https://stackoverflow.com/questions/31659567/performance-of-mutationobserver-to-detect-nodes-in-entire-dom/39332340

@marcj
Copy link
Owner

marcj commented Jan 16, 2020

Yeah maybe it's not a performance issue at all anymore and we could just make this available for everyone. Would definitely make working with SPA frameworks very easy and increases compatibility with those (especially Angular). I don't know yet where React/Vue puts their stylesheets for loaded components, but I hope in the head. For better performance however we should add a new method ElementQueries.parseStyles(stylesNode) (because init parses all styles) that parses only style rules from given (newly added) node.

@FirstVertex
Copy link

this is working for our Angular application, in the root app.component.ts

export class AppComponent {
  constructor(zone: NgZone) {
    zone.runOutsideAngular(() => ElementQueries.init());
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants