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

How to style child components from parent component's CSS file?

我有一个父组件:

<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 中,但这也没有帮助。

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

更新 3:

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

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

更新 2:

因为 /deep/ 和所有其他阴影穿刺选择器现已弃用。 Angular 删除了 ::ng-deep,应该改用它以获得更广泛的兼容性。

更新:

如果使用 Angular-CLI,您需要使用 /deep/ 而不是 >>>,否则它将无法工作。

原版:

进入 Angular2 的 Github 页面并随机搜索 "style" 后,我发现了这个问题:

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

(>>>) (and the equivalent/deep/) and ::shadow were added in 2.0.0-beta.10. They are similar to the shadow DOM CSS combinators (which are deprecated) and only work with encapsulation: ViewEncapsulation.Emulated which is the default in Angular2. They probably also work with ViewEncapsulation.None but are then only ignored because they are not necessary. These combinators are only an intermediate solution until more advanced features for cross-component styling is supported.

所以简单地做:

:host >>> .child {}

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

更新 - 最新方式

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

最后更新

Angular 4.3.0 到现在 (Angular 12.x),所有穿孔 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 and/or 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>      
`

有同样的问题,所以如果你使用 angular2-cli scss/sass 使用'/deep/'而不是'>>>',最后一个选择器还不受支持(但与css).

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

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

您可以将样式 object 传递给您的 child 并通过以下方式应用它:
<div [attr.style]="styleobject">

或者,如果你有特定的风格,你可以使用类似的东西:
<div [style.background-color]="colorvar">

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

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

笨蛋: 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 {}

希望对您有所帮助!

代码矩阵

在 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() {

   }
 }

我举一个例子来说明得更清楚,因为 angular.io/guide/component-styles 指出:

The shadow-piercing descendant combinator is deprecated and support is being removed from major browsers and tools. As such we plan to drop support in Angular (for all 3 of /deep/, >>> and ::ng-deep). Until then ::ng-deep should be preferred for a broader compatibility with the tools.

app.component.scss 上,如果需要,请导入您的 *.scss_colors.scss有一些常见的颜色值:

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

将规则应用于所有组件

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

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

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

将规则应用于单个组件

app-login 组件上具有 btn-red class 的所有按钮都将设置样式。

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

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

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

思路是,parent告诉child它的出现状态应该是什么,而child决定如何显示状态。这是一个不错的架构

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>

快速回答是你根本不应该这样做。它破坏了组件封装并破坏了您从 self-contained 组件中获得的好处。考虑将 prop 标志传递给 child 组件,然后它可以自行决定如何以不同方式呈现或应用不同 CSS,如有必要。

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

Angular 弃用所有影响 child 样式的方式 parents.

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

您不应使用 ::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 class 添加到组件的顶部标记并将您的 css 此标签“内部”:

模板:

<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
}

您不应该为父组件中的子组件元素编写 CSS 规则,因为 Angular 组件是一个自包含的实体,应该明确声明外部世界可用的内容.如果将来子布局发生变化,您散布在其他组件的 SCSS 文件中的子组件元素的样式很容易被破坏,从而使您的样式非常脆弱。这就是 ViewEncapsulation 在 CSS 中的作用。否则,如果您可以在面向对象编程中将值分配给来自任何其他 class 的某些 class 的私有字段,那将是相同的。

因此,您应该做的是定义一组 classes 可以应用于子宿主元素并实现子元素如何响应它们。

从技术上讲,可以按如下方式完成:

// 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>

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

其实还有一种选择。这是相当安全的。您可以使用 ViewEncapsulation.None 但将所有组件样式放入其标签(又名选择器)中。不过反正总是喜欢一些全局样式加上封装样式。

这里是修改过的 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() { }
}

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

父母

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

child 分量

@Input() ContainerCustomStyle: string;

在 child 在 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);
  }

按预期工作,不应弃用 ;)

我在外面解决了Angular。我已经定义了一个共享的 scss,我正在导入到我的 children.

shared.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 会贬值,在我看来,禁用封装会造成太多代码泄漏。

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

首先是一些注意事项。

  1. 还是别做了。澄清一下,我不打算让 child 组件允许您设置它们的样式。 SOC。如果您作为组件设计者想要允许这样做,那么您将拥有更多的权力。
  2. 如果你的 child 没有生活在阴影中 dom 那么这对你不起作用。
  3. 如果您必须支持不能有阴影的浏览器 dom 那么这也不适合您。

首先,将 child 组件的封装标记为阴影,以便它在实际阴影中呈现 dom。其次,将 part 属性添加到您希望允许 parent 设置样式的元素。在 parent 的组件样式表中,您可以使用 ::part() 方法访问

截至今天 (Angular 9),Angular 使用 Shadow DOM to display the components as custom HTML elements. One elegant way to style those custom elements might be using custom CSS variables。这是一个通用示例:

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

要在 child 组件中分配元素的 class,您只需在 child 的组件中使用 @Input 字符串并将其用作内部表达式模板。这是我们在共享 Bootstrap 加载按钮组件中更改图标和按钮类型的示例,而不影响它在整个代码库中的使用方式:

app-loading-button.component.html (child)

<button class="btn {{additionalClasses}}">...</button>

app-loading-button.component.ts

@Input() additionalClasses: string;

parent.html

<app-loading-button additionalClasses="fa fa-download btn-secondary">...</app-loading-button>

设'parent'为parent的class-name,'child'为child

的class-name
.parent .child{
//css definition for child inside parent components
} 

您可以使用此格式将 CSS 格式定义为 'parent'

内的 'child' 组件

因为 /deep/、>>> 和 ::ng-deep 都已弃用。 最好的方法是在子组件样式中使用以下内容

:host-context(.theme-light) h2 {
  background-color: #eef;
}

这将在您的子组件的任何祖先中查找 theme-light。 请在此处查看文档:https://angular.io/guide/component-styles#host-context

我更喜欢实现以下目标:

使用@Component 将css class 添加到宿主元素并将封装设置为none。然后在组件 style.css.scss 中引用添加到主机的 class 这将允许我们声明只会影响我们自己和我们 class 范围内的 children 的样式. f.e.

@Component({
  selector: 'my-component',
  templateUrl: './my-component.page.html',
  styleUrls: ['./my-component.page.scss'],
  host: {
    class: 'my-component-class'
  },
  encapsulation: ViewEncapsulation.None
})

结合以下 css (my-component.page.scss)

// refer ourselves so we are allowed to overwrite children but not global styles
.my-component-class {
  // will effect direct h1 nodes within template and all h1 elements within child components of the 
  h1 {
    color: red;
  }
}
// without class "scope" will affect all h1 elements globally
h1 {
  color: blue;
}

这是一个只有香草 css 的解决方案,没什么特别的,你甚至不需要 !important。我假设你不能修改child,否则答案更简单,我把那个答案放在最后以防万一。

使用库中的 pre-made 组件时,有时需要覆盖 child 的 CSS,并且开发人员未提供任何 class 输入变量。 ::ng-deep 已弃用,encapsulation: ViewEncapsulation.None 将所有组件的 CSS 变为全局。所以这是一个不使用这些的简单解决方案。

事实是,我们确实需要全局样式才能使 CSS 达到 child。因此,我们可以只将样式放在 styles.css 中,或者我们可以创建一个新的 CSS 文件并将其添加到 angular.json 中的 styles 数组中。唯一的问题是我们需要一个特定的选择器,以便不针对其他元素。这是一个非常简单的解决方案 - 只需将唯一的 class 名称添加到 html,我建议在 class 名称中使用 parent 组件的名称以确保它是唯一的。

Parent 组件

<child class="child-in-parent-component"></child>

假设我们想要更改 child 中所有按钮的 background-color,我们确实需要实现正确的特异性以确保我们的样式优先。我们可以在所有属性旁边使用 !important 来做到这一点,但更好的方法是重复 class 名称,直到我们的选择器足够具体,可能需要尝试几次。这样,其他人可以在必要时再次覆盖此 css。

全局样式文件

.child-in-parent-component.child-in-parent-component.child-in-parent-component
  button {
  background-color: red;
}

或者使用 !important 又快又脏(不推荐)

.child-in-parent-component button {
  background-color: red !important;
}

如果child组件可以修改

只需向组件添加一个输入变量并使用 Angular 的 ngStyle 指令。您可以添加多个变量来为组件的多个区域设置样式。

Child 组件

type klass = { [prop: string]: any } | null;

@Component({...})
export class ChildComponent {
  @Input() containerClass: klass = null;
  @Input() pClass: klass = null;
...
}
<div [ngStyle]="containerClass">
  <p [ngStyle]="pClass">What color will I be?</p>
</div>

Parent 组件

<child
  [containerClass]="{ padding: '20px', 'background-color': 'black' }"
  [pClass]="{ color: 'red' }"
>
</child>

这是创建具有动态样式的组件的预期方式。许多 pre-made 组件将具有相似的输入变量。