39

I am using Angular 2 and D3.js. I want to show a red rectangle.

It only works if I put styles in the style.css file. Check this plunkr

When I put my styles in the component styles: [], it does not work. Check this plunkr

How to let it work when I use the component styles: []? Thanks

UPDATE: @micronyks provides a solution, but it makes the styles in the component global, basically no difference with writing in style.css file. In this plunkr, it shows style in one component will override another component's styles, so cannot show green and red rectangles.

UPDATE 2: @Günter's way perfectly solve this problem!! Just a remind, for Günter's way: it needs at least Angular beta 10. (My other plunkrs use Angular beta 8) The working demo for green and one red rectangle using Angular beta 12 is here.

import {Component} from 'angular2/core'
@Component({
  selector: 'my-app',
  providers: [],
   styles: [`
    /*this does not work*/
    .bar {
      fill: red;
    }
  `],
  template: `
    <div>
      <svg class="chart"></svg>
    </div>
  `,
  directives: []
})
export class App {
  constructor() {}

  ngOnInit() {
    this.draw();
  }

  draw() {
    let data = [{name: 'A', value: 1}];
    let width = 400, height = 200;

    let x = d3.scale.ordinal().rangeRoundBands([0, width]);
    let y = d3.scale.linear().range([height, 0]);

    let chart = d3.select(".chart")
      .attr("width", width)
      .attr("height", height)
      .append("g");

    x.domain(data.map(function(d) { return d.name; }));
    y.domain([0, d3.max(data, function(d) { return d.value; })]);

    chart.selectAll(".bar")
      .data(data)
      .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.name); })
      .attr("y", function(d) { return y(d.value); })
      .attr("height", function(d) { return height - y(d.value); })
      .attr("width", x.rangeBand());
  }
}
Hongbo Miao
  • 45,290
  • 60
  • 174
  • 267

6 Answers6

52

Update

Angular and SASS agreed on supporting ::ng-deep (instead of >>> or /deep/) a while ago until ::slotted or whatever makes it into browser standards becomes available in all browsers.

ViewEncapsulation.Emulated (default)

That's by design. Angular adds class names unique to components and rewrites the added styles to only apply to the components where they were added.

D3 generates HTML dynamically without Angulars knowledge and Angular can't apply the classes to make the styles apply on the generated HTML.

If you add the styles at the entry point HTML file, Angular also doesn't rewrite the styles and the added helper classes don't take effect.

ViewEncapsulation.None

With encapsulation: ViewEncapsulation.None Angular doesn't do this rewriting, therefore the result is similar to adding the HTML to the index.html.

"Shadow-piercing"

Alternatively you can use the recently introduced shadow piercing CSS combinators >>>, /deep/ and ::shadow (::shadow is just replaced by a and thus very limited). See also https://stackoverflow.com/a/36225709/217408 and the Plunker

:host /deep/ div {
  color: red;
}

SASS

/deep/ works fine with SASS but the alias >>> doesn't.

The shadow-piersing CSS combinators are rewritten by Angular and they don't need to be supported by the browsers. Chrome supported them for a while but they are deprecated - but as said, that doesn't matter, because Angular rewrites them to use its encapsulation emulation.

ViewEncapsulation.Native

Angular doesn't support any way to style such components from the outside. Only if the browser provides support like CSS variables then these can be used.

okandas
  • 11,570
  • 2
  • 15
  • 17
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    thank you, Günter!! It is working perfectly now!! Those things shadow piercing CSS combinators `>>>`, `/deep/` and `::shadow` are amazing!! I feel there are sooooooooo many thing I need to learn. – Hongbo Miao Mar 25 '16 at 19:02
  • Just a remind for other people: for this way, it needs at least Angular beta 10. (My other plunkrs use Angular beta 8) The working demo for green and one red rectangle using Angular beta 12 is [here](http://plnkr.co/edit/qxvODQmklhNpdhaoP8UI?p=preview). – Hongbo Miao Mar 25 '16 at 19:27
  • Hi, Günter, are these CSS combinators deprecated? If yes, then any other ways? https://www.chromestatus.com/feature/6750456638341120 – Hongbo Miao May 17 '16 at 04:18
  • 1
    "This doesn't work" no idea what you think I should do with this. You probably did something wrong but how should we know? – Günter Zöchbauer Dec 21 '16 at 08:41
  • 1
    This worked to show 'dots' for the data points on a line chart : Added the following to my `reportgraph.component.css` file : `>>> #reportClubSignupRate .nv-point { fill-opacity: 1 !important; }` – Simon_Weaver Dec 22 '16 at 04:42
  • Actually `::ng-deep` is what should be used since quite a while and is the only one supported by SASS. – Günter Zöchbauer Jun 27 '19 at 03:00
23

ViewEncapsulation will fix your problem.

import {Component,ViewEncapsulation} from 'angular2/core'

@Component({
  selector: 'my-app',
  encapsulation: ViewEncapsulation.None,
  providers: [],
   styles: [`
     .bar {
       fill: red;
    }
  `],
  template: `
    <div>
      <svg class="chart"></svg>
    </div>
  `,
  directives: []
})
micronyks
  • 54,797
  • 15
  • 112
  • 146
  • can u explain a little bit? that will be more helpful. thanks – Hongbo Miao Mar 25 '16 at 05:53
  • 2
    https://egghead.io/lessons/angular-2-controlling-how-styles-are-shared-with-view-encapsulation just see this video once and you will become aware of three different ways that are available in angular2. – micronyks Mar 25 '16 at 05:59
  • after watching this video, basically it makes the styles global, then just like using in `style.css`, please see this [plunkr](http://plnkr.co/edit/D5EDAC5QvQDWxtcSjLHq?p=preview), then I cannot show one red and one green rectangle... The problem comes back – Hongbo Miao Mar 25 '16 at 06:24
9

View Encapsulation

This is because of the view encapsulation in Angular 2. By default, all the HTML and CSS is transformed so that it's only applied locally. In other words, if you add this style in your component's CSS:

h2 { color: red; }

It will only affect h2 elements inside the component, not every h2 element in your whole app. You can read more about this mechanisms in Angular documentation on View Encapsulation.

Why does it affect you?

Angular transforms your styles but since the C3 graph is not yet drawn, it can't transform HTML/SVG too. Because of that, component styles won't match elements inside of C3 graph.

What should I do?

External stylesheet

External stylesheets are not transformed by the View Encapsulation mechanisms, so effectively they will affect your C3 chart (and any other element for that matter).

If you're using Angular CLI, adding external stylesheet is really simple. Edit your angular-cli.json file and inside of apps property find styles array. Add another stylesheet here:

{
    …
    "apps": [
        {
            …
            "styles": [
                "styles.scss",
                "c3.scss" // <---- add this or any other file
            ],
        }
    ],
    …
}

In case you're not using Angular CLI, there must be some way to add external stylesheets. Probably the simplest one is adding another <link …> inside of <head> in your index.html file.

ViewEncapsulation.None

Your first option is: Create a component with the chart (and only chart) and turn off View Encapsulation inside of it. It's a good idea to do that also because of obeying the Single Responsibility Principle. Your chart, by design, should be encapsulated in a separate component. Turning of View Encapsulation is as simple as adding another property to your @Component decorator:

@Component({
    …
    encapsulation: ViewEncapsulation.None
})

/deep/ CSS selector

If, for some reason, you don't want to do that, there's another possibility. You can try using /deep/ selector inside of your CSS which forces styles down into all child components views. Effectively, this breaks the encapsulation and should affect your C3 chart. So, for example, you can do that in your component's CSS file:

/deep/ .c3-chart-arc path {
    stroke: white;
}

Either way, I recommend reading the aforementioned documentation on View Encapsulation in Angular 2 in order to understand why this happens and how it works. This feature is supposed to help you write code, not cause troubles :) This article might help you understand how it works: View Encapsulation on blog.thoughtram.io

Michał Miszczyszyn
  • 11,835
  • 2
  • 35
  • 53
7

you can use

::ng-deep
.bar {
    fill: red;
}

Here you can read perfect article explaining the approach.

And... information from the Angular documentation

Mihail
  • 403
  • 4
  • 8
  • 2
    Please edit this answer to describe what this code does, in a way that responds to the specific question. – bignose Apr 24 '18 at 01:12
1

...then I cannot show one red and one green rectangle... The problem comes back

I think it's some override, I do not know how much of this is true, but I think this solves your problem.

add in child1-cmp, child1-cmp .bar for example:

@Component({
  encapsulation: ViewEncapsulation.None,
  selector: 'child1-cmp',
   styles: [`
    child1-cmp .bar {
      fill: red;
    }
  `],
  template: `
    <div>
      <svg class="chart1"></svg>
    </div>
  `,
  directives: []
})

Note: in addition to encapsulation: ViewEncapsulation.None, as mentioned by micronyks.

Test

Plunker


or this:

@Component({
  selector: 'my-app',
  directives: [Child1Cmp, Child2Cmp],
   encapsulation: ViewEncapsulation.None,
   styles: [`
    child1-cmp .bar {
      fill: red;
    }
  
    child2-cmp .bar {
      fill: yellow;
    }
  `],
   ..//

@Component({
  //encapsulation: ViewEncapsulation.None,
  selector: 'child1-cmp',
  template: `
    <div>
      <svg class="chart1"></svg>
    </div>
  `,
  directives: []
})

@Component({
  //encapsulation: ViewEncapsulation.None,
  selector: 'child2-cmp',
  template: `
    <div>
      <svg class="chart2"></svg>
    </div>
  `,
  directives: []
})

Test

Plunker


or this using class .chart1, .chart2, for example if you want.

@Component({
  selector: 'my-app',
  directives: [Child1Cmp, Child2Cmp],
   encapsulation: ViewEncapsulation.None,
   styles: [`
    .chart1 .bar {
      fill: red;
    }
  
    .chart2 .bar {
      fill: yellow;
    }
  `],
   ..//

Test

Plunker

Community
  • 1
  • 1
Angel Angel
  • 19,670
  • 29
  • 79
  • 105
  • thanks, @angel-angel, they are very nice walkaround ways, but I have tens of these small components, each of them is also reused tens or even hundreds of times. It is quite hard to maintain if I cannot limit their styles in their own component. – Hongbo Miao Mar 25 '16 at 16:39
0

I found that * /deep/ .my-element-class works, but for some reason, ONLY if the svg parent element is present in the html template (not when the svg parent element is created on the fly by d3).

For instance, the following situation would work:

mycomponent.component.html

<svg id="mygraph"></svg> <!-- IMPORTANT!! -->

mycomponent.component.css

* /deep/ .my-element-class {
  /* ... desired styles */
}

mycomponent.component.ts

d3.select("svg#mygraph").append("circle").classed("my-element-class", true)
 ...
maia
  • 3,910
  • 4
  • 27
  • 34