¿Cómo diseñar los componentes secundarios del archivo CSS del componente principal?

266

Tengo un componente padre:

<parent></parent>

Y quiero llenar este grupo con componentes secundarios:

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

Plantilla principal:

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

Plantilla hijo:

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

Ya que parent y childson dos componentes separados, sus estilos son encerrados en su propio ámbito de aplicación.

En mi componente principal intenté hacer:

.parent .child {
  // Styles for child
}

Pero el .child estilos no se aplican a los childcomponentes.

Intenté usar styleUrlspara incluir la parenthoja de estilo de 's en el childcomponente para resolver el problema del alcance:

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

Pero eso no ayudó, también lo intentó de otra manera al buscar la childhoja de estilo parentpero tampoco ayudó.

Entonces, ¿cómo diseña los componentes secundarios que se incluyen en un componente principal?

Chrillewoodz
fuente
1
Ver también stackoverflow.com/questions/34542143/…
Günter Zöchbauer el
Vea una manera completamente amigable con los paradigmas y sin trucos en mi respuesta .
Alexander Abakumov

Respuestas:

242

Actualización: la forma más nueva

No lo hagas, si puedes evitarlo. Como Devon Sans señala en los comentarios: es muy probable que esta característica esté en desuso.

Actualización: nueva forma

Desde Angular 4.3.0 , todos los combinadores de piercing css quedaron en desuso. El equipo angular introdujo un nuevo combinador ::ng-deep (todavía está en el nivel experimental y no en la forma completa y final) como se muestra a continuación,

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


Vieja forma

Puedes usar encapsulation modey / opiercing CSS combinators >>>, /deep/ and ::shadow

ejemplo de trabajo: 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
fuente
3
Sin embargo
Robin-Hoodie
22
El equipo angular planea abandonar el soporte de :: ng-deep también. De sus documentos: "El combinador descendiente que atraviesa las sombras está en desuso y el soporte se está eliminando de los principales navegadores y herramientas. Como tal, planeamos dejar el soporte en Angular (para los 3 de / deep /, >>> y :: ng- profundo). Hasta entonces :: ng-deep debería preferirse para una compatibilidad más amplia con las herramientas ". angular.io/guide/component-styles#deprecated-deep--and-ng-deep .
Devon Sams
55
Mientras esto se mantenga como una respuesta aceptada, la gente se engañará. :: ng-deep no debe usarse como puntos @DevonSams en el comentario anterior.
Kostas Siabanis
1
::ng-deepahora está en desuso , no recomiendo usarlo en futuras aplicaciones
Marché
11
Despreciar algo sin proporcionar una alternativa probablemente no sea la mejor solución.
tehlivi
56

ACTUALIZACIÓN 3:

::ng-deeptambién está en desuso, lo que significa que ya no debes hacer esto. No está claro cómo esto afecta las cosas en las que necesita anular los estilos en los componentes secundarios de un componente primario. Para mí, parece extraño que esto se elimine por completo porque ¿cómo afectaría esto a las bibliotecas en las que necesita anular los estilos en un componente de biblioteca?

Comenta si tienes alguna idea sobre esto.

ACTUALIZACIÓN 2:

Desde entonces, /deep/todos los demás selectores de piercing de sombra ahora están en desuso. Angular descartado ::ng-deepque debe usarse en su lugar para una compatibilidad más amplia.

ACTUALIZAR:

Si usa Angular-CLI, debe usarlo en /deep/lugar de hacerlo >>>o no funcionará.

ORIGINAL:

Después de ir a la página de Github de Angular2 y hacer una búsqueda aleatoria de "estilo", encontré esta pregunta: Angular 2 - estilo innerHTML

Lo cual dice que use algo que se agregó 2.0.0-beta.10, los selectores >>>y ::shadow.

(>>>) (y el equivalente / deep /) y :: shadow se agregaron en 2.0.0-beta.10. Son similares a los combinadores CSS DOM de sombra (que están en desuso) y solo funcionan con encapsulación: ViewEncapsulation.Emulated, que es el valor predeterminado en Angular2. Probablemente también funcionen con ViewEncapsulation. Ninguno, pero luego solo se ignoran porque no son necesarios. Estos combinadores son solo una solución intermedia hasta que se admitan características más avanzadas para el diseño de componentes cruzados.

Entonces simplemente haciendo:

:host >>> .child {}

En parentel archivo de hoja de estilo de resuelto el problema. Tenga en cuenta que, como se indica en la cita anterior, esta solución es solo intermedia hasta que se admita un estilo de componentes cruzados más avanzado.

Chrillewoodz
fuente
Parece que van a eliminar el soporte para :: ng-deep angular.io/guide/component-styles#deprecated-deep--and-ng-deep
Jed Richards
42

NO debe usar ::ng-deep, está en desuso. En Angular, la forma correcta de cambiar el estilo del componente infantil del padre es usar encapsulation(lea la advertencia a continuación para comprender las implicaciones):

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

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

Y luego, podrá modificar el formato CSS de su componente sin necesidad de :: ng-deep

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

ADVERTENCIA: Al hacer esto, todas las reglas CSS que escriba para este componente serán globales.

Para limitar el alcance de su CSS a este componente solamente, agregue una clase CSS a la etiqueta superior de su componente y coloque su CSS "dentro" de esta etiqueta:

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

Archivo Scss:

.my-component {
  // All your css goes in there in order not to be global
}
Tonio
fuente
3
Esta es la mejor respuesta de la OMI, ya que en realidad es una alternativa viable a la que pronto será desaprobada ::ng-deep. En general, los componentes tienen su propio selector de todos modos ( <my-component>, <div my-component>, etc.), por lo que ni siquiera es necesario un elemento contenedor con una clase especial.
Alex Walker
@AlexWalker Esta podría ser la mejor solución para su situación, pero vale la pena mencionar que sólo responde a la mitad de la pregunta afaict de la OP: Este método permite CSS para propagar de forma normal, de arriba abajo, pero, en virtud de tirar TODOS encapsulación, doesn No limite ese estilo a los hijos de un padre específico . Si da estilo a los hijos de parent1 de una manera y a los hijos de parent2 de otra, esas reglas CSS ahora se enfrentarán entre sí en ambos lugares. Eso puede ser muy doloroso (y Angular agregó encapsulación para evitarlo).
ruffin
@ruffin Es exactamente por eso que agregué la advertencia en mi respuesta para comprender la implicación del uso de esta técnica y cómo "encapsular manualmente" usando una etiqueta css superior en su componente
Tonio
1
@Tonio - Sí, de acuerdo; estaba respondiendo directamente a Alex en lugar de a ti. Su comentario, " así que ni siquiera hay necesidad de un elemento contenedor con una clase especial " me asustó un poco. Tal vez para una situación específica , pero hay una razón por la cual Angular "pierde" el tiempo apoyando la encapsulación. Esta respuesta es una solución viable en casos específicos, pero, como usted dice, es potencialmente peligrosa en general. La solución de MatthewB , por ejemplo, estiliza a los niños mientras mantiene la encapsulación (pero se vuelve realmente complicado si tiene más de una generación de componentes secundarios).
ruffin
19

Lamentablemente, parece que el selector / deep / está en desuso (al menos en Chrome) https://www.chromestatus.com/features/6750456638341120

En resumen, parece que (actualmente) no hay una solución a largo plazo que no sea lograr que su componente hijo estilice las cosas dinámicamente.

Puede pasar un objeto de estilo a su hijo y aplicarlo a través de:
<div [attr.style]="styleobject">

O si tiene un estilo específico, puede usar algo como:
<div [style.background-color]="colorvar">

Más discusión relacionada con esto: https://github.com/angular/angular/issues/6511

Matthew B.
fuente
16

Tuve el mismo problema, así que si está usando angular2-cli con scss / sass use '/ deep /' en lugar de '>>>', el último selector aún no es compatible (pero funciona muy bien con css).

SergiySev
fuente
11

Si desea centrarse más en el componente secundario real de lo que debería, haga lo siguiente. De esta manera, si otros componentes secundarios comparten el mismo nombre de clase, no se verán afectados.

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

Por ejemplo:

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 esto ayude!

codematrix

código5
fuente
9

En realidad hay una opción más. Lo cual es bastante seguro. Puede usar ViewEncapsulation. Ninguno PERO coloca todos los estilos de sus componentes en su etiqueta (también conocido como selector). Pero de todos modos, siempre prefiero un estilo global más estilos encapsulados.

Aquí está el ejemplo modificado 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
fuente
7

Hay algunas opciones para lograr esto en Angular:

1) Puedes usar selectores css profundos

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

2) También puede cambiar la encapsulación de vista que está configurada como Emulada por defecto, pero puede cambiarse fácilmente a Nativo, que usa la implementación del navegador nativo de DOM DOM, en su caso solo necesita deshabilitarlo

Por ejemplo: `

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
fuente
3
En realidad, significa que los estilos afectan toda la dom, no solo los elementos secundarios.
Kasper Ziemianek
7

No debe escribir reglas CSS para los elementos de un componente secundario en un componente primario, ya que un componente angular es una entidad autónoma que debe declarar explícitamente lo que está disponible para el mundo exterior. Si el diseño secundario cambia en el futuro, sus estilos para los elementos de ese componente secundario dispersos en los archivos SCSS de otros componentes podrían romperse fácilmente, haciendo que su estilo sea muy frágil. Eso es lo queViewEncapsulation es en el caso de CSS. De lo contrario, sería lo mismo si pudiera asignar valores a campos privados de alguna clase desde cualquier otra clase en Programación Orientada a Objetos.

Por lo tanto, lo que debe hacer es definir un conjunto de clases que podría aplicar al elemento host secundario e implementar cómo responde el niño a ellas.

Técnicamente, se podría hacer de la siguiente manera:

// 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 otras palabras, utiliza el :hostpseudo-selector proporcionado por Angular + conjunto de clases CSS para definir posibles estilos secundarios en el componente secundario en sí. Luego tiene la capacidad de activar esos estilos desde el exterior aplicando clases predefinidas al <child>elemento host.

Alexander Abakumov
fuente
Parece una buena solución, ¿hay un archivo parent.component.scss? en caso afirmativo, ¿te gustaría darlo?
Manohar Reddy Poreddy
@ManoharReddyPoreddy No debe haber un estilo parent.component.scssrelacionado con el estilo del componente secundario. Es el único propósito de este enfoque. ¿Por qué lo necesitas parent.component.scss?
Alexander Abakumov
No estoy seguro, solo sé un poco de CSS. ¿Puedes compartir una solución completa en jsbin u otro? Su solución puede ser una solución futura para todos.
Manohar Reddy Poreddy
2
@ManoharReddyPoreddy Te sugiero que pruebes esos códigos en la práctica primero. Luego, si se encontrara con algún problema, tendría una pregunta específica que podría responder o un consejo para analizar un tema específico y comprender cómo solucionar su problema. Mencioné ViewEncapsulationsolo porque su valor predeterminado es lo que lleva a la pregunta OP. No tiene que asignar un ViewEncapsulationcódigo diferente para que funcione el código anterior.
Alexander Abakumov
1
+1 gracias. Volveré para tomar esta solución en el futuro, conformada por :: ng-deep stackoverflow.com/a/36528769/984471 por hoy.
Manohar Reddy Poreddy
5

Me parece mucho más limpio pasar una variable @INPUT si tiene acceso al código del componente secundario:

La idea es que el padre le dice al niño cuál debería ser su estado de apariencia, y el niño decide cómo mostrar el estado. Es una bonita arquitectura

Manera SCSS:

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

Mejor manera: - usar 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
fuente
2
También es difícil de mantener, especialmente para componentes recursivos.
Erik Philips
2

A partir de hoy (Angular 9), Angular utiliza un DOM DOM para mostrar los componentes como elementos HTML personalizados . Una forma elegante de diseñar esos elementos personalizados podría ser usar variables CSS personalizadas . Aquí hay un ejemplo 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 en el código anterior, creamos un elemento personalizado, tal como lo haría Angular para nosotros con cada componente, y luego anulamos la variable responsable del color de fondo dentro de la raíz sombra del elemento personalizado, desde el alcance global .

En una aplicación Angular, esto podría 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
fuente
0

La respuesta rápida es que no deberías estar haciendo esto, en absoluto. Rompe la encapsulación de componentes y socava el beneficio que obtienes de los componentes autónomos. Considere pasar una bandera de apoyo al componente hijo, entonces puede decidir por sí mismo cómo renderizar de manera diferente o aplicar CSS diferente, si es necesario.

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

Angular está despreciando todas las formas de afectar los estilos infantiles de los padres.

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

Jed Richards
fuente
Bueno, han dicho explícitamente en sus documentos que finalmente lo harán, lo que supongo que significa que lo harán. Sin embargo, estoy de acuerdo, no sucederá pronto.
Jed Richards
Por lo tanto, prácticamente harán que su propia biblioteca de Materiales sea inútil. Nunca he podido usar un tema predeterminado en ninguna biblioteca ya que cada cliente requiere su propio diseño. Por lo general, solo desea la funcionalidad de un componente. No puedo decir que entiendo su lógica general detrás de esta decisión.
Chrillewoodz
0

También tuve este problema y no quería usar una solución obsoleta, así que terminé con:

en padres

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

componente hijo

@Input() ContainerCustomStyle: string;

en niño en html div

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

y en código

constructor(private sanitizer: DomSanitizer) {  }

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

funciona como se esperaba y no debe ser desaprobado;)

d00lar
fuente
¡Interesante! Terminé con algo similar (por ahora). ¿De dónde obtienes DomSanitizer? Editar: Lo encontré: angular.io/api/platform-browser/DomSanitizer
Zaphoid
Sí, en v7 es nativo, solo tiene que solicitar la inyección en el constructor. ;), en mayores no tengo idea si existió - comencé desde v7;)
d00lar
0

A medida que Internet se actualiza, he encontrado una solución.

Primero algunas advertencias.

  1. Aún no lo hagas. Para aclarar, no planearía componentes secundarios que le permitan diseñarlos. SOC. Si usted, como diseñador de componentes, desea permitir esto, tendrá aún más poder.
  2. Si su hijo no vive en la sombra, entonces esto no funcionará para usted.
  3. Si tiene que admitir un navegador que no puede tener una sombra dom, entonces esto tampoco funcionará para usted.

Primero, marque la encapsulación de su componente hijo como sombra para que se represente en el dom de sombra real. En segundo lugar, agregue el atributo de la parte al elemento que desea permitir que el padre aplique. En la hoja de estilo de componentes de sus padres, puede usar el método :: part () para acceder

Luminoso
fuente
-1

Propongo un ejemplo para hacerlo más claro, ya que angular.io/guide/component-styles dice:

El combinador descendente que atraviesa las sombras está en desuso y el soporte se está eliminando de los principales navegadores y herramientas. Como tal, planeamos eliminar el soporte en Angular (para los 3 de / deep /, >>> y :: ng-deep). Hasta entonces :: ng-deep debería preferirse para una compatibilidad más amplia con las herramientas.

Encendido app.component.scss, importe su *.scsssi es necesario. _colors.scsstiene algunos valores de color comunes:

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

Aplicar una regla a todos los componentes

Se diseñarán todos los botones que tengan btn-redclase.

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

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

Aplicar una regla a un solo componente

Se diseñarán todos los botones que tengan btn-redclase en el app-logincomponente.

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

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

Lo he resuelto fuera de Angular. He definido un scss compartido que estoy importando a mis hijos.

shared.scss

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

child.scss

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

Mi enfoque propuesto es una forma de resolver el problema que el OP ha preguntado. Como se mencionó en varias ocasiones, :: ng-deep,: ng-host se depreciará y, en mi opinión, deshabilitar la encapsulación es una pérdida de código.

Jakub
fuente