How to style child components from parent componen

2020-01-22 13:54发布

问题:

I've got a parent component:

<parent></parent>

And I want to populate this group with child components:

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

Parent template:

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

Child template:

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

Since parent and child are two seperate components, their styles are locked to their own scope.

In my parent component I tried doing:

.parent .child {
  // Styles for child
}

But the .child styles are not getting applied to the child components.

I tried using styleUrls to include the parent's stylesheet into child component to solve the scope issue:

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

But that didn't help, also tried the other way by fetching the child stylesheet into parent but that didn't help either.

So how do you style child components that are included into a parent component?

回答1:

Update - Newest Way

Don't do it, if you can avoid it. As Devon Sans points out in the comments: This feature will most likely be deprecated.

Update - Newer Way

From Angular 4.3.0, all piercing css combinartors were deprecated. Angular team introduced a new combinator ::ng-deep (still it is at experimental level and not the full and final way) as shown below,

DEMO : 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>      
    `


Old way

You can use encapsulation mode and/or piercing CSS combinators >>>, /deep/ and ::shadow

working example : 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>      
`


回答2:

UPDATE 3:

::ng-deep is also deprecated which means you should not do this at all anymore. It is unclear how this affects things where you need to override styles in child components from a parent component. To me it seems odd if this gets removed completely because how would this affect things as libraries where you need to override styles in a library component?

Comment if you have any insight in this.

UPDATE 2:

Since /deep/ and all other shadow piercing selectors are now deprecated. Angular dropped ::ng-deep which should be used instead for a broader compatibility.

UPDATE:

If using Angular-CLI you need to use /deep/ instead of >>> or else it will not work.

ORIGINAL:

After going to Angular2's Github page and doing a random search for "style" I found this question: Angular 2 - innerHTML styling

Which said to use something that was added in 2.0.0-beta.10, the >>> and ::shadow selectors.

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

So simply doing:

:host >>> .child {}

In parent's stylesheet file solved the issue. Please note, as stated in the quote above, this solution is only intermediate until more advanced cross-component styling is supported.



回答3:

If you don't want to use ::ng-deep, you can do this which seems to be the proper way:

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

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

And then, you will be able to modify the css form your component without a need from ::ng-deep

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

WARNING: Doing this will make all css rules you write for this component to be global.

In order to limit the scope of your css to this component only, add a css class to the top tag of your component and put your css "inside" this tag:

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

Scss file:

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


回答4:

Had same issue, so if you're using angular2-cli with scss/sass use '/deep/' instead of '>>>', last selector isn't supported yet (but works great with css).



回答5:

Sadly it appears that the /deep/ selector is deprecated (at least in Chrome) https://www.chromestatus.com/features/6750456638341120

In short it appears there is (currently) no long term solution other than to somehow get your child component to style things dynamically.

You could pass a style object to your child and have it applied via:
<div [attr.style]="styleobject">

Or if you have a specific style you can use something like:
<div [style.background-color]="colorvar">

More discussion related to this: https://github.com/angular/angular/issues/6511



回答6:

If you want to be more targeted to the actual child component than you should do the follow. This way, if other child components share the same class name, they won't be affected.

Plunker: https://plnkr.co/edit/ooBRp3ROk6fbWPuToytO?p=preview

For example:

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

Hope this helps!

codematrix



回答7:

There are a few options to achieve this in Angular:

1) You can use deep css selectors

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

2) You can also change view encapsulation it's set to Emulated as a default but can be easily changed to Native which uses Shadow DOM native browser implementation, in your case you just need to disable it

For example:`

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

   }
 }


回答8:

Actually there is one more option. Which is rather safe. You can use ViewEncapsulation.None BUT put all your component styles into its tag (aka selector). But anyway always prefer some global style plus encapsulated styles.

Here is modified Denis Rybalka example:

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


回答9:

You should not write CSS rules for a child component elements in a parent component, since an Angular component is a self-contained entity which should explicitly declare what is available for the outside world. If child layout changes in the future, your styles for that child component elements scattered across other components' SCSS files could easily break, thus making your styling very fragile. That's what ViewEncapsulation is for in the case of CSS. Otherwise, it would be the same if you could assign values to private fields of some class from any other class in Object Oriented Programming.

Therefore, what you should do is to define a set of classes you could apply to the child host element and implement how the child responds to them.

Technically, it could be done as follows:

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

In other words, you use :host pseudo-selector provided by Angular + set of CSS classes to define possible child styles in child component itself. You then have the ability to trigger those styles from outside by applying pre-defined classes to the <child> host element.



回答10:

I find it a lot cleaner to pass an @INPUT variable if you have access to the child component code:

The idea is that the parent tells the child what its state of appearance should be, and the child decides how to display the state. It's a nice architecture

SCSS Way:

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

Better way: - use selected variable:

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


回答11:

The quick answer is you shouldn't be doing this, at all. It breaks component encapsulation and undermines the benefit you're getting from self-contained components. Consider passing a prop flag to the child component, it can then decide itself how to render differently or apply different CSS, if necessary.

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

Angular is deprecating all ways of affecting child styles from parents.

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



回答12:

i also had this problem and didnt wanted to use deprecated solution so i ended up with:

in parrent

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

child component

@Input() ContainerCustomStyle: string;

in child in html div

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

and in code

constructor(private sanitizer: DomSanitizer) {  }

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

works like expected and should not be deprecated ;)



回答13:

I propose an example to make it more clear, since angular.io/guide/component-styles states:

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.

On app.component.scss, import your *.scss if needed. _colors.scss has some common color values:

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

Apply a rule to all components

All the buttons having btn-red class will be styled.

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

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

Apply a rule to a single component

All the buttons having btn-red class on app-login component will be styled.

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

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


回答14:

I have solved it outside Angular. I have defined a shared scss that I'm importing to my children.

shared.scss

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

child.scss

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

My proposed approach is a way how to solve the problem the OP has asked about. As mentioned at multiple occasions, ::ng-deep, :ng-host will get depreciated and disabling encapsulation is just too much of a code leakage, in my view.