Come modellare i componenti figlio dal file CSS del componente padre?


266

Ho un componente genitore:

<parent></parent>

E voglio popolare questo gruppo con componenti figlio:

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

Modello principale:

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

Modello figlio:

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

Poiché parente childsono due componenti separati, i loro stili sono bloccati sul proprio ambito.

Nel mio componente genitore ho provato a fare:

.parent .child {
  // Styles for child
}

Ma gli .childstili non vengono applicati ai childcomponenti.

Ho provato a utilizzare styleUrlsper includere il parentfoglio di stile nel childcomponente per risolvere il problema relativo all'ambito:

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

Ma questo non ha aiutato, ha anche provato dall'altra parte recuperando il childfoglio di stile, parentma neanche quello ha aiutato.

Quindi, come modellare i componenti figlio inclusi in un componente principale?



Vedi un modo completamente paradigmatico e senza trucco nella mia risposta .
Alexander Abakumov,

Risposte:


242

Aggiornamento - Modo più recente

Non farlo, se puoi evitarlo. Come sottolinea Devon Sans nei commenti: molto probabilmente questa funzione sarà deprecata.

Aggiornamento - Modo più recente

Da Angular 4.3.0 , tutti i combinatori css piercing sono stati deprecati. Il team angolare ha introdotto un nuovo combinatore ::ng-deep (ancora a livello sperimentale e non in modo completo e definitivo) come mostrato di seguito,

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


Alla vecchia maniera

Puoi usare encapsulation modee / opiercing CSS combinators >>>, /deep/ and ::shadow

esempio funzionante: 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>      
`

3
Piercing combinatori CSS sono deprecati in Chrome però
Robin-Hoodie il

22
Il team angolare prevede di eliminare anche il supporto di :: ng-deep. Dai loro documenti: "Il combinatore discendente penetrante nell'ombra è deprecato e il supporto viene rimosso dai principali browser e strumenti. Pertanto, abbiamo in programma di abbandonare il supporto in Angolare (per tutte e 3 le / deep /, >>> e :: ng- fino ad allora :: ng-deep dovrebbe essere preferito per una più ampia compatibilità con gli strumenti. " angular.io/guide/component-styles#deprecated-deep--and-ng-deep .
Devon Sams,

5
Finché rimarrà una risposta accettata, le persone saranno fuorviate. :: ng-deep non dovrebbe essere usato come punti @DevonSams nel commento sopra.
Kostas Siabanis,

1
::ng-deepè ora obsoleto , non è consigliabile utilizzarlo in applicazioni future
Wilt

11
Deprecare qualcosa senza fornire un'alternativa probabilmente non è la soluzione migliore.
Tehlivi,

56

AGGIORNAMENTO 3:

::ng-deepè anche deprecato, il che significa che non dovresti più farlo. Non è chiaro come ciò influisca sulle cose in cui è necessario sostituire gli stili nei componenti figlio da un componente padre. A me sembra strano se questo viene rimosso completamente perché in che modo ciò influirebbe sulle cose come librerie in cui è necessario sovrascrivere gli stili in un componente di libreria?

Commenta se hai qualche idea in questo.

AGGIORNAMENTO 2:

Da allora /deep/e tutti gli altri selettori di piercing all'ombra sono ora deprecati. Rilascio angolare ::ng-deepche dovrebbe essere usato invece per una più ampia compatibilità.

AGGIORNARE:

Se si utilizza Angular-CLI è necessario utilizzare al /deep/posto di >>>o altrimenti non funzionerà.

ORIGINALE:

Dopo essere andato alla pagina Github di Angular2 e aver fatto una ricerca casuale di "stile", ho trovato questa domanda: Angular 2 - styling innerHTML

Che ha detto di usare qualcosa che è stato aggiunto in 2.0.0-beta.10, i selettori >>>e ::shadow.

(>>>) (e l'equivalente / deep /) e :: shadow sono stati aggiunti in 2.0.0-beta.10. Sono simili ai combinatori CSS DOM ombra (che sono deprecati) e funzionano solo con l'incapsulamento: ViewEncapsulation.Emulated che è il valore predefinito in Angular2. Probabilmente funzionano anche con ViewEncapsulation.None ma vengono ignorati solo perché non sono necessari. Questi combinatori sono solo una soluzione intermedia fino a quando non saranno supportate funzionalità più avanzate per lo stile tra componenti.

Quindi semplicemente facendo:

:host >>> .child {}

Nel parentfile del foglio di stile il problema è stato risolto. Si noti che, come indicato nella citazione sopra, questa soluzione è solo intermedia fino a quando non viene supportato uno stile cross-component più avanzato.


Sembra che rimuoveranno il supporto per :: ng-deep angular.io/guide/component-styles#deprecated-deep--and-ng-deep
Jed Richards

42

NON dovresti usare ::ng-deep, è deprecato. In angolare, è necessario utilizzare il modo corretto di modificare lo stile del componente per bambini dal genitore encapsulation(leggere l'avvertenza seguente per comprendere le implicazioni):

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

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

E poi, sarai in grado di modificare il css dal tuo componente senza la necessità di :: ng-deep

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

ATTENZIONE: In questo modo tutte le regole CSS scritte per questo componente saranno globali.

Per limitare l'ambito del tuo CSS solo a questo componente, aggiungi una classe CSS al tag superiore del tuo componente e metti il ​​tuo CSS "dentro" questo tag:

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

File scss:

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

3
Questa è la migliore risposta IMO, in quanto in realtà è una valida alternativa a quella che sarà presto deprecata ::ng-deep. In generale, i componenti hanno comunque il loro selettore ( <my-component>, <div my-component>, ecc.), Quindi non c'è nemmeno bisogno di un elemento wrapper con una classe speciale.
Alex Walker,

@AlexWalker Questa potrebbe essere la risposta migliore per la tua situazione, ma vale la pena ricordare che risponde solo alla metà della domanda dell'OP afaict: questo metodo consente ai CSS di propagarsi normalmente dall'alto verso il basso, ma, in virtù del buttare via TUTTA l'incapsulamento, non non limiterai questo stile ai figli di un genitore specifico . Se modifichi i figli di parent1 in un modo e i figli di parent2 in un altro, quelle regole CSS ora si combatteranno in entrambi i posti. Questo può essere doloroso per la mente (e l'incapsulamento angolare aggiunto per evitarlo).
ruffin,

@ruffin Questo è esattamente il motivo per cui ho aggiunto l'avvertimento nella mia risposta per comprendere le implicazioni dell'utilizzo di questa tecnica e come "incapsulare manualmente" utilizzando un tag css superiore sul componente
Tonio

1
@Tonio - Sì, d'accordo; stava rispondendo direttamente ad Alex piuttosto che a te. Il suo commento, " quindi non c'è nemmeno bisogno di un elemento wrapper con una classe speciale " mi ha spaventato un po '. Forse per una situazione specifica , ma c'è un motivo per cui "gli sprechi" di tempo supportano l'incapsulamento. Questa risposta è una soluzione praticabile in casi specifici, ma, come dici tu, è potenzialmente pericolosa in generale. La soluzione di MatthewB , ad esempio, modella i bambini mantenendo l'incapsulamento (ma diventa davvero disordinato se si dispone di più di una generazione di componenti figlio).
ruffin,

19

Purtroppo sembra che il / deep / selector sia deprecato (almeno in Chrome) https://www.chromestatus.com/features/6750456638341120

In breve sembra che non ci sia (attualmente) una soluzione a lungo termine se non quella di far sì che il componente figlio modifichi le cose in modo dinamico.

Puoi passare un oggetto stile a tuo figlio e farlo applicare tramite:
<div [attr.style]="styleobject">

O se hai uno stile specifico puoi usare qualcosa come:
<div [style.background-color]="colorvar">

Altre discussioni relative a questo: https://github.com/angular/angular/issues/6511


16

Aveva lo stesso problema, quindi se stai usando angular2-cli con scss / sass usa '/ deep /' invece di '>>>', l'ultimo selettore non è ancora supportato (ma funziona benissimo con css).


11

Se si desidera essere più mirati al componente figlio effettivo di quanto si dovrebbe fare quanto segue. In questo modo, se altri componenti figlio condividono lo stesso nome di classe, non saranno interessati.

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

Per esempio:

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

Spero che questo ti aiuti!

codematrix


9

In realtà c'è un'altra opzione. Il che è piuttosto sicuro. Puoi usare ViewEncapsulation.Nessuno MA inserisci tutti i tuoi stili di componente nel suo tag (aka selettore). Ma comunque preferisco sempre uno stile globale più stili incapsulati.

Ecco un esempio di Denis Rybalka modificato:

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

7

Ci sono alcune opzioni per raggiungere questo obiettivo in Angolare:

1) È possibile utilizzare i selettori deep css

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

2) Puoi anche cambiare l'incapsulamento della vista, è impostato su Emulato come predefinito ma può essere facilmente cambiato in Nativo che utilizza l'implementazione del browser nativo di Shadow DOM, nel tuo caso devi solo disabilitarlo

Ad esempio: `

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

   }
 }

3
In realtà significa che gli stili influenzano l'intera dom, non solo gli elementi figlio.
Kasper Ziemianek,

7

Non è necessario scrivere regole CSS per elementi di un componente figlio in un componente principale, poiché un componente Angolare è un'entità autonoma che dovrebbe dichiarare esplicitamente ciò che è disponibile per il mondo esterno. Se in futuro il layout del figlio dovesse cambiare, i tuoi stili per gli elementi del componente figlio sparsi tra i file SCSS di altri componenti potrebbero facilmente rompersi, rendendo così il tuo stile molto fragile. Questo è quello cheViewEncapsulation serve nel caso dei CSS. Altrimenti, sarebbe lo stesso se si potessero assegnare valori a campi privati ​​di una classe da qualsiasi altra classe in Programmazione orientata agli oggetti.

Pertanto, ciò che dovresti fare è definire un insieme di classi che potresti applicare all'elemento host figlio e implementare il modo in cui il bambino risponde a loro.

Tecnicamente, potrebbe essere fatto come segue:

// 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 altre parole, si utilizza lo :hostpseudo-selettore fornito da Angular + set di classi CSS per definire possibili stili figlio nel componente figlio stesso. Hai quindi la possibilità di attivare quegli stili dall'esterno applicando classi predefinite <child>all'elemento host.


Sembra una buona soluzione, c'è un file parent.component.scss? se si, ti interessa darglielo?
Manohar Reddy Poreddy,

@ManoharReddyPoreddy Non ci dovrebbe essere uno stile in parent.component.scssrelazione allo stile del componente figlio. È l'unico scopo di questo approccio. Perché avete bisogno parent.component.scss?
Alexander Abakumov,

Non sono sicuro, conosci solo un po 'di CSS. Puoi condividere una soluzione completa su jsbin o altro. La tua soluzione può essere una soluzione futura per tutti.
Manohar Reddy Poreddy il

2
@ManoharReddyPoreddy Ti suggerirei di provare prima quei pezzi di codice in pratica. Quindi, se dovessi riscontrare problemi, avresti una domanda specifica a cui potrei rispondere o consigli per esaminare un argomento specifico per capire come risolvere il problema. ho menzionatoViewEncapsulation solo perché il suo valore predefinito è ciò che porta alla domanda OP. Non è necessario assegnare un diverso ViewEncapsulationper il codice sopra per funzionare.
Alexander Abakumov,

1
+1 Grazie. Tornerà per prendere questa soluzione in futuro, optato per :: ng-deep stackoverflow.com/a/36528769/984471 per oggi.
Manohar Reddy Poreddy il

5

Trovo molto più pulito il passaggio di una variabile @INPUT se hai accesso al codice componente figlio:

L'idea è che il genitore dice al bambino quale dovrebbe essere il suo stato di apparenza e il bambino decide come visualizzare lo stato. È una bella architettura

Modo SCSS:

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

Modo migliore: - usa la selectedvariabile:

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

2
Anche difficile da mantenere, soprattutto per i componenti ricorsivi.
Erik Philips,

2

Ad oggi (Angular 9), Angular utilizza un DOM Shadow per visualizzare i componenti come elementi HTML personalizzati . Un modo elegante per dare uno stile a quegli elementi personalizzati potrebbe essere usare variabili CSS personalizzate . Ecco un esempio generico:

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>

Come possiamo vedere dal codice sopra, creiamo un elemento personalizzato, proprio come Angular farebbe per noi con ogni componente, e quindi sovrascriviamo la variabile responsabile del colore di sfondo all'interno della radice dell'ombra dell'elemento personalizzato, dall'ambito globale .

In un'app angolare, potrebbe essere qualcosa del tipo:

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

0

La risposta rapida è che non dovresti farlo affatto. Rompe l'incapsulamento dei componenti e mina il vantaggio che si ottiene dai componenti autonomi. Considera di passare un flag di prop al componente figlio, può quindi decidere se stesso come eseguire il rendering in modo diverso o applicare CSS diversi, se necessario.

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

Angular sta deprecando tutti i modi di influenzare gli stili dei bambini dai genitori.

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


Beh, hanno detto esplicitamente nei loro documenti che lo stanno facendo alla fine, il che credo significhi che lo faranno. Sono d'accordo però, non accadrà presto.
Jed Richards,

Quindi renderanno praticamente inutile la propria libreria di materiali. Non sono mai stato in grado di utilizzare un tema predefinito in nessuna libreria poiché ogni cliente richiede il proprio design. Di solito vuoi solo la funzionalità di un componente. Non posso dire di aver compreso la logica generale alla base di questa decisione.
Chrillewoodz,

0

ho anche avuto questo problema e non volevo usare una soluzione obsoleta, quindi ho finito con:

in pappagallo

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

componente figlio

@Input() ContainerCustomStyle: string;

in child in html div

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

e nel codice

constructor(private sanitizer: DomSanitizer) {  }

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

funziona come previsto e non dovrebbe essere deprecato;)


Interessante! Ho finito con qualcosa di simile (per ora). Da dove prendi DomSanitizer? Modifica: Trovato: angular.io/api/platform-browser/DomSanitizer
Zaphoid

sì, in v7 è nativo devi solo richiederne l'iniezione nel costruttore. ;), in precedenza non ho idea se esistesse - sono partito dalla v7;)
d00lar

0

Con gli aggiornamenti di Internet ho trovato una soluzione.

Prima alcuni avvertimenti.

  1. Ancora non farlo. Per chiarire, non pianificherei i componenti figlio che consentono di modellarli. SOC. Se tu come progettista di componenti vuoi permetterti, allora tanto più potere per te.
  2. Se tuo figlio non vive nell'ombra, questo non funzionerà per te.
  3. Se devi supportare un browser che non può avere una Shadow Dom, anche questo non funzionerà per te.

Innanzitutto, contrassegnare l'incapsulamento del componente figlio come ombra in modo che venga visualizzato nell'ombra dom effettiva. In secondo luogo, aggiungere l'attributo della parte all'elemento che si desidera consentire allo stile principale. Nel foglio di stile dei componenti del tuo genitore puoi usare il metodo :: part () per accedere


-1

Propongo un esempio per renderlo più chiaro, poiché angular.io/guide/component-styles afferma:

Il combinatore discendente penetrante nell'ombra è deprecato e il supporto viene rimosso dai principali browser e strumenti. Pertanto, prevediamo di eliminare il supporto in Angular (per tutte e 3 le opzioni di / deep /, >>> e :: ng-deep). Fino ad allora :: ng-deep dovrebbe essere preferito per una più ampia compatibilità con gli strumenti.

Attiva app.component.scss, importa il tuo *.scssse necessario. _colors.scssha alcuni valori di colore comuni:

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

Applica una regola a tutti i componenti

Tutti i pulsanti con btn-redclasse saranno disegnati.

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

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

Applica una regola a un singolo componente

Tutti i pulsanti con btn-redclasse sul app-logincomponente saranno disegnati.

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

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

-1

L'ho risolto al di fuori di Angular. Ho definito uno scss condiviso che sto importando ai miei figli.

shared.scss

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

child.scss

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

Il mio approccio proposto è un modo per risolvere il problema richiesto dall'OP. Come accennato più volte, :: ng-deep,: ng-host verrà deprezzato e disabilitare l'incapsulamento è semplicemente una perdita di codice, a mio avviso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.