Comment styliser les composants enfants à partir du fichier CSS du composant parent?

266

J'ai un composant parent:

<parent></parent>

Et je veux remplir ce groupe avec des composants enfants:

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

Modèle parent:

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

Modèle enfant:

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

Puisque parentet childsont deux composants séparés, leurs styles sont verrouillés à leur propre portée.

Dans mon composant parent, j'ai essayé de faire:

.parent .child {
  // Styles for child
}

Mais les .childstyles ne sont pas appliqués aux childcomposants.

J'ai essayé d'utiliser styleUrlspour inclure la parentfeuille de style de dans le childcomposant pour résoudre le problème de portée:

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

Mais cela n'a pas aidé, a également essayé dans l'autre sens en récupérant la childfeuille de style dans parentmais cela n'a pas aidé non plus.

Alors, comment stylisez-vous les composants enfants qui sont inclus dans un composant parent?

Chrillewoodz
la source
1
Voir aussi stackoverflow.com/questions/34542143/…
Günter Zöchbauer
Voir une manière complètement paradigmatique, sans truc dans ma réponse .
Alexander Abakumov

Réponses:

242

Mise à jour - Nouvelle façon

Ne le faites pas, si vous pouvez l'éviter. Comme le souligne Devon Sans dans les commentaires: Cette fonctionnalité sera très probablement obsolète.

Mise à jour - Nouvelle façon

À partir d' Angular 4.3.0 , tous les combinartors piercing css ont été déconseillés. L'équipe angulaire a introduit un nouveau combinateur ::ng-deep (toujours au niveau expérimental et non pas de manière complète et finale) comme indiqué ci-dessous,

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


À l'ancienne

Vous pouvez utiliser encapsulation modeet / oupiercing CSS combinators >>>, /deep/ and ::shadow

exemple de travail: 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>      
`
micronyks
la source
3
Les combinateurs CSS perçants sont obsolètes dans Chrome
Robin-Hoodie
22
L'équipe angulaire prévoit également de supprimer le support de :: ng-deep. De leurs documents: "Le combinateur descendant qui perce les ombres est déconseillé et le support est supprimé des principaux navigateurs et outils. En tant que tel, nous prévoyons de supprimer le support dans Angular (pour les 3 de / deep /, >>> et :: ng- Jusque-là :: ng-deep devrait être préféré pour une compatibilité plus large avec les outils. " angular.io/guide/component-styles#deprecated-deep--and-ng-deep .
Devon Sams
5
Tant que cela reste une réponse acceptée, les gens seront induits en erreur. :: ng-deep ne doit pas être utilisé comme points @DevonSams dans le commentaire ci-dessus.
Kostas Siabanis
1
::ng-deepest désormais obsolète , je ne recommande pas de l'utiliser dans de futures applications
Wilt
11
Déprécier quelque chose sans fournir d'alternative n'est probablement pas la meilleure solution.
tehlivi
56

MISE À JOUR 3:

::ng-deepest également obsolète, ce qui signifie que vous ne devriez plus faire cela du tout. Il n'est pas clair comment cela affecte les choses où vous devez remplacer les styles dans les composants enfants à partir d'un composant parent. Pour moi, il semble étrange que cela soit complètement supprimé, car comment cela affecterait-il les choses en tant que bibliothèques où vous devez remplacer les styles dans un composant de bibliothèque?

Commentez si vous avez une idée à ce sujet.

MISE À JOUR 2:

Depuis /deep/et tous les autres sélecteurs de perforation d'ombre sont désormais obsolètes. Angular drop ::ng-deepqui devrait être utilisé à la place pour une compatibilité plus large.

METTRE À JOUR:

Si vous utilisez Angular-CLI, vous devez utiliser à la /deep/place de >>>sinon, cela ne fonctionnera pas.

ORIGINAL:

Après être allé sur la page Github d'Angular2 et faire une recherche aléatoire de "style", j'ai trouvé cette question: Angular 2 - style innerHTML

Qui a dit d'utiliser quelque chose qui a été ajouté 2.0.0-beta.10, les sélecteurs >>>et ::shadow.

(>>>) (et l'équivalent / deep /) et :: shadow ont été ajoutés dans 2.0.0-beta.10. Ils sont similaires aux combinateurs CSS DOM DOM (qui sont obsolètes) et ne fonctionnent qu'avec l'encapsulation: ViewEncapsulation.Emulated, qui est la valeur par défaut dans Angular2. Ils fonctionnent probablement aussi avec ViewEncapsulation.None mais ne sont alors ignorés que parce qu'ils ne sont pas nécessaires. Ces combinateurs ne sont qu'une solution intermédiaire jusqu'à ce que des fonctionnalités plus avancées pour le style inter-composants soient prises en charge.

Donc, tout simplement:

:host >>> .child {}

Dans parentle fichier de feuille de style » résolu le problème. Veuillez noter, comme indiqué dans la citation ci-dessus, que cette solution n'est qu'intermédiaire jusqu'à ce que le style croisé plus avancé soit pris en charge.

Chrillewoodz
la source
On dirait qu'ils vont supprimer le support de :: ng-deep angular.io/guide/component-styles#deprecated-deep--and-ng-deep
Jed Richards
42

Vous ne devez PAS l'utiliser ::ng-deep, il est obsolète. Dans Angular, la bonne façon de changer le style du composant enfant du parent est d'utiliser encapsulation(lisez l'avertissement ci-dessous pour comprendre les implications):

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

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

Et puis, vous pourrez modifier la forme css de votre composant sans avoir besoin de :: ng-deep

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

AVERTISSEMENT: cette opération rendra toutes les règles CSS que vous écrivez pour ce composant globales.

Afin de limiter la portée de votre css à ce composant uniquement, ajoutez une classe css à la balise supérieure de votre composant et placez votre css "à l'intérieur" de cette balise:

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

Fichier SCSS:

.my-component {
  // All your css goes in there in order not to be global
}
Tonio
la source
3
C'est la meilleure réponse de l'OMI, car il s'agit en fait d'une alternative viable à celle qui sera bientôt déconseillée ::ng-deep. En général, les composants ont de toute façon leur propre sélecteur ( <my-component>, <div my-component>, etc.), il n'y a donc même pas besoin d'un élément wrapper avec une classe spéciale.
Alex Walker
@AlexWalker Cela pourrait être la meilleure réponse à votre situation, mais il convient de mentionner qu'elle ne répond qu'à la moitié de la question du PO afaict: Cette méthode permet à CSS de se propager normalement de haut en bas, mais, en vertu de jeter TOUTE l'encapsulation, ne fait pas 'limite ce style aux enfants d'un parent spécifique . Si vous stylisez les enfants de parent1 dans un sens et les enfants de parent2 dans un autre, ces règles CSS s'affronteront désormais aux deux endroits. Cela peut être extrêmement douloureux (et une encapsulation angulaire supplémentaire pour l'éviter).
ruffin
@ruffin C'est exactement pourquoi j'ai ajouté l'avertissement dans ma réponse pour comprendre l'implication de l'utilisation de cette technique et comment "encapsuler manuellement" en utilisant une balise CSS supérieure sur votre composant
Tonio
1
@Tonio - Oui, d'accord; répondait directement à Alex plutôt qu'à vous. Son commentaire, " donc il n'y a même pas besoin d'un élément wrapper avec une classe spéciale " m'a un peu effrayé. Peut-être pour une situation spécifique , mais il y a une raison pour laquelle "le temps perdu" angulaire prend en charge l'encapsulation. Cette réponse est une solution réalisable dans des cas spécifiques, mais, comme vous le dites, elle est potentiellement dangereuse en général. La solution de MatthewB , par exemple, stylise les enfants tout en conservant l'encapsulation (mais cela devient vraiment compliqué si vous avez plus d'une génération de composants enfants).
ruffin
19

Malheureusement, il semble que le sélecteur / deep / soit obsolète (au moins dans Chrome) https://www.chromestatus.com/features/6750456638341120

En bref, il semble qu'il n'y ait (actuellement) aucune solution à long terme autre que d'obtenir en quelque sorte votre composant enfant pour styliser les choses de manière dynamique.

Vous pouvez passer un objet de style à votre enfant et le faire appliquer via:
<div [attr.style]="styleobject">

Ou si vous avez un style spécifique, vous pouvez utiliser quelque chose comme:
<div [style.background-color]="colorvar">

Plus de discussion à ce sujet: https://github.com/angular/angular/issues/6511

Matthew B.
la source
16

Nous avons eu le même problème, donc si vous utilisez angular2-cli avec scss / sass, utilisez '/ deep /' au lieu de '>>>', le dernier sélecteur n'est pas encore pris en charge (mais fonctionne très bien avec css).

SergiySev
la source
11

Si vous souhaitez être plus ciblé sur le composant enfant réel que vous devez le faire, procédez comme suit. De cette façon, si d'autres composants enfants partagent le même nom de classe, ils ne seront pas affectés.

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

Par exemple:

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

J'espère que cela t'aides!

codematrix

code5
la source
9

En fait, il y a une autre option. Ce qui est plutôt sûr. Vous pouvez utiliser ViewEncapsulation.None MAIS mettre tous vos styles de composants dans sa balise (aka sélecteur). Mais de toute façon, préférez toujours un style global et des styles encapsulés.

Voici l'exemple modifié de 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() { }
}
ilius33
la source
7

Il existe quelques options pour y parvenir dans Angular:

1) Vous pouvez utiliser des sélecteurs CSS profonds

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

2) Vous pouvez également modifier l'encapsulation de la vue, elle est définie sur Emulated par défaut, mais peut être facilement remplacée par Native qui utilise l'implémentation native du navigateur Shadow DOM, dans votre cas, il vous suffit de la désactiver

Par exemple: `

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

   }
 }
Denis Rybalka
la source
3
En fait, cela signifie que les styles affectent l'ensemble du dom, pas seulement les éléments enfants.
Kasper Ziemianek du
7

Vous ne devez pas écrire de règles CSS pour les éléments d'un composant enfant dans un composant parent, car un composant angulaire est une entité autonome qui doit déclarer explicitement ce qui est disponible pour le monde extérieur. Si la disposition des enfants change à l'avenir, vos styles pour ces éléments de composants enfants dispersés dans les fichiers SCSS des autres composants pourraient facilement se casser, rendant ainsi votre style très fragile. C'est à cela que ViewEncapsulationsert le CSS. Sinon, ce serait la même chose si vous pouviez attribuer des valeurs aux champs privés d'une classe de n'importe quelle autre classe en programmation orientée objet.

Par conséquent, ce que vous devez faire est de définir un ensemble de classes que vous pourriez appliquer à l'élément hôte enfant et d'implémenter la façon dont l'enfant y répond.

Techniquement, cela pourrait se faire comme suit:

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

En d'autres termes, vous utilisez le :hostpseudo-sélecteur fourni par Angular + ensemble de classes CSS pour définir les styles enfants possibles dans le composant enfant lui-même. Vous avez ensuite la possibilité de déclencher ces styles de l'extérieur en appliquant des classes prédéfinies à l' <child>élément hôte.

Alexander Abakumov
la source
Cela ressemble à une bonne solution, existe-t-il un fichier parent.component.scss? si oui, voulez-vous le donner?
Manohar Reddy Poreddy
@ManoharReddyPoreddy Il ne doit pas y avoir de style parent.component.scsslié au style du composant enfant. C'est le seul but de cette approche. Pourquoi as-tu besoin parent.component.scss?
Alexander Abakumov
Pas sûr, sachez juste un peu de CSS. Pouvez-vous partager une solution complète sur jsbin, ou autre. Votre solution peut être une solution d'avenir pour tout le monde.
Manohar Reddy Poreddy
2
@ManoharReddyPoreddy Je vous suggère d'essayer d'abord ces morceaux de code dans la pratique. Ensuite, si vous rencontriez des problèmes, vous auriez une question spécifique à laquelle je pourrais répondre ou des conseils pour examiner un sujet spécifique afin de comprendre comment résoudre votre problème. J'ai mentionné ViewEncapsulationjuste parce que sa valeur par défaut est ce qui conduit à la question OP. Vous n'avez pas besoin d'attribuer un autre ViewEncapsulationpour que le code ci-dessus fonctionne.
Alexander Abakumov
1
+1 Merci. Reviendra pour prendre cette solution à l'avenir, réglé pour :: ng-deep stackoverflow.com/a/36528769/984471 pour aujourd'hui.
Manohar Reddy Poreddy
5

Je trouve beaucoup plus propre de passer une variable @INPUT si vous avez accès au code du composant enfant:

L'idée est que le parent indique à l'enfant quel doit être son état d'apparence et que l'enfant décide comment afficher l'état. C'est une belle architecture

SCSS Way:

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

Meilleure façon: - utilisez la selectedvariable:

<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>
Robert King
la source
2
Également difficile à entretenir, en particulier pour les composants récursifs.
Erik Philips
2

À ce jour (Angular 9), Angular utilise un DOM Shadow pour afficher les composants en tant qu'éléments HTML personnalisés . Une manière élégante de styliser ces éléments personnalisés peut être d'utiliser des variables CSS personnalisées . Voici un exemple générique:

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>

Comme nous pouvons le voir dans le code ci-dessus, nous créons un élément personnalisé, tout comme Angular le ferait pour nous avec chaque composant, puis nous remplaçons la variable responsable de la couleur d'arrière-plan dans la racine d'ombre de l'élément personnalisé, à partir de la portée globale .

Dans une application angulaire, cela pourrait ressembler à:

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;
}
vivanov
la source
0

La réponse rapide est que vous ne devriez pas faire ça du tout. Il rompt l'encapsulation des composants et compromet les avantages que vous obtenez des composants autonomes. Pensez à passer un indicateur prop au composant enfant, il peut alors décider lui-même comment effectuer un rendu différent ou appliquer différents CSS, si nécessaire.

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

Angular déprécie toutes les façons d'affecter les styles d'enfants des parents.

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

Jed Richards
la source
Eh bien, ils ont dit explicitement dans leurs documents qu'ils le font éventuellement, ce qui signifie que je le pense. Je suis d'accord cependant, cela n'arrivera pas de sitôt.
Jed Richards
Ils rendront donc à peu près inutile leur propre bibliothèque de matériaux. Je n'ai jamais pu utiliser un thème par défaut dans une bibliothèque car chaque client a besoin de sa propre conception. Habituellement, vous voulez juste la fonctionnalité d'un composant. Je ne peux pas dire que je comprends leur logique globale derrière cette décision.
Chrillewoodz
0

j'ai également eu ce problème et je ne voulais pas utiliser une solution obsolète, donc je me suis retrouvé avec:

en parallèle

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

composant enfant

@Input() ContainerCustomStyle: string;

chez l'enfant en div html

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

et en code

constructor(private sanitizer: DomSanitizer) {  }

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

fonctionne comme prévu et ne doit pas être déconseillé;)

d00lar
la source
Intéressant! Je me suis retrouvé avec quelque chose de similaire (pour l'instant). D'où obtenez-vous DomSanitizer? Edit: Trouvé: angular.io/api/platform-browser/DomSanitizer
Zaphoid
yep en v7 il est natif il suffit d'en demander l'injection dans le constructeur. ;), dans les anciens je n'ai aucune idée s'il existait - j'ai commencé à partir de la v7;)
d00lar
0

Au fur et à mesure des mises à jour sur Internet, je suis tombé sur une solution.

D'abord quelques mises en garde.

  1. Ne le fais toujours pas. Pour clarifier, je ne prévois pas de composants enfants vous permettant de les styliser. SOC. Si vous, en tant que concepteur de composants, souhaitez autoriser cela, vous avez d'autant plus de pouvoir.
  2. Si votre enfant ne vit pas dans le dom de l'ombre, cela ne fonctionnera pas pour vous.
  3. Si vous devez prendre en charge un navigateur qui ne peut pas avoir de shadow dom, cela ne fonctionnera pas non plus pour vous.

Tout d'abord, marquez l'encapsulation de votre composant enfant comme ombre afin qu'elle soit rendue dans le dom réel de l'ombre. Ensuite, ajoutez l'attribut de pièce à l'élément que vous souhaitez autoriser au parent à styliser. Dans la feuille de style des composants de votre parent, vous pouvez utiliser la méthode :: part () pour accéder

Lumineux
la source
-1

Je propose un exemple pour le rendre plus clair, car angular.io/guide/component-styles déclare:

Le combinateur descendant qui perce les ombres est obsolète et le support est supprimé des principaux navigateurs et outils. En tant que tel, nous prévoyons de supprimer le support dans Angular (pour tous les 3 de / deep /, >>> et :: ng-deep). Jusque-là :: ng-deep devrait être préféré pour une compatibilité plus large avec les outils.

Le app.component.scss, importez votre *.scsssi nécessaire. _colors.scssa des valeurs de couleur communes:

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

Appliquer une règle à tous les composants

Tous les boutons ayant de la btn-redclasse seront stylisés.

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

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

Appliquer une règle à un seul composant

Tous les boutons ayant une btn-redclasse sur le app-logincomposant seront stylisés.

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

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

Je l'ai résolu en dehors d'Angular. J'ai défini un scss partagé que j'importe à mes enfants.

shared.scss

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

child.scss

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

L'approche que je propose est un moyen de résoudre le problème posé par le PO. Comme mentionné à plusieurs reprises, :: ng-deep,: ng-host sera déprécié et la désactivation de l'encapsulation est tout simplement trop une fuite de code, à mon avis.

Jakub
la source