59

In the given examples of Attribute directives (i.e. a directive to add appearance/behaviour), we have a fairly simple setting of a style on the host element.. e.g.

import {Directive, ElementRef } from 'angular2/core';
@Directive({
    selector: '[myHighlight]'
})
export class HighlightDirective {
    constructor(element) {
       element.nativeElement.style.backgroundColor = 'yellow';
    }

static get parameters(){
    return [[ElementRef]];
}

Rather than setting the style, can i use a styles instead? e.g.

@Directive({
    selector: '[myHighlight]',
    styles: [':host { background-color: yellow; }']
})

This doesn't seem to work for me?

I'm doing something slightly more complex which has led to a fair amount of monolothic code, setting lots of styles, using AnimationBuilder etc etc. feels to me like it would be much better to seperate this out into classes and animations in a CSS.

ViewEncapsulation = emulated/default if that matters?

ct5845
  • 1,428
  • 3
  • 16
  • 20
  • Though it's been a year, for posterity's sake, I added the answer below. It involves both using a component as a directive, and changing the default ViewEncapsulation. See my answer below. - Cheers – Modular Mar 14 '17 at 18:43
  • I added an Angular Feature Request for this: https://github.com/angular/angular/issues/48871 – Ole Jan 27 '23 at 18:40

6 Answers6

65

You can use host binding to bind to style attributes:

@Directive({
    selector: '[myHighlight]',
    host: {
      '[style.background-color]': '"yellow"',
    }
})

or

@Directive({
    selector: '[myHighlight]',
})
class MyDirective {
  @HostBinding('style.background-color')
  backgroundColor:string = 'yellow';
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 4
    Thanks, but that still leaves me with the issue of needing to set n number of properties individually, rather than utilising inline/external CSS? It's fine in this simple example, but what if i have 30+ rules with animations, and those rules can be applied under different states/combinations, e.g. hovers, clicks etc. – ct5845 Mar 10 '16 at 11:49
  • Appreciate your answer in this case is much better than adding a dependency to ElementRef! – ct5845 Mar 10 '16 at 11:50
  • 4
    This is what Angular provides. Only components support adding stylesheets (inline or imports). (`styles`, `styleUrls`). – Günter Zöchbauer Mar 10 '16 at 11:53
  • 1
    What if I have a LESS file with @primaryColour and I need to set that style? – David Jul 28 '16 at 07:07
  • @David I don't understand your question, maybe because I don't know LESS. – Günter Zöchbauer Jul 28 '16 at 07:11
23

While the other answers are helpful in most circumstances, you seem to need a more traditional CSS stylesheet approach like I had a use case for.

The issue is Angular's default of emulating a Shadow DOM which scopes styles only within the host element.

TWO OPTIONS:

1)

You can tell Angular to cascade your styles down through all its descendants using the :host /deep/ .some-style-to-cascade-down-like-normal {} or replace /deep/ with >>>. See Angular's Docs about this.

Three important things to note:

  • ViewEncapsulation needs to be its default (emulated) state
  • Angular/Chrome are deprecating both these syntaxes while they are working on a better approach
  • If you're using the Angular CLI, you have to use the /deep/ instead of >>>

2)

Though you'll loose the scoped component encapsulation (if that matters in your case), here is an example using "myHighlight" as a directive though TypeScripted as a component so I can import the stylesheet:

USAGE:
<p myHighlight>Highlight me!</p>

TS (component treated as a directive):

import {
    Component,
    ViewEncapsulation
} from '@angular/core';

@Component({
    selector: 'p[myHighlight]', // Refer to it like an attribute directive
    templateUrl: './my-highlight.component.html',
    styleUrls: ['./my-highlight.component.scss'],
    encapsulation: ViewEncapsulation.None // Tell Angular to not scope your styles
})

Angular Material 2's Button uses this same approach to solve this issue.

And here's a great article called All the Ways to Add CSS to Angular 2 Components which brought me to this awareness and explains how Angular treats all three ViewEncapsulation properties.

Modular
  • 6,440
  • 2
  • 35
  • 38
  • 1
    "Angular Material 2's Button uses this same approach to solve this issue." - they do use the technique you are using, and the selector is directive-like. But unlike in this topic they were not trying to add styles to a directive. For those who decide to convert their directive into a component just for this style benefit, what template do you use? Could you add your .\my-highlight.component.html so we can see how to make this work? And will this not conflict with other components being made out of the same element? e.g. – jcairney Jan 02 '18 at 17:07
  • @jcairney `` is the important part of `my-highlight.component.html` – Michael Jul 09 '18 at 14:32
  • There has been some development on the use of >>> ::ng-deep and /deep/. It looks like those will soon be depreciated and from the link above "Until then ::ng-deep should be preferred for a broader compatibility with the tools." – Andy Braham Oct 20 '18 at 13:49
  • When I try this with a directive for `th`, I get the error `Multiple components match node with tagname th.` – Trevortni Feb 22 '22 at 20:59
7

I have read your comment below first answer. I don't know how would you be able to apply your 30 rules. But few ways are here- plunker.

selector:"[myHighlight]", 
    host: {        
    '(mouseenter)':'changeColor()',
    '[style.background]': '"pink"', 
    '(click)':'clickMe()',
    '(mouseout)':'changeColorOnOut()',
  }
micronyks
  • 54,797
  • 15
  • 112
  • 146
6

It's too late for this answer but has used one tricky solution in my same kind of requirement so i feel it may help someone.

I did it in the following way and it worked for me

<div class="someClass" customDirective>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>

import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[customDirective]'
})
export class CustomDirective {
  domElement: any;

  constructor(private elementRef: ElementRef) {
  this.domElement = this.elementRef.nativeElement;
  const newStyles = {
     'background-color': 'yellow',
     'color': 'red',
     'font-weight': 'bold',
     //...and so on
    };
   Object.keys(newStyles).forEach(element => {
     this.domElement.style.setProperty(`${element}`,newStyles[element]);
    }      

  } 
//Other logic required for the directive...

}

Working Example

Anup Bangale
  • 581
  • 7
  • 7
2

Same as @m.spyratos, but using Renderer2:

import {
  Directive,
  ElementRef,
  OnInit,
  Renderer2
} from '@angular/core';

@Directive({
  selector: '[myButton]'
})
export class MyButtonDirective implements OnInit {
  constructor(
    private elementRef: ElementRef,
    private renderer: Renderer2
  ) { }

  public ngOnInit(): void {
    this.renderer.addClass(
      this.elementRef.nativeElement,
      'my-button'
    );
  }
}
Steve Brush
  • 2,911
  • 1
  • 23
  • 15
  • Why do all of that instead of just writing a CSS style that targets the directive attribute? e.g. [myButton] { ....styles here } <-- This is valid CSS. – dudewad Sep 14 '18 at 18:38
  • Good question. A potential use case is that you want to attach an existing global CSS class name to your directive element. Yet another use case: you want to add a CSS class name programmatically based on some condition. – Steve Brush Sep 15 '18 at 16:33
  • I don't understand the difference between targeting a class name and targeting the directive attribute name. Furthermore, when/how you add a class name doesn't have anything to do with whether or not your CSS will kick in. I'm not understanding how either of those two things are related. – dudewad Sep 19 '18 at 23:00
  • When a global style sheet defines a CSS class (say, in another library you installed from NPM) and is already added to the page in your root HTML template, maybe you want to attach that class name to your directive element? Maybe you want to add the global class name only under certain conditions? I'd say those are valid use cases. – Steve Brush Sep 26 '18 at 14:34
1

Just style the element as you would do normally using the attribute selector. Create a myHighlight.directive.scss (or whatever) file in the same folder as your directive and write your styles in there:

[myhighlight] {
  background-color: yellow;
}

If your app doesn't include your style file automatically, just import it in your main style file. For me in Ionic 2 it was picked up automatically.

If you want to use a specific class instead of the attribute selector, then use the Renderer to add the class.

import {Directive, ElementRef, Renderer} from 'angular2/core';
@Directive({
    selector: '[myHighlight]'
})
export class HighlightDirective {
    constructor(private el: ElementRef, private renderer: Renderer) {
        this.renderer.setElementClass(this.el.nativeElement, 'my-highlight', true);
    }
}
m.spyratos
  • 3,823
  • 2
  • 31
  • 40
  • Using the first approach did not worked for me. Using Angular 2.4. If I need to import into the global.scss, I lose style encapsulation. You can only set a custom class to a directive or use a @Component without view encapsulation. – aelkz May 17 '17 at 01:09
  • works for me. Altered `parentElement{ ion-card{ }}` to `[parentElement]{ }` when i changed the element-selector to an attribute-selector and add the attribute-selector to the `ion-card` element in my component. – fastr.de Aug 09 '17 at 06:40
  • @aelkz: I am a bit late to the party, but it works for me in Angular 5 and Ionic 3. Have you checked that the selector in the scss is not the same as in the .ts file? aka, in the .ts file it usually says "selector:[mySelectorSomething]" and for the scss to have some effect it has to refer to all-lowercase, aka [myselectorsomething]: {}, as also stated in the above example. It is rather subtle and not very logical, so it is easy to miss (it was at least for me). – yogibimbi Mar 02 '18 at 13:16
  • probably it is way better with angular 5 improvements. – aelkz Mar 06 '18 at 17:46