Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dartlang: How to allow 3rd parties to style your angular2 component with emulated encapsulation?

Disclaimer for the mods: This does not ask for an opinion on the best way to do this, this asks for a way, how to solve this issue at all.

Problem

Let's imagine, I want to write a material design style button component for my projects. (I know, this is already available, but this is only for illustration purposes.)
I have the button component setup as normal dart library project, which works fine. Now I want to use this material button component in another angular2 app, so i add it to pubspec.yaml and insert it as directive dependency to my new app-component.

The question is, how can I change, for example, the hover color of this button from the outside?

Since my component should use style encapsulation (emulated styles), one cannot simply set the styles globally.

Possible Solutions

Using Custom CSS properties

This is good in theory, however, they're not shipped yet in all browsers and there are no production ready shims. For this to work, someone would define custom css properties inside of the component and set their value outside.

Using input properties for adjustable style settings

Simply give my button component input properties for the styles which should be changed. This does work, but only for those styles, which do not incorporate css pseudo selectors like :hover, since it's not possible to change :hover styles per javaScript and therefore not in dart too. Someone could write the :hover function in javascript/dart to allow this style change, but this seems bad, because it simply duplicates available code from the css engine.

Using normal css links and native shadow dom

This one would work for native shadow dom, since a style link tag inside the component, filled by a customCssPath property of the button component would load the styles correctly into the component. However, since shadowdom is not natively available in all browsers, we cannot use it reliably yet. So we are forced to use emulated encapsulation now.

like image 983
Benjamin Jesuiter Avatar asked Feb 06 '23 04:02

Benjamin Jesuiter


1 Answers

Solution - Using SASS (Huge Thanks to Matan Lurey for this!)

When asking at the Angular2 Repo directly, Matan Lurey, one of the Angular2 Developers for dart provided some guide on how to use Sass mixins to allow the desired styling operations.

One can find the original thread here: https://github.com/dart-lang/angular2/issues/154

Solution Details:

However, since I found it not very straightforward to implement, here is a little more detail on how to make that work:

  1. Imagine, we have the following file internal_component.dart:

    import 'package:angular2/angular2.dart';
    
    @Component(
      selector: 'internal-component',
      styleUrls: const ['internal_component.css'],
      templateUrl: 'internal_component.html',
    )
    class InternalComponent {}
    
  2. This component has this template file internal_component.html:

    <div>
      Some Text
    </div>
    
  3. An this sass style file internal_component.sass:

    div {
      background-color: #ffff00;
    }
    
    @mixin internal-component-background($selector, $color) {
    
      polyfill-unscoped-rule {
        background-color: $color;
        content: '#{$selector} > div'
      }
    }
    
    1. Include the internal-component in some other component (here app_component.dart):

      import 'package:angular2/core.dart';
      import 'package:sass_internal_styles/internal_component/internal_component.dart';
      
      @Component(
        selector: 'my-app',
        styleUrls: const ['app_component.css'],
        templateUrl: 'app_component.html',
        directives: const [InternalComponent],
      )
      class AppComponent {}
      
    2. Inside of the template file app_component.html:

      <internal-component id="comp1"></internal-component>
      <internal-component id="comp2"></internal-component>
      
    3. And finally, inside the `app_component.scss:

      @import "../lib/internal_component/internal_component.scss";
      @include internal-component-background($selector: '#comp2', $color: red);
      

What happens:

  • The string "Some Text" will be displayed twice, the string, as seen in internal_component.htmland twice as seen in app_component.html
  • The first string will have a yellow background, as defined in internal_component.scss. This can be used for defining default values inside the components!
  • The second string will have the red background, as defined by the included mixin in app_component.scss.

One thing to note here:

The selector given to the mixin, must appear inside of the template, where your inner component is used. In our case, this is the id=comp2attribute inside app_component.html

Update 1: Another thing to note:

Use the [attribute] css selector instead of a .class selector and be aware of the css selector strength hirarchy!

The normal component styles are 'scoped' by applying an attribute to the component's tag and apply the styles of this component only to the component selector in combination with the attribute for scoping. Since a elem[attribute]-css rule has more strength than a simple class selector, the class selector will not work. However, you can try to specify an element name before the class to fix this.

You could also use a .class selector in combination with !important. However, do this only if you know the implications! For example the :hover selector for the same element will not work properly anymore, since !important takes precedence.

Why this might work:

The polyfill-unscoped-rule block gets compiled into

#comp2 > div {
    background-color: red;
}

inside the style block for the app_component.css file. (As stylesheet will be moved to the headers in emulation mode by angular2) Since the style-encapsulation is only emulated, this child selector for an inner divis working perfectly.

Update 1: The polyfill-unscoped-rule block compiling is done by webcomponents.js! :)

One might argue, that this trick could be also done from the outside without sass. That's correct, however, with using the mixin techique, the user of your component does not need to dig into the internal structure of your component. He can use some nice Sass mixins instead, which should be documented in the readme of your component.

like image 90
Benjamin Jesuiter Avatar answered Feb 08 '23 00:02

Benjamin Jesuiter