48

So I have this component called InputEdit (basically a Label that can be edited when you click on it... simple enough) and this component has its own shadowed DOM CSS styling. But of course each hosting component will want to set its own font size and color for the input component...

So what would be the best way? Can you just pass in a styling class and apply the entire CSS to the component? Or would it be better to pass each value manually as in:

     <InputEdit [color]="'red'"/>

Which would seem a lot of work, but again since we are using the shadow or emulated DOM, we can't just control the CSS externally.

I am also aware that you can splice open the shadow and target direct elements via:

/* styles.css */
UserInfo /deep/ InputEdit label {
    color: red;
    font-size: 1.1em;
}

Which will basically allow you to enter into a custom component named UserInfo / deep (any level ) / custom component InputEdit and target label with color red...

But again, I am wondering what is the best approach specifically for ng2 like passing a class config into a directive maybe?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
born2net
  • 24,129
  • 22
  • 65
  • 104

6 Answers6

38

I would just use a styles input property on InputEdit, and pass in an object with the desired styles:

<InputEdit [styles]="stylesObj">                 // in host component's template

stylesObj = {font-size: '1.1em', color: 'red'};  // in host component class

<input [ngStyle]="stylesObj" ...>                // in InputEdit component's template

If you have multiple DOM elements you want to style, pass in a more complex object:

<InputEdit [styles]="stylesObj">

stylesObj = {
  input: {font-size: '1.1em', color: 'red'}
  label: { ... } 
};

<label [ngStyle]="styles.label" ...>
<input [ngStyle]="styles.input" ...>
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
23

Mark Rajcok's answer is good for a group of styles, but if you're only going to allow the font-size and color to be changed, you may want to use a more direct approach like you started with (in this example, also enforcing only pixels instead of a more flexible string for demonstration purposes):

For Individual Style Properties:

Component:
<InputEdit [color]="'red'" [fontSize]="16">

component.ts:
Input() color: string = 'black';
Input() fontSize: number = 18;

component.template:
<input type="text" [style.color]="color" [style.fontSize.px]="fontSize">


If Allowing a Group of Styles:

Component:
<InputEdit [styles]="{backgroundColor: 'blue', 'font-size': '16px'}"> NOTE: Make sure the CSS properties are camelCased or in a string if there is more than one word.

component.ts:
@Input() styles: any = {};

component.template:
<input type="text" [ngStyle]="styles">

Modular
  • 6,440
  • 2
  • 35
  • 38
5

update

::slotted is now supported by all new browsers and can be used with `ViewEncapsulation.ShadowDom

https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted

original

/deep/, ::shadow and >>> are deprecated. ::ng-deep is the best option until all browsers properly support style encapsulation and ViewEncapsulation.Emulated can be removed.

The deprecation is only for the native implementation in Chrome (other browsers never implemented it) but Angular has its own emulation of these CSS combinators in ViewEncapsulation.Emulated (default)

/deep/, ::shadow and >>>::ng-deep can therefore be used just fine in Angular2.

For more than simple classes or style property settings use ngStyle Angular 2.0 and ng-style

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • https://bugs.chromium.org/p/chromium/issues/detail?id=498405 Notable render performance optimizations that are now possible with shadow DOM, aren't compatible with these selectors. – Günter Zöchbauer Mar 01 '16 at 22:46
4

If you want to leave it up to the component to define the actual css you can try one of the following:

Add a property on your component for each 'logical' style setting, for instance headerSize.

@Input('headerSize') headerSize: ('small' | 'large');

Then your component can check its value in one of a few ways to style itself:

  1. Modify the HTML itself by showing or hiding child elements

    <h1 *ngIf="headerSize == 'large'">{{ title }}</h1>
    <h2 *ngIf="headerSize == 'small'">{{ title }}</h2>
    
  2. Set a custom class dynamically inside the component somewhere, and style it:

    <div [ngClass]="'header-' + headerSize">
    
    .header-small { h1 { font-size: 20px; } }
    .header-large { h1 { font-size: 30px; } }
    
  3. Set a custom class dynamically at the class level. This is the same as #2 and doesn't require a wrapper element. However it's less than trivial to actually enable and disable these classes.

    @HostBinding('class.header-small') _header_small;
    @HostBinding('class.header-large') _header_large;
    

Also note that if you're using ng-content that the styles applied are the styles defined in the containing component and not the component that actually does the content replacement.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
3

Another option is use CSS variables. In this case, to style the color and font-size of the child component's label, you could set two variables on the parent component's CSS and make use of them in the child's CSS.

userInfo.component.css

InputEdit {
  --label-color: red;
  --label-font-size: 1.1em;
}

inputEdit.component.css

label {
  color: var(--label-color, #000);
  font-size: var(--label-font-size, 1em);
}

Of course, this means you have to define every property you want to be styled, but if you only need to set a few styles on the child, it works fine.

Thomas Higginbotham
  • 1,662
  • 20
  • 25
  • 1
    This approach will probably get a lot wider adoption now that Angular has discontinued support for IE11 - in fact Material may even start to adopt this at some point. – Simon_Weaver Dec 18 '21 at 04:10
0

There are 2 more possible ways I can think of (and that I don't see in any other answer):

@Component({
  selector: 'my-input-edit-button',
  template: `
        <button class="btn btn-theme">My button</button>
  `,
  styles: [`
      :host-context(.red) .btn-theme {
        background: red;
      }
      :host-context(.blue) .btn-theme {
          background: blue;
      }
  `]
})
export class InputEditComponent {}

Then, just add a class on the parent's context:

<div class="blue">
    <my-input-edit-button></my-input-edit-button>
</div>
<div class="red">
    <my-input-edit-button></my-input-edit-button>
</div>
  • Or directly on the host, adding a selector on an input (here for the size)
@Component({
  selector: 'my-input-edit-button',
  template: `
        <button class="btn btn-theme">My button</button>
  `,
  styles: [`
:host {
  &[size='small'] button {
    height: 30px;
  }

  &[size='big'] button {
    height: 60px;
  }
}  `]
})
export class InputEditComponent {
  @Input()
  size: 'small' | 'big';
}

In which case you can pass the typed size as follows:

    <my-input-edit-button size="big"></my-input-edit-button>
    <my-input-edit-button size="small"></my-input-edit-button>
Flavien Volken
  • 19,196
  • 12
  • 100
  • 133