Angular2 Styles in a Directive
Asked Answered
J

6

62

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?

Jinja answered 10/3, 2016 at 11:38 Comment(2)
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. - CheersRagged
I added an Angular Feature Request for this: github.com/angular/angular/issues/48871Khalif
S
68

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';
}
Snorter answered 10/3, 2016 at 11:41 Comment(5)
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.Jinja
Appreciate your answer in this case is much better than adding a dependency to ElementRef!Jinja
This is what Angular provides. Only components support adding stylesheets (inline or imports). (styles, styleUrls).Streusel
What if I have a LESS file with @primaryColour and I need to set that style?Affusion
@Affusion I don't understand your question, maybe because I don't know LESS.Streusel
R
25

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.

Ragged answered 14/3, 2017 at 18:36 Comment(4)
"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. <other-component myHighlight></other-component>Avogadro
@Avogadro <ng-content></ng-content> is the important part of my-highlight.component.htmlRihana
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."Shredding
When I try this with a directive for th, I get the error Multiple components match node with tagname th.Predation
F
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()',
  }
Floorman answered 10/3, 2016 at 12:1 Comment(0)
Y
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

Yellowhammer answered 13/9, 2019 at 12:7 Comment(0)
T
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'
    );
  }
}
Tralee answered 27/11, 2017 at 14:57 Comment(4)
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.Banter
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.Tralee
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.Banter
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.Tralee
L
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);
    }
}
Landgravine answered 21/10, 2016 at 10:56 Comment(4)
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.Yusem
works for me. Altered parentElement{ ion-card{ <mystyles> }} to [parentElement]{ <mystyles> } when i changed the element-selector to an attribute-selector and add the attribute-selector to the ion-card element in my component.Multiplex
@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).Cards
probably it is way better with angular 5 improvements.Yusem

© 2022 - 2024 — McMap. All rights reserved.