如何从父组件的CSS文件设置子组件的样式?


264

我有一个父组件:

<parent></parent>

我想用子组件填充该组:

<parent>
  <child></child>
  <child></child>
  <child></child>
</parent>

父模板:

<div class="parent">
  <!-- Children goes here -->
  <ng-content></ng-content>
</div>

子模板:

<div class="child">Test</div>

由于parentchild是两个独立的组件,因此它们的样式被锁定在自己的范围内。

在我的父组件中,我尝试执行以下操作:

.parent .child {
  // Styles for child
}

但是.child样式没有被应用到child组件。

我尝试使用styleUrlsparent的样式表包含到child组件中来解决范围问题:

// child.component.ts
styleUrls: [
  './parent.component.css',
  './child.component.css',
]

但这无济于事,还尝试了另一种方法,即将child样式表提取进来,parent但这也无济于事。

那么,如何设置父组件中包含的子组件的样式?



在我的答案中,请参阅完全不依赖范例的技巧。
亚历山大·阿巴库莫夫

Answers:


242

更新-最新方式

如果可以避免,请不要这样做。正如Devon Sans在评论中指出的那样:此功能很可能已被弃用。

更新-较新的方式

Angular 4.3.0开始,不推荐使用所有可穿透的CSS组合器。Angular团队推出了一种新的组合器::ng-deep (仍然处于实验水平,而不是完整的最终方法),如下所示,

演示:https : //plnkr.co/edit/RBJIszu14o4svHLQt563? p =preview

styles: [
    `
     :host { color: red; }

     :host ::ng-deep parent {
       color:blue;
     }
     :host ::ng-deep child{
       color:orange;
     }
     :host ::ng-deep child.class1 {
       color:yellow;
     }
     :host ::ng-deep child.class2{
       color:pink;
     }
    `
],



template: `
      Angular2                                //red
      <parent>                                //blue
          <child></child>                     //orange
          <child class="class1"></child>      //yellow
          <child class="class2"></child>      //pink
      </parent>      
    `


旧方法

您可以使用encapsulation mode和/或piercing CSS combinators >>>, /deep/ and ::shadow

工作示例:http : //plnkr.co/edit/1RBDGQ?p=preview

styles: [
    `
     :host { color: red; }
     :host >>> parent {
       color:blue;
     }
     :host >>> child{
       color:orange;
     }
     :host >>> child.class1 {
       color:yellow;
     }
     :host >>> child.class2{
       color:pink;
     }
    `
    ],

template: `
  Angular2                                //red
  <parent>                                //blue
      <child></child>                     //orange
      <child class="class1"></child>      //yellow
      <child class="class2"></child>      //pink
  </parent>      
`

3
虽然在Chrome中不推荐使用穿孔CSS组合器
Robin-Hoodie

22
角度小组计划也放弃对:: ng-deep的支持。从他们的文档中:“不推荐使用穿刺影子的后代组合器,并且从主要的浏览器和工具中删除了支持。因此,我们计划放弃对Angular的支持(针对/ deep /,>>>和:: ng-的全部3种在此之前,:: ng-deep应该是首选,以便与工具更广泛地兼容。” angular.io/guide/component-styles#deprecated-deep--和ng-deep
德文·萨姆斯

5
只要这是一个公认的答案,人们就会被误导。:: ng-deep不应用作以上注释中的@DevonSams点。
Kostas Siabanis

1
::ng-deep现在已弃用,我不建议在以后的应用程序中使用它
Wilt

11
不提供替代方案而过时的方法可能不是最佳解决方案。
tehlivi

56

更新3:

::ng-deep也已弃用,这意味着您不再应该这样做。目前尚不清楚这将如何影响您需要从父组件覆盖子组件中的样式的事物。对我来说,如果将其完全删除似乎很奇怪,因为这将如何影响需要替换库组件中样式的库?

如果您对此有任何见解,请发表评论。

更新2:

从那时起/deep/,所有其他阴影穿刺选择器均已弃用。Angular掉落了::ng-deep,应该使用它来获得更大的兼容性。

更新:

如果使用Angular-CLI,则需要使用/deep/代替,>>>否则它将不起作用。

原版的:

转到Angular2的Github页面并随机搜索“样式”后,我发现了以下问题:Angular 2-innerHTML样式

表示使用2.0.0-beta.10>>>::shadow选择器中添加的内容。

在2.0.0-beta.10中添加了(>>>)(以及等效的/ deep /)和:: shadow。它们类似于影子DOM CSS组合器(已弃用),仅与封装一起使用:ViewEncapsulation.Emulated,这是Angular2中的默认设置。它们可能也可以与ViewEncapsulation.None一起使用,但由于没有必要而仅被忽略。在支持跨组件样式的更高级功能之前,这些组合器只是一个中间解决方案。

所以简单地做:

:host >>> .child {}

parent的样式表文件中解决了此问题。请注意,如以上引用所述,在支持更高级的跨组件样式之前,此解决方案只是中间的。



40

您不应该使用::ng-deep,它已被弃用。在Angular中,从父级更改子级组件样式的正确方法是使用encapsulation(请阅读以下警告以了解含义):

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

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

然后,您可以通过:: ng-deep修改组件中的CSS形式

.mat-sort-header-container {
  display:flex;
  justify-content:center;
}

警告:这样做会使您为该组件编写的所有CSS规则成为全局规则。

为了将css的范围限制为仅此组件,请将css类添加到组件的top标签​​,然后将css放在此标签的“内部”:

template:
    <div class='my-component'>
      <child-component class="first">First</child>
    </div>,

Scss文件:

.my-component {
  // All your css goes in there in order not to be global
}

3
这是IMO的最佳答案,因为它实际上是即将弃用的可行替代方案::ng-deep。通常,组件无论如何都有自己的选择器(<my-component>, <div my-component>等),因此甚至不需要带有特殊类的包装器元素。
Alex Walker

@AlexWalker这可能是最好的回答您的情况,但值得一提的是,它仅回答了OP的问题AFAICT的一半:这种方法允许CSS传播作为从上到下正常,但凭借扔掉所有的封装中,没有按不仅限于特定父母的孩子。如果您以一种方式设置parent1的子代,并以另一种方式设置parent2的子代,那么这些CSS规则现在将在两个地方相互冲突。这可能令人非常痛苦(并且为避免这种情况,Angular添加了封装)。
鲁芬

@ruffin这就是为什么我在回答中添加警告以了解使用此技术的含义以及如何使用组件上的顶级CSS标记“手动封装”的原因
Tonio,

1
@Tonio-是的,同意; 是直接回复亚历克斯而不是你。他的评论说:“ 所以甚至没有必要使用带有特殊类的包装器元素 ”,这让我有些害怕。也许是针对特定情况,但是Angular浪费时间来支持封装是有原因的。在某些情况下,此答案是可行的解决方案,但是,正如您所说,这通常是一个潜在的危险解决方案。MatthewB的解决方案,例如,在保留封装样式的同时对子元素进行样式设置(但是,如果您拥有多于一代的子组件,它将变得非常混乱)。
鲁芬

19

可悲的是/ deep /选择器似乎已被弃用(至少在Chrome中) https://www.chromestatus.com/features/6750456638341120

简而言之,似乎(目前)没有长期解决方案,只能以某种方式让您的子组件动态设置样式。

您可以将样式对象传递给您的孩子,并通过以下方式应用该对象:
<div [attr.style]="styleobject">

或者,如果您有特定的样式,则可以使用以下方法:
<div [style.background-color]="colorvar">

与此相关的更多讨论:https : //github.com/angular/angular/issues/6511


16

发生了同样的问题,因此,如果您在使用scss / sass的angular2-cli时使用“ / deep /”而不是“ >>>”,则尚不支持最后一个选择器(但在CSS中效果很好)。


11

如果要比实际子组件更有针对性,则应执行以下操作。这样,如果其他子组件共享相同的类名,则它们不会受到影响。

柱塞:https://plnkr.co/edit/ooBRp3ROk6fbWPuToytO p = preview

例如:

import {Component, NgModule } from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>I'm the host parent</h2>
      <child-component class="target1"></child-component><br/>
      <child-component class="target2"></child-component><br/>
      <child-component class="target3"></child-component><br/>
      <child-component class="target4"></child-component><br/>
      <child-component></child-component><br/>
    </div>
  `,
  styles: [`

  /deep/ child-component.target1 .child-box {
      color: red !important; 
      border: 10px solid red !important;
  }  

  /deep/ child-component.target2 .child-box {
      color: purple !important; 
      border: 10px solid purple !important;
  }  

  /deep/ child-component.target3 .child-box {
      color: orange !important; 
      border: 10px solid orange !important;
  }  

  /* this won't work because the target component is spelled incorrectly */
  /deep/ xxxxchild-component.target4 .child-box {
      color: orange !important; 
      border: 10px solid orange !important;
  }  

  /* this will affect any component that has a class name called .child-box */
  /deep/ .child-box {
      color: blue !important; 
      border: 10px solid blue !important;
  }  


  `]
})
export class App {
}

@Component({
  selector: 'child-component',
  template: `
    <div class="child-box">
      Child: This is some text in a box
    </div>
  `,
  styles: [`
    .child-box {
      color: green;    
      border: 1px solid green;
    }
  `]
})
export class ChildComponent {
}


@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, ChildComponent ],
  bootstrap: [ App ]
})
export class AppModule {}

希望这可以帮助!

码矩阵


9

实际上,还有另一种选择。这比较安全。您可以使用ViewEncapsulation.Bone不会将所有组件样式放入其标签(也称为选择器)中。但是无论如何,总是喜欢一些全局样式以及封装样式。

这是修改后的Denis Rybalka示例:

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

@Component({
  selector: 'parent',
  styles: [`
    parent {
      .first {
        color:blue;
      }
      .second {
        color:red;
      }
    }
 `],
 template: `
    <div>
      <child class="first">First</child>
      <child class="second">Second</child>
    </div>`,
  encapsulation: ViewEncapsulation.None,
})
export class ParentComponent  {
  constructor() { }
}

7

在Angular中有一些实现此目的的选项:

1)您可以使用深CSS选择器

:host >>> .childrens {
     color: red;
 }

2)您也可以更改视图封装,将其默认设置为“模拟”,但可以轻松更改为使用Shadow DOM本机浏览器实现的本机,您只需要禁用它即可

例如:

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

@Component({
  selector: 'parent',
  styles: [`
    .first {
      color:blue;
    }
    .second {
      color:red;
    }
 `],
 template: `
    <div>
      <child class="first">First</child>
      <child class="second">Second</child>
    </div>`,
  encapsulation: ViewEncapsulation.None,
 })
 export class ParentComponent  {
   constructor() {

   }
 }

3
实际上,这意味着样式会影响整个dom,而不仅是子元素。
卡巴斯尔

7

您不应该为父组件中的子组件元素编写CSS规则,因为Angular组件是一个独立的实体,应该明确声明外部可用的内容。如果将来子布局发生更改,散布在其他组件的SCSS文件中的该子组件元素的样式可能会轻易中断,从而使您的样式非常脆弱。这ViewEncapsulation就是CSS的目的。否则,如果您可以将值分配给面向对象编程中任何其他类的某个类的私有字段,则将是相同的。

因此,您应该做的是定义可以应用于子级宿主元素的一组类,并实现子级对它们的响应方式。

从技术上讲,可以按照以下步骤进行:

// child.component.html:
<span class="label-1"></span>

// child.component.scss:
:host.child-color-black {
    .label-1 {
        color: black;
    }
}

:host.child-color-blue {
    .label-1 {
        color: blue ;
    }
}

// parent.component.html:
<child class="child-color-black"></child>
<child class="child-color-blue"></child>

换句话说,您可以使用:hostAngular + CSS类集提供的伪选择器在子组件本身中定义可能的子样式。然后,您可以通过将预定义的类应用于<child>宿主元素来从外部触发这些样式。


看起来是一个不错的解决方案,是否有一个parent.component.scss文件?如果是,请给它吗?
Manohar Reddy Poreddy

@ManoharReddyPoreddy在parent.component.scss与子组件的样式相关的样式中应该没有样式。这是此方法的唯一目的。为什么需要parent.component.scss
亚历山大·阿巴库莫夫

不确定,只知道一点CSS。您可以在jsbin或其他平台上共享完整的解决方案吗?您的解决方案可以成为每个人的未来解决方案。
Manohar Reddy Poreddy

2
@ManoharReddyPoreddy我建议您首先在实践中尝试这些代码。然后,如果您遇到任何问题,您将有一个特定的问题,我可以回答或提出建议以调查特定主题,以使您对如何解决问题有所了解。我ViewEncapsulation之所以提到它,是因为它的默认值是导致OP问题的原因。您无需ViewEncapsulation为上面的代码分配其他代码即可工作。
亚历山大·阿巴库莫夫

1
+1谢谢。将来会回来采用此解决方案,今天定为:: ng-deep stackoverflow.com/a/36528769/984471
Manohar Reddy Poreddy

5

我发现如果可以访问子组件代码,则传递@INPUT变量会更干净

这样的想法是,父母告诉孩子其外表状态应该是什么,然后孩子决定如何显示状态。这是一个不错的架构

SCSS方式:

.active {
  ::ng-deep md-list-item {
    background-color: #eee;
  }
}

更好的方法: -使用selected变量:

<md-list>
    <a
            *ngFor="let convo of conversations"
            routerLink="/conversations/{{convo.id}}/messages"
            #rla="routerLinkActive"
            routerLinkActive="active">
        <app-conversation
                [selected]="rla.isActive"
                [convo]="convo"></app-conversation>
    </a>
</md-list>

2
也很难维护,特别是对于递归组件。
Erik Philips

2

从今天(Angular 9)开始,Angular使用Shadow DOM将组件显示为自定义HTML元素。一种样式化这些自定义元素的优雅方法是使用自定义CSS变量。这是一个通用示例:

class ChildElement extends HTMLElement {
  constructor() {
    super();
    
    var shadow = this.attachShadow({mode: 'open'});
    var wrapper = document.createElement('div');
    wrapper.setAttribute('class', 'wrapper');
    
    // Create some CSS to apply to the shadow dom
    var style = document.createElement('style');
    
    style.textContent = `
    
      /* Here we define the default value for the variable --background-clr */
      :host {
        --background-clr: green;
      }
      
      .wrapper {
        width: 100px;
        height: 100px;
        background-color: var(--background-clr);
        border: 1px solid red;
      }
    `;
    
    shadow.appendChild(style);
    shadow.appendChild(wrapper);
  }
}

// Define the new element
customElements.define('child-element', ChildElement);
/* CSS CODE */

/* This element is referred as :host from the point of view of the custom element. Commenting out this CSS will result in the background to be green, as defined in the custom element */

child-element {
  --background-clr: yellow; 
}
<div>
  <child-element></child-element>
</div>

从上面的代码中可以看到,我们创建了一个自定义元素,就像Angular会为每个组件做的那样,然后我们从全局范围覆盖自定义元素的阴影根中负责背景颜色的变量。

在Angular应用中,可能类似于:

parent.component.scss

child-element {
  --background-clr: yellow;
}

child-element.component.scss

:host {
  --background-clr: green;
}

.wrapper {
  width: 100px;
  height: 100px;
  background-color: var(--background-clr);
  border: 1px solid red;
}

0

快速的答案是,您根本不应该这样做。它破坏了组件的封装,并破坏了您从独立组件获得的收益。考虑将prop标志传递给子组件,然后,如果需要,它可以自行决定如何进行不同的呈现或应用不同的CSS。

<parent>
  <child [foo]="bar"></child>
</parent>

Angular不赞成从父母那里影响孩子风格的所有方式。

https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep


好吧,他们已经在文档中明确表示他们最终会这样做,我想这意味着他们会这样做。我同意,但不会很快发生。
杰德·理查兹

因此,他们将使自己的材料库变得无用。由于每个客户都需要自己的设计,因此我从未在任何库中使用默认主题。通常,您只需要组件的功能。我不能说我理解他们在这项决定背后的总体逻辑。
Chrillewoodz

0

我也有这个问题,不想使用不推荐使用的解决方案,所以我最终得到了:

同时

 <dynamic-table
  ContainerCustomStyle='width: 400px;'
  >
 </dynamic-Table>

子组件

@Input() ContainerCustomStyle: string;

在html div中的子项中

 <div class="container mat-elevation-z8"
 [style]='GetStyle(ContainerCustomStyle)' >

和在代码中

constructor(private sanitizer: DomSanitizer) {  }

  GetStyle(c) {
    if (isNullOrUndefined(c)) { return null; }
    return  this.sanitizer.bypassSecurityTrustStyle(c);
  }

像预期的那样工作,不应弃用;)


有趣!我最终得到了类似的东西(暂时)。您从哪里获得DomSanitizer?编辑:找到它:angular.io/api/platform-b​​rowser/DomSanitizer
Zaphoid

是的,在v7中,它是本机的,您只需要在构造函数中请求将其注入即可。;),在更老的时候我不知道它是否存在-我从v7开始;)
d00lar

0

随着互联网的更新,我遇到了一个解决方案。

首先一些警告。

  1. 还是不要这样做。为了澄清,我不会计划允许您设置子组件样式的子组件。SOC。如果您作为组件设计者希望允许这样做,那么您将获得更大的权力。
  2. 如果您的孩子没有住在暗影之屋,那么这对您不起作用。
  3. 如果您必须支持没有影子dom的浏览器,那么这对您也不起作用。

首先,将子组件的封装标记为阴影,以便在实际的阴影dom中进行渲染。其次,将part属性添加到希望允许父元素样式化的元素上。在您父母的组件样式表中,您可以使用:: part()方法访问


-1

我提出了一个示例,以使其更加清晰,因为angular.io/guide/component-styles指出:

不推荐使用穿刺阴影的后代组合器,并且从主要的浏览器和工具中删除了对它的支持。因此,我们计划放弃对Angular的支持(针对/ deep /,>>>和:: ng-deep的全部3个)。在此之前,应首选:: ng-deep以获得与工具的广泛兼容性。

在上app.component.scss*.scss根据需要导入。_colors.scss具有一些常见的颜色值:

$button_ripple_red: #A41E34;
$button_ripple_white_text: #FFF;

将规则应用于所有组件

所有具有btn-red类的按钮将被设置样式。

@import `./theme/sass/_colors`;

// red background and white text
:host /deep/ button.red-btn {
    color: $button_ripple_white_text;
    background: $button_ripple_red;
}

将规则应用于单个组件

所有btn-redapp-login组件上具有类的按钮将被设置样式。

@import `./theme/sass/_colors`;

/deep/ app-login button.red-btn {
    color: $button_ripple_white_text;
    background: $button_ripple_red;
}

-1

我已经在Angular之外解决了。我已经定义了要导入到我的孩子们的共享scss。

共享的

%cell {
  color: #333333;
  background: #eee;
  font-size: 13px;
  font-weight: 600;
}

child.scss

@import 'styles.scss';
.cell {
  @extend %cell;
}

我提出的方法是一种解决OP所问问题的方法。正如我多次提到的,:: ng-deep,:ng-host会被弃用,并且禁用封装在我看来是太多的代码泄漏。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.