D3.js组件中的样式未在Angular 2中显示

39

我正在使用Angular 2和D3.js,想要展示一个红色的矩形。

只有当我把样式放在style.css文件中时才有效。请参考这个 plunkr

当我把样式放在组件的styles: []中时,它不起作用。请参考这个 plunkr

如何让我在使用组件 styles: []时能够生效?谢谢

更新: @micronyks 提供了一种解决方案,但它使组件中的样式全局化,基本上和写在style.css文件中没有区别。在这个 plunkr中,它显示一个组件中的样式将覆盖另一个组件的样式,因此无法显示绿色和红色的矩形。

更新2: @Günter的方法完美地解决了这个问题!只是提醒一下,对于Günter的方法: 它需要至少Angular beta 10。(我的其他plunkr使用Angular beta 8)使用Angular beta 12,绿色和一个红色矩形的工作演示在这里

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());
  }
}

似乎它正在运行。你检查过了吗? - micronyks
@micronyks 当样式只在 style.css 文件中时,它是有效的。 - Hongbo Miao
似乎正在工作 http://www.clipular.com/c/4782456639586304.png?k=_gmVReYtaNL4165y7WzpiWYpOmA - Cyril Cherian
@ Cyril,是的,当样式只在style.css文件中时,它可以工作。 - Hongbo Miao
6个回答

52

更新

Angular和SASS在一段时间前就支持::ng-deep(而不是>>>/deep/),直到::slotted或其他内容被纳入所有浏览器标准。

ViewEncapsulation.Emulated(默认)

这是有意设计的。Angular会为组件添加唯一的类名,并重新编写添加的样式,仅适用于添加它们的组件。

D3动态生成HTML,没有经过Angular的知识,因此Angular无法应用类来使样式应用于生成的HTML。

如果您将样式添加到入口点HTML文件中,则Angular也不会重新编写样式,添加的帮助程序类也不会生效。

ViewEncapsulation.None

使用encapsulation:ViewEncapsulation.None,Angular不会进行这种重写,因此结果类似于将HTML添加到index.html中。

“Shadow-piercing”

或者,您可以使用最近引入的“穿透影子”的CSS组合符号>>>/deep/::shadow::shadow只被替换为 ,因此非常受限制)。请参见https://dev59.com/6loV5IYBdhLWcg3wc-jo#36225709Plunker

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

SASS

/deep/在SASS中可以正常工作,但别名>>>不行。

影子DOM CSS组合器会被Angular重写,因此浏览器无需支持它们。Chrome曾经支持它们一段时间,但已被弃用-但是如上所述,这并不重要,因为Angular会将它们重写为使用其封装仿真。

ViewEncapsulation.Native

Angular不支持从外部样式化这些组件的任何方法。只有浏览器提供支持,例如CSS变量,才能使用它们。


1
谢谢,Günter!!现在它完美地工作了!!那些穿透阴影的CSS组合器>>>/deep/::shadow真是太神奇了!!我感觉还有很多东西需要学习。 - Hongbo Miao
提醒其他人:使用此方法,至少需要 Angular beta 10。(我的其他 plunkrs 使用 Angular beta 8)使用 Angular beta 12 创建的绿色和红色矩形的演示可以在这里找到。 - Hongbo Miao
嗨,Günter,这些CSS组合器已经被弃用了吗?如果是的话,还有其他方法吗?https://www.chromestatus.com/feature/6750456638341120 - Hongbo Miao
1
这没用,我不知道你认为我该怎么做。你可能做错了什么,但我们怎么知道呢? - Günter Zöchbauer
1
这个方法可以在折线图上显示数据点的“点”:将以下内容添加到我的 reportgraph.component.css 文件中:>>> #reportClubSignupRate .nv-point { fill-opacity: 1 !important; } - Simon_Weaver
实际上,::ng-deep 是应该使用的,因为它已经被支持了一段时间,并且是 SASS 唯一支持的选择。 - Günter Zöchbauer

23

ViewEncapsulation会解决你的问题。

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: []
})

能否稍微解释一下?那会更有帮助。谢谢。 - Hongbo Miao
2
https://egghead.io/lessons/angular-2-controlling-how-styles-are-shared-with-view-encapsulation 观看此视频一次,您就会了解 Angular2 中可用的三种不同方式。 - micronyks
看完这个视频后,基本上可以使样式全局化,就像在style.css中使用一样,请参阅此plunkr,然后我无法显示一个红色和一个绿色的矩形...问题又出现了。 - Hongbo Miao

9

视图封装

这是由于Angular 2中的视图封装机制。默认情况下,所有HTML和CSS都会被转换成本地应用的形式,也就是说,如果您在组件的CSS中添加以下样式:

h2 { color: red; }

它只会影响组件内的h2元素,而不是整个应用程序中的每个h2元素。您可以在Angular视图封装文档中了解更多有关此机制的信息。

它为什么会影响你?

Angular会转换您的样式,但由于C3图表尚未绘制,因此它无法转换HTML/SVG。因此,组件样式不会匹配C3图表内的元素。

我该怎么办?

使用外部样式表

外部样式表不受视图封装机制的影响,因此它们将有效地影响您的C3图表(以及任何其他元素)。

如果您使用的是Angular CLI,则添加外部样式表非常简单。编辑您的angular-cli.json文件,并在apps属性内查找styles数组。在这里添加另一个样式表:

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

如果您没有使用Angular CLI,那么必须有一种方法来添加外部样式表。最简单的方法可能是在您的index.html文件中的<head>内添加另一个<link …>标签。

ViewEncapsulation.None

您的第一选择是:创建一个仅包含图表(而非其他元素)并在其中关闭视图封装的组件。这样做还遵循单一职责原则的好习惯。设计上,您的图表应该封装在一个单独的组件中。关闭视图封装就像为您的@Component装饰器添加另一个属性一样简单:
@Component({
    …
    encapsulation: ViewEncapsulation.None
})

/deep/ CSS 选择器

如果出于某种原因,您不想这样做,还有另一种可能性。您可以尝试在CSS中使用 /deep/ 选择器,将样式强制应用于所有子组件视图。实际上,这会破坏封装并影响C3图表。例如,您可以在组件的CSS文件中这样做:

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

无论如何,我建议阅读上述关于Angular 2中视图封装的文档,以了解为什么会发生这种情况以及它是如何工作的。这个功能旨在帮助你编写代码,而不是引起麻烦 :) 这篇文章可能会帮助你理解它的工作原理:博客文章:Angular 2中的视图封装策略

7

2
请编辑此回答,以针对特定问题描述此代码的功能。 - bignose

1
如果我不能显示一个红色和一个绿色的矩形……问题就会重新出现。 我认为这是一些重写,我不知道其中多少是正确的,但我认为这可以解决你的问题。 例如,在`child1-cmp`中添加`child1-cmp .bar`。
@Component({
  encapsulation: ViewEncapsulation.None,
  selector: 'child1-cmp',
   styles: [`
    child1-cmp .bar {
      fill: red;
    }
  `],
  template: `
    <div>
      <svg class="chart1"></svg>
    </div>
  `,
  directives: []
})

注意:除了micronyks提到的encapsulation: ViewEncapsulation.None之外。

测试

Plunker


或者这个:
@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: []
})

测试

Plunker


您可以使用类名 .chart1.chart2 等来实现此效果,例如如果您需要的话。
@Component({
  selector: 'my-app',
  directives: [Child1Cmp, Child2Cmp],
   encapsulation: ViewEncapsulation.None,
   styles: [`
    .chart1 .bar {
      fill: red;
    }
  
    .chart2 .bar {
      fill: yellow;
    }
  `],
   ..//

测试

Plunker


谢谢,@angel-angel,这些方法非常不错,但是我有数十个这样的小组件,每个组件也被重复使用了数十甚至几百次。如果不能在它们自己的组件中限制它们的样式,那么维护起来会相当困难。 - Hongbo Miao

0
我发现* /deep/ .my-element-class是有效的,但出于某种原因,仅当SVG父元素在HTML模板中存在时才有效(而不是在运行时由d3创建)。
例如,以下情况将起作用:

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)
 ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接