Como estilizar componentes filho do arquivo CSS do componente pai?

266

Eu tenho um componente pai:

<parent></parent>

E eu quero preencher esse grupo com componentes filhos:

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

Modelo principal:

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

Modelo filho:

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

Como parente childsão dois componentes separados, seus estilos são bloqueados para seu próprio escopo.

No meu componente pai, tentei fazer:

.parent .child {
  // Styles for child
}

Mas os .childestilos não estão sendo aplicados aos childcomponentes.

Tentei usar styleUrlspara incluir a parentfolha de estilo no childcomponente para resolver o problema de escopo:

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

Mas isso não ajudou, também tentou o contrário, buscando a childfolha de estilo, parentmas também não ajudou.

Então, como você estiliza componentes filhos incluídos em um componente pai?

Chrillewoodz
fonte
1
Veja também stackoverflow.com/questions/34542143/…
Günter Zöchbauer 7/16
Veja uma maneira completamente livre de truques e paradigmas na minha resposta .
Alexander Abakumov

Respostas:

242

Atualização - A maneira mais nova

Não faça isso, se puder evitá-lo. Como Devon Sans aponta nos comentários: Esse recurso provavelmente será preterido.

Atualização - Maneira mais recente

No Angular 4.3.0 , todos os combinadores piercing css foram preteridos. A equipe angular introduziu um novo combinador ::ng-deep (ainda está no nível experimental e não no caminho completo e final), como mostrado abaixo,

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


À moda antiga

Você pode usar encapsulation modee / oupiercing CSS combinators >>>, /deep/ and ::shadow

exemplo de trabalho: 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
fonte
3
Os combinadores Piercing CSS estão obsoletos no Chrome
Robin-Hoodie
22
A equipe angular planeja abandonar o suporte ao :: ng-deep também. De seus documentos: "O combinador descendente de penetração de sombras foi descontinuado e o suporte está sendo removido dos principais navegadores e ferramentas. Como tal, planejamos descartar o suporte no Angular (para todos os 3 de / deep /, >>> e :: ng- Até então, o :: ng-deep deve ser preferido para uma compatibilidade mais ampla com as ferramentas. " angular.io/guide/component-styles#deprecated-deep--and-ng-deep .
precisa
5
Enquanto isso permanecer como uma resposta aceita, as pessoas serão enganadas. :: ng-deep não deve ser usado como pontos @DevonSams no comentário acima.
Kostas Siabanis
1
::ng-deepagora está obsoleto , não recomendo usá-lo em aplicativos futuros
Wilt
11
Preterir algo sem fornecer uma alternativa provavelmente não é a melhor solução.
tehlivi 14/02
56

ATUALIZAÇÃO 3:

::ng-deeptambém foi descontinuado, o que significa que você não deve mais fazer isso. Não está claro como isso afeta as coisas nas quais você precisa substituir estilos em componentes filhos de um componente pai. Para mim, parece estranho se isso for removido completamente, porque como isso afetaria coisas como bibliotecas nas quais você precisa substituir estilos em um componente de biblioteca?

Comente se você tem alguma idéia disso.

ATUALIZAÇÃO 2:

Desde então /deep/e todos os outros seletores de piercing de sombra agora estão obsoletos. Descarga angular ::ng-deepque deve ser usada para uma compatibilidade mais ampla.

ATUALIZAR:

Se estiver usando Angular-CLI, você precisará usar em /deep/vez de >>>ou não funcionará.

ORIGINAL:

Depois de ir para a página Github do Angular2 e fazer uma pesquisa aleatória por "estilo", encontrei a seguinte pergunta: Angular 2 - estilo innerHTML

Que dizia usar algo que foi adicionado 2.0.0-beta.10, os seletores >>>e ::shadow.

(>>>) (e o equivalente / deep /) e :: shadow foram adicionados no 2.0.0-beta.10. Eles são semelhantes aos combinadores de CSS DOM da sombra (que foram descontinuados) e funcionam apenas com o encapsulamento: ViewEncapsulation.Emulated, que é o padrão no Angular2. Eles provavelmente também funcionam com ViewEncapsulation.None, mas são ignorados apenas porque não são necessários. Esses combinadores são apenas uma solução intermediária até que recursos mais avançados para o estilo de componentes cruzados sejam suportados.

Então, simplesmente fazendo:

:host >>> .child {}

No parentarquivo de estilo 's resolveu o problema. Observe que, conforme indicado na citação acima, esta solução é apenas intermediária até que o estilo de componentes cruzados mais avançado seja suportado.

Chrillewoodz
fonte
Parece que eles estão indo para ser removendo o suporte para :: NG-profunda angular.io/guide/component-styles#deprecated-deep--and-ng-deep
Jed Richards
42

Você NÃO deve usar ::ng-deep, está obsoleto. No Angular, a maneira correta de alterar o estilo do componente infantil dos pais é usar encapsulation(leia o aviso abaixo para entender as implicações):

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

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

E então, você poderá modificar o css do seu componente sem a necessidade de :: ng-deep

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

AVISO: Isso fará com que todas as regras css que você escreve para esse componente sejam globais.

Para limitar o escopo do seu css apenas a este componente, adicione uma classe css à tag superior do seu componente e coloque seu css "dentro" dessa tag:

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

Arquivo Scss:

.my-component {
  // All your css goes in there in order not to be global
}
Tonio
fonte
3
Esta é a melhor resposta para a IMO, pois na verdade é uma alternativa viável para o futuro que está sendo preterido ::ng-deep. Geralmente, os componentes têm seu próprio seletor de qualquer maneira ( <my-component>, <div my-component>, etc.), portanto, não há necessidade de um elemento wrapper com uma classe especial.
Alex Walker
@AlexWalker Essa pode ser a melhor resposta para a sua situação, mas vale a pena mencionar que apenas responde à metade da pergunta do OP: Este método permite que o CSS se propague normalmente de cima para baixo, mas, em virtude de jogar TODO o encapsulamento, não limitar esse estilo a filhos de um pai ou mãe específico . Se você estilizar os filhos de parent1 de uma maneira e filhos de parent2 de outra, essas regras de CSS agora estarão lutando entre si nos dois lugares. Isso pode ser incrivelmente doloroso (e o Angular adicionou um encapsulamento para evitá-lo).
ruffin
@ ruffin Foi exatamente por isso que adicionei o aviso na minha resposta para entender as implicações do uso dessa técnica e como "encapsular manualmente" usando uma tag css superior no seu componente
Tonio
1
@Tonio - Sim, concordou; estava respondendo diretamente para Alex e não para você. Seu comentário " para que não haja necessidade de um elemento wrapper com uma classe especial " me assustou um pouco. Talvez para uma situação específica , mas há uma razão pela qual o Angular "perde" tempo suportando o encapsulamento. Essa resposta é uma solução viável em casos específicos, mas, como você diz, é potencialmente perigosa em geral. A solução de MatthewB , por exemplo, estiliza os filhos enquanto mantém o encapsulamento (mas fica muito confuso se você tiver mais de uma geração de componentes filhos).
ruffin
19

Infelizmente, parece que o seletor / deep / está obsoleto (pelo menos no Chrome) https://www.chromestatus.com/features/6750456638341120

Em suma, parece que atualmente não há solução a longo prazo, a não ser que, de alguma forma, o componente filho modele as coisas dinamicamente.

Você pode passar um objeto de estilo para seu filho e aplicá-lo via:
<div [attr.style]="styleobject">

Ou, se você tiver um estilo específico, poderá usar algo como:
<div [style.background-color]="colorvar">

Mais discussões relacionadas a isso: https://github.com/angular/angular/issues/6511

Matthew B.
fonte
16

Teve o mesmo problema, portanto, se você estiver usando o angular2-cli com o scss / sass use '/ deep /' em vez de '>>>', o último seletor ainda não é suportado (mas funciona muito bem com o css).

SergiySev
fonte
11

Se você deseja ser mais direcionado para o componente filho real, faça o seguinte. Dessa forma, se outros componentes filhos compartilharem o mesmo nome de classe, eles não serão afetados.

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

Por exemplo:

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

Espero que isto ajude!

codematrix

code5
fonte
9

Na verdade, há mais uma opção. O que é bastante seguro. Você pode usar o ViewEncapsulation. De qualquer forma, sempre prefira algum estilo global mais estilos encapsulados.

Aqui está o exemplo de Denis Rybalka modificado:

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

Existem algumas opções para conseguir isso no Angular:

1) Você pode usar seletores css profundos

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

2) Você também pode alterar o encapsulamento de exibição que está definido como Emulado como padrão, mas pode ser facilmente alterado para Nativo, que usa a implementação do navegador nativo Shadow DOM; no seu caso, você só precisa desativá-lo

Por exemplo: `

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
fonte
3
Na verdade, isso significa que os estilos afetam todo o domínio, não apenas os elementos filhos.
Kasper Ziemianek
7

Você não deve escrever regras CSS para um componente filho em um componente pai, pois um componente Angular é uma entidade independente que deve declarar explicitamente o que está disponível para o mundo externo. Se o layout filho mudar no futuro, seus estilos para os elementos componentes componentes espalhados pelos arquivos SCSS de outros componentes poderão ser facilmente quebrados, tornando seu estilo muito frágil. Isso é o que ViewEncapsulationacontece no caso do CSS. Caso contrário, seria o mesmo se você pudesse atribuir valores a campos particulares de alguma classe de qualquer outra classe na Programação Orientada a Objetos.

Portanto, o que você deve fazer é definir um conjunto de classes que você pode aplicar ao elemento host filho e implementar como a criança responde a elas.

Tecnicamente, isso poderia ser feito da seguinte maneira:

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

Em outras palavras, você usa o :hostpseudo-seletor fornecido pelo conjunto Angular + de classes CSS para definir possíveis estilos filho no próprio componente filho. Você pode acionar esses estilos de fora aplicando classes predefinidas ao <child>elemento host.

Alexander Abakumov
fonte
Parece uma boa solução, existe um arquivo parent.component.scss? se sim, gostaria de dar?
Manohar Reddy Poreddy
@ManoharReddyPoreddy Não deve haver estilo em um parent.component.scssrelacionado ao estilo do componente filho. É o único objetivo dessa abordagem. Por que você precisa parent.component.scss?
Alexander Abakumov
Não tenho certeza, apenas conheço um pouco de css. Você pode compartilhar uma solução completa no jsbin ou em outro. Sua solução pode ser uma solução futura para todos.
Manohar Reddy Poreddy
2
@ManoharReddyPoreddy Eu sugiro que você tente primeiro esses trechos de código na prática. Em seguida, se você encontrar algum problema, terá uma pergunta específica que eu poderia responder ou aconselhar para procurar um tópico específico e entender melhor como corrigir o problema. Mencionei ViewEncapsulationapenas porque seu valor padrão é o que leva à questão do OP. Você não precisa atribuir um ViewEncapsulationcódigo diferente para o código acima funcionar.
Alexander Abakumov
1
+1 Obrigado. Voltará para tomar esta solução no futuro, resolvida para :: ng-deep stackoverflow.com/a/36528769/984471 por hoje.
Manohar Reddy Poreddy
5

Acho muito mais limpo passar uma variável @INPUT se você tiver acesso ao código do componente filho:

A idéia é que o pai diga à criança qual deve ser seu estado de aparência e que a criança decida como exibir o estado. É uma bela arquitetura

Maneira de SCSS:

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

Melhor maneira: - use a selectedvariável:

<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
fonte
2
Também é difícil de manter, especialmente para componentes recursivos.
Erik Philips
2

A partir de hoje (Angular 9), o Angular usa um DOM DOM para exibir os componentes como elementos HTML personalizados . Uma maneira elegante de estilizar esses elementos personalizados pode estar usando variáveis ​​CSS personalizadas . Aqui está um exemplo genérico:

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>

Como podemos ver no código acima, criamos um elemento personalizado, como o Angular faria por todos os componentes e, em seguida, substituímos a variável responsável pela cor do plano de fundo na raiz da sombra do elemento personalizado, no escopo global .

Em um aplicativo Angular, isso pode ser algo como:

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

A resposta rápida é que você não deveria estar fazendo isso. Ele quebra o encapsulamento de componentes e prejudica o benefício que você obtém dos componentes independentes. Considere passar um sinalizador de prop para o componente filho, ele pode decidir como renderizar de forma diferente ou aplicar CSS diferente, se necessário.

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

Angular está obsoleta todas as maneiras de afetar o estilo infantil dos pais.

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

Jed Richards
fonte
Bem, eles disseram explicitamente em seus documentos que estão fazendo isso eventualmente, o que eu acho que significa que eles farão. Eu concordo, porém, não acontecerá tão cedo.
Jed Richards
Portanto, eles praticamente tornarão inútil sua própria biblioteca de materiais. Nunca pude usar um tema padrão em nenhuma biblioteca, pois cada cliente exige seu próprio design. Normalmente você só quer a funcionalidade de um componente. Não posso dizer que entendo a lógica geral por trás dessa decisão.
Chrillewoodz 01/01
0

Eu também tive esse problema e não queria usar solução obsoleta, então acabei com:

em parrent

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

componente filho

@Input() ContainerCustomStyle: string;

na criança em div html

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

e no código

constructor(private sanitizer: DomSanitizer) {  }

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

funciona como esperado e não deve ser preterido;)

d00lar
fonte
Interessante! Acabei com algo semelhante (por enquanto). De onde você tira o DomSanitizer? Edit: Encontre-o: angular.io/api/platform-browser/DomSanitizer #
Zaphoid
sim na v7 é nativo, basta solicitar a injeção no construtor. ;), Em mais velho eu não tenho idéia se ele existisse - i começou a partir v7;)
d00lar
0

Conforme a internet é atualizada, me deparei com uma solução.

Primeiro algumas advertências.

  1. Ainda não faça isso. Para esclarecer, eu não planejaria componentes filhos, permitindo que você os modelasse. SOC. Se você, como projetista de componentes, deseja permitir isso, tem ainda mais poder para você.
  2. Se seu filho não mora na sombra, então isso não funcionará para você.
  3. Se você precisar dar suporte a um navegador que não pode ter um shadow dom, isso também não funcionará para você.

Primeiro, marque o encapsulamento do componente filho como sombra, para renderizá-lo no dom de sombra real. Segundo, adicione o atributo part ao elemento que você deseja permitir que o pai crie. Na folha de estilo do componente de seus pais, você pode usar o método :: part () para acessar

Luminoso
fonte
-1

Proponho um exemplo para torná-lo mais claro, pois o angular.io/guide/component-styles afirma:

O combinador descendente de penetrante sombra está obsoleto e o suporte está sendo removido dos principais navegadores e ferramentas. Como tal, planejamos descartar o suporte no Angular (para todos os 3 de / deep /, >>> e :: ng-deep). Até então, o :: ng-deep deve ser preferido para uma compatibilidade mais ampla com as ferramentas.

Em app.component.scss, importe seu, *.scssse necessário. _colors.scsstem alguns valores de cores comuns:

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

Aplique uma regra a todos os componentes

Todos os botões da btn-redclasse serão estilizados.

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

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

Aplicar uma regra a um único componente

Todos os botões com btn-redclasse no app-logincomponente serão estilizados.

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

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

Eu o resolvi fora do Angular. Eu defini um scss compartilhado que estou importando para meus filhos.

shared.scss

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

child.scss

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

Minha abordagem proposta é uma maneira de resolver o problema que o OP perguntou. Como mencionado em várias ocasiões, :: ng-deep,: ng-host será depreciado e desabilitar o encapsulamento é muito vazamento de código, na minha opinião.

Jakub
fonte