What is the best way to contain the global CSS of an Angular app within an exported web component?

Objective

In my Angular 11 website, there is a comprehensive form component created using Angular Material named myExportableComponent. I aim to utilize this component in the site as usual, but also export it as a generic web component or "custom element," known as an "Angular Element." This will enable its usage on any third-party platform, regardless of their technology stack, while functioning identically to how it operates on my site.

myExportableComponent should retain normal functionality as an Angular component within my website. It should be customizable through both global CSS located in styles.scss and component-specific CSS found in my-exportable.component.scss.

When implemented as a web component, myExportableComponent must also respond to global and component-specific CSS styling like regular, without impacting the surrounding elements on the third-party page where it is embedded. This aspect presents a challenge for me.

Current Configuration

The myExportableComponent is organized within a distinct sub-project of the main project. I am able to build it independently and integrate the generated js/css files into another site successfully. The process resembles:

<link rel="stylesheet" href="myExportableComponentStyles.css">
<script type="text/javascript" src="myExportableComponent.js"></script>

<div class="mat-app-background mat-typography">
    <my-exportable-component></my-exportable-component>
</div>

To ensure that myExportableComponent shares styles with my primary site, adjustments were made in angular.json, linking it to the main app's theme.scss file (containing Angular material theme) and styles.scss (additional global styles). The setup looks like this...

angular.json:

"mainApp": {
    ...
        "styles": [
            "src/theme.scss",
            "src/styles.scss"
        ],
"myExportableComponentApp": {
    ...
        "styles": [
            "src/theme.scss",  //NOT "projects/myExportableComponentApp/src/theme.scss"
            "src/styles.scss"  //NOT "projects/myExportableComponentApp/src/styles.scss"
        ],

Issue at Hand

The global CSS does not remain contained when the web component is inserted on a different site. For instance, adding a global link style for my app in styles.scss impacts all links on the external site's layout too. While the styles from theme.scss (Material) appear isolated, this may change if the host site also employs Angular Material.

Potential Solutions

Idea #1: encapsulation settings

Considering employing

encapsulation: ViewEncapsulation.ShadowDom
on myExportableComponent, however, A) it disrupts some of the Material styling and icons, and B) it fails to address the fundamental issue concerning global CSS. Furthermore, the default setting appears effective in containing component-specific CSS.

Idea #2: target global CSS to my HTML only

If consumers could include a specific class in their HTML tag as present in my app and apply it around the web component, it might allow targeting of global CSS solely to elements within those containers, such as:

.special-wrapper a {
   /* styling */
}

This method does isolate global styles from the third-party page effectively. Nonetheless, it seems to result in a precedence conflict where global styles override component-specific styles erroneously.

Idea #3: utilization of :host or :host-context selectors in global CSS?

I confess struggling with implementing these selectors and observed no noticeable impact in my application.

Idea #4: Build-time scripts?

Upon building the myExportableComponent sub-project, if there exists a means to automatically extract all global CSS from theme.scss and

styles.scss</code, transferring them to the beginning of <code>my-exportable.component.scss</code, it could potentially resolve the issue. Consequently, my "global" styles would then remain encapsulated within the web component due to placement in the component-specific file.</p>
<p>Is such a modification feasible during the execution of <code>ng build myExportableComponentApp --prod
? Regrettably, I lack initial guidance on the implementation.

Any innovative suggestions are welcome!

Answer №1

Here is the approach I ultimately took:

  1. I removed the global style.scss file for the sub-project from the angular.json.

  2. I created a new component that extends myExportableComponent, with no additional code of its own. This new component shares the template of the base component and does not need to be exported from the sub-project module. This ensures that the main app can only use the base component by default.

  3. The styleUrls property of the new child component includes two files. The first file is a component-specific stylesheet that simply imports the global site stylesheet using something like

    @import '../../../../../../src/app/styles.scss';
    . I encountered build errors when trying to reference the global stylesheet directly, so importing it within a "normal" component-specific stylesheet solved the issue. The second file references the normal component-specific stylesheet of the base component.

  4. When creating the custom element for the web component, I used the child component instead of the parent.

In summary, when building the generic web component, the global css from the main site will be included at the top of the component-specific css for myExportableComponent, rather than being applied globally. This encapsulates the global styles and allows them to still be overridden by component-specific styles as usual.

I also wanted to apply the same technique to the theme.scss containing the Material theme, but it seems to only work if it's in the global scope (https://github.com/angular/components/issues/3386). However, this limitation is not a critical issue for me at the moment.

PROs:

  • No need for special setup or disruption of the main site project. Both it and myExportableComponent can be managed in the standard way within our main app.
  • Style encapsulation functions the same in the exported web component as it does in our main project, without leaking out to external sites.
  • No requirement for special build scripts.

CONs:

  • It may seem unconventional, and precautions must be taken to prevent other developers from adding content to the dummy/inherited files used in this solution.
  • As far as I could determine, applying this method to the Material theme css is currently not feasible.

Answer №2

My approach involves utilizing scss/sass in a unique way.

In my (global) styles.scss file, I have organized my global styles within the webcomponents "tag". This method seems to be effective even with nested imports, such as those for bootstrap.

https://sass-lang.com/documentation/at-rules/import#nesting

my-component-tag {
  @import '../../../libs/some.scss';
  
  body {
    margin: 0px;
    height: 100%;
    width: 100%;
    overflow-x: hidden;
  }
  
  .modal-content {
    padding: 20px;
  }
}

Unfortunately, I encountered an issue with build optimization when trying to inline "critical css", resulting in an "Index html generation failed" error.

To resolve this, I disabled it in angular.json

"architect": {
  "build": {
     ...
     "optimization": {
       "scripts": true,
       "styles": {
         "minify": true,
         "inlineCritical": false
       },
       "fonts": true
   }
  }
}

This also has implications for importing bootstrap

*, :after, :before {
    box-sizing: border-box;
}

now becomes

my-component-tag *, my-component-tag :after, my-component-tag :before {
    box-sizing: border-box;
}

and no longer disrupts the hosting website.

If you wish to maintain your global styles.scss "as is", you can create a webcomponent.scss file

my-component-tag {
 @import "styles";
}

and utilize it in the build configuration:

"architect": {
  "build": {
     ...
     "configurations": {
       "webcomponent": {
         "styles": ["src/webcomponent.scss"],
       }
     }

   }
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Concealing and Revealing Bootstrap Grid Elements

Exploring ways to streamline my development process, I am looking to apply bootstrap's best practices. However, I have a query regarding showing and hiding columns. Here is the layout setup I am working with: <div class="row" id="contactGrid"> ...

Guide on achieving a full scrollable parent by positioning a pseudo-element absolutely

I am trying to have a position: absolute element fill its parent, even if the parent is scrollable. To demonstrate this issue, I created a codepen example. Although the red overlay fills the div initially, it does not stay overlaid when the content is sc ...

Filtering object properties from arrays in Angular HTML can be achieved using various methods such as

I am currently working on a unique "Presentation-Editor" tool where I can see all the presentations that have been created. For this overview, I want to showcase the first slide of each presentation as a preview. However, there is a challenge because I on ...

Angular 6 CSS spacing dilemmas

We recently made the switch from Angular 5 to Angular 6 and noticed that there is no spacing between the buttons/icons, etc. We are looking to bring back the spaces between the buttons and icons. I have recreated the issue below. As you can see in the Ang ...

Enabling or Disabling CSS Background Clip Support for Internet Explorer

Applying background-clip to text is effective on most browsers, except for IE! .main-menu .nav li a { font-family: freight-big-pro, serif; font-size: 4rem; font-weight: 400; color: #0f0e0e; text-decoration: none; -webkit-background ...

Conceal flexbox item depending on neighboring element dimensions or content

I've encountered an issue while using a flexbox to display two elements side by side. The left element contains text, while the right one holds a number. When the value in the right element has at least 7 digits ( > 999,999 or < -999,999), I ne ...

Customize Typography style variations in Material-UI version 5

I have encountered a similar issue, but with a twist from Can't override root styles of Typography from Materil-UI Back in v4, I had a theme set up like this: import { createTheme } from '@material-ui/core/styles'; export const theme = cre ...

How can I toggle the visibility of an item on click using jQuery?

I need to toggle the visibility of a specific div when clicking on an anchor. Here is the code I have written for this: jQuery('.mycart').click(function(e){ e.preventDefault(); var target = jQuery(".basket"); ...

Integrating Vimeo videos into Angular applications

I am attempting to stream videos using URLs in my Angular application. Every time I try, I encounter the following error: Access to XMLHttpRequest at 'https://player.vimeo.com/video/548582212?badge=0&amp;autopause=0&amp;player_id=0&amp;ap ...

What is the best way to eliminate a vertical line from an HTML table?

I am looking to remove specific vertical lines from an HTML table. There are only 3 vertical lines in total, and I want to remove the first and third lines. Below is my code: <html> <head> <style type="text/css"> .table1{ background: ...

An image overlaying a background design featuring a navigation bar and company logo

Is there a way to stack images on a background image similar to this using HTML? There is also a navbar and logo on the background. I attempted to do something like this: //clearfix .image-stack::after { content: ' '; display: table; clear ...

Insert Angular HTML tag into TypeScript

I am currently working on creating my own text editor, but I'm facing an issue. When I apply the bold style, all of the text becomes bold. How can I make it so that only the text I select becomes bold without affecting the rest of the text? Additional ...

Discover the Location and Sign Up for Angular2+ Service

I'm currently using the Google Maps API to retrieve a user's geoLocation data, including latitude and longitude. My goal is to pass this information to a web API service in order to receive JSON output of surrounding addresses. I have implemented ...

An issue with glob patterns in Angular's ngsw-config.json configuration

I need to prevent specific js files from being cached by a service worker. Specifically, I want to include all js files except for those that start with a certain string, like config. "assetGroups": [ { "name": "app&quo ...

Adjusting the Image Size in Web Browser Control to Match Device Width on WP8

I am currently working on a WP8 application that utilizes the WebBrowser control to display images. My goal is to have the image width match the width of the phone exactly, but I am encountering some difficulties in achieving this. My approach involves ob ...

Explore the Filter List without being affected by the arrangement of words or the level of search precision

I was able to resolve the issue by adjusting my search filter algorithm. Now, I can search regardless of word order, but the results are not as specific as I need them to be. When I type "Correy" in the search bar, it shows all results containing that wo ...

Tips for executing an operation based on multiple observables in Angular

I need to create a functionality where upon clicking a button, a file containing a specific user's data is exported. The user's identity is stored in an observable (user$). If the user is not authorized to access this data, they should be redirec ...

I am looking to create a div that can consistently refresh on its own without needing to refresh

I have implemented a comment system using ajax that is functioning well. I am looking to incorporate an ajax/js code to automatically refresh my "time ago" div every 5 seconds so that the time updates while users are viewing the same post. Important note: ...

Utilizing CSS to adjust the position of a background image at its starting point

I'm looking to create a unique design for the sidebar on my Tumblr page, where the header has a curved shape while the rest of the sidebar has a squared-off look with 100% height. This design should flow seamlessly off the page without a visible foote ...

Exploring the World of Design in Next JS

When styling a Next.js app, is it necessary to use import styles from "./stylesheet.css" and apply classes with className={styles.aClassName}? If so, what if I need to style an element by ID or tag name? Is there a way to style a Next.js app li ...