Preventing 3rd party CSS pollution in Angular

39 views Asked by At

Our app - consisting of multiple Angular application and library projects - uses several 3rd party Angular libraries for generic components like inputs, buttons, popups etc. Each such 3rd party brings its own CSS which is usually not encapsulated, i.e. we're supposed to import the stylesheet globally in each Angular application. What's worse is that sometimes the 3rd party is dependent on another 3rd party that also has its own CSS - the culprit here is usually bootstrap - which contains "aggresive" CSS rules, i.e. it does not only style HTML elements with specific classes, but it styles HTML elements based on their tags, e.g. "all inputs of type text are supposed to look like this".

Now, if we were to just start an application project and import the necessary external CSS from the beginning, then I guess it would be OK to just go ahead and style everything around the external CSS rules. The problem is that we are in a situation where the application project is quite a few years old and has a lot of content and functionality and we just got a new feature request where we are supposed to use 3rd party component Bar, which in order to look properly, we are supposed to bring the entire CSS of the 3rd party, but that messes up the styling of the entire project. Also, our code has this flaw that devs used very generic classes in the past, like "btn", "icon", "tooltip" so you can imagine the impact one such global CSS import can have on the project.

I have tried and thought of a few sollutions for this:

  1. Go through the entire HTML content of the project and individually fix every issue caused by the import - This is too big of an effort. We do not want this!
  2. Apply some CSS stylesheet that maybe we can find online that resets everything back to normal - We would prefer not to do this since the projects are already a CSS nightmare because of global overrides, careless ::ng-deep usages and !important rules
  3. Try to self-contain the external CSS in some generic wrappers - We are already in the process of creating wrappers over most used 3rd party components in our app that behave like black-boxes (I will not go into details right now on why we do this) so this seems like a good solution candidate. I am going to elaborate on this in the following lines.

We need to import Bar component into our application project, but we are not going to import it directly; instead, we have a library in our workspace where we created component Foo that uses 3rd party component Bar, and we are going to import component Foo in the application project. Here are some approaches that I can think of on importing the CSS for 3rd party component Bar:

  1. Do not import bar-library.min.css in foo-library, but import it in the end-user applications that use foo-library using angular.json file. From what I understand from bar-library and Angular documentation, this is the recommended way. Upside of this is that bar-library.min.css will surely be imported only once. Downside of this is the CSS pollution I explained earlier.
  2. Import bar-library.min.css into the .less file of each component from foo-library that needs it, like this:
    :host::ng-deep { // here, :host is <foo-component>
      // import inside :host in order to prevent CSS rules from leaking outside of <foo-component>
      @import (less, once) "node_modules/bar-library/bar-library.min.css"
    }
    
    Upsides of this approach are the prevention of CSS leak / pollution and the fact that we are following OOP encapsulation principle (once we import foo.component into the application project, we get everything we need in order for it to run correctly; no need to know what 3rd party is foo.component using behind the scenes in order to import the corresponding CSS in angular.json). Downside is the concern that bar-library.min.css might get imported multiple times in the final javascript output, based on the number of foo-library components that import it and / or based on the number of <foo-component> usages in outside applications, thus exponentially increasing the size on disk.
  3. Same as 2, but we use ViewEncapsulation.None instead of ::ng-deep so we get the following code:
    foo-component { // here, foo-component tag name is a very specific name so that there's almost 0 chance that this selector selects more than it needs to
      @import (less, once) "node_modules/bar-library/bar-library.min.css"
    }
    

The questions are as follows:

  1. Which one of the above approaches should I prefer? Is there another, better approach that I did not list here?
  2. Regardless of the answer on question 1, how many times will bar-library.min.css get imported in the final javascript output of the application that uses foo-library that in turn uses bar-library? Is the number constant (once)? Is it variable based on the number of foo-library components that import bar-library.min.css? Is it variable based on the number of instances of <foo-component> or of any other similar component from foo-library that get used in the application?
  3. Is there any difference in terms of number of imports between approach 2 and 3?
0

There are 0 answers