Як створити дочірні компоненти з CSS-файлу батьківського компонента?


264

У мене є батьківський компонент:

<parent></parent>

І я хочу заселити цю групу дочірніми компонентами:

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

Батьківський шаблон:

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

Дочірній шаблон:

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

Так як parentі childв двох окремих компонентів, їх стилі замикаються у своїй власній області.

У своєму батьківському компоненті я намагався робити:

.parent .child {
  // Styles for child
}

Але .childстилі не застосовуються до childкомпонентів.

Я намагався styleUrlsвключити parentтаблицю стилів у childкомпонент для вирішення проблеми сфери:

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

Але це не допомогло, також спробували інший спосіб, ввівши childтаблицю стилів, parentале це теж не допомогло.

Отже, як ви стилізуєте дочірні компоненти, які входять до батьківського компонента?



Дивіться в моїй відповіді цілком сприятливий парадигми спосіб .
Олександр Абакумов

Відповіді:


242

Оновлення - найновіший спосіб

Не робіть цього, якщо зможете цього уникнути. Як в коментарях зазначає Девон Санс: Ця функція, швидше за все, буде застарілою.

Оновлення - новіший шлях

З кутового 4.3.0 усі компресорні css-комбінатори застаріли. Кутова команда представила нового комбінатора ::ng-deep (все ще він знаходиться на експериментальному рівні, а не на повному та остаточному шляху) як показано нижче,

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


Старий шлях

Ви можете використовувати encapsulation modeта / абоpiercing CSS combinators >>>, /deep/ and ::shadow

приклад роботи: 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
Комбінатори пірсингу CSS застаріли в Chrome, хоча
Робін-Худі

22
Команда кутів планує також відмовитись від підтримки :: ng-deep. З їхніх документів: "Комбінатор нащадків, що пронизує тінь, застарів, і підтримка видаляється з основних браузерів та інструментів. Як такий, ми плануємо відмовитись у підтримці Angular (для всіх 3 / deep /, >>> та :: ng- до цього часу: ng-deep слід віддавати перевагу для ширшої сумісності з інструментами. " angular.io/guide/component-styles#deprecated-deep--and-ng-deep .
Девон Самс

5
Поки це залишається прийнятою відповіддю, люди будуть вводити в оману. :: ng-deep не слід використовувати як пункти @DevonSams у коментарі вище.
Костас Сіабаніс

1
::ng-deepтепер застаріло , я не рекомендую використовувати його в майбутніх програмах
Wilt

11
Позбавлення чогось, не пропонуючи альтернативу, мабуть, не найкраще рішення.
tehlivi

56

ОНОВЛЕННЯ 3:

::ng-deepтакож застаріло, а це означає, що ви більше не повинні цього робити. Незрозуміло, як це впливає на речі, де потрібно змінити стилі в дочірніх компонентах з батьківського компонента. Мені здається дивним, якщо це видаляється повністю, бо як це вплине на речі як бібліотеки, де потрібно переосмислити стилі в компоненті бібліотеки?

Прокоментуйте, якщо ви маєте про це розуміння.

ОНОВЛЕННЯ 2:

З тих пір /deep/і всі інші селекторні пірсинг застарілі. Кутове падіння, ::ng-deepяке слід використовувати замість цього для більш широкої сумісності.

ОНОВЛЕННЯ:

Якщо ви використовуєте Angular-CLI, вам потрібно використовувати /deep/замість цього, >>>він не працюватиме.

ОРИГІНАЛ:

Перейшовши на сторінку Github на Angular2 і зробивши випадковий пошук "стилю", я знайшов таке запитання: Angular 2 - укладкаHTHTML

Який сказав використовувати щось, що було додано в 2.0.0-beta.10, >>>і ::shadowселектори.

(>>>) (та еквівалент / deep /) та :: тінь були додані в 2.0.0-beta.10. Вони схожі на тіньові комбінатори CSS DOM (які застаріли) і працюють лише з інкапсуляцією: ViewEncapsulation.Емульований, що є типовим у Angular2. Вони, ймовірно, також працюють з ViewEncapsulation.None, але потім ігноруються лише тому, що вони не потрібні. Ці комбінатори є лише проміжним рішенням до тих пір, поки не підтримуються більш вдосконалені функції для міжкомпонентних стилів.

Так просто робимо:

:host >>> .child {}

У parentфайлі таблиці стилів вирішено проблему. Зверніть увагу, як зазначено в цитуванні вище, це рішення є лише проміжним, поки не підтримується більш вдосконалений міжкомпонентний стиль.


Схоже, вони будуть видаляти підтримку :: ng-deep angular.io/guide/component-styles#deprecated-deep--and-ng-deep
Джед Річардс

40

НЕ ВИКОРИСТОВУЙТЕ ::ng-deep, воно застаріле. У Angular правильним способом зміни стилю дитячого компонента від батьків є використання encapsulation(прочитайте попередження нижче, щоб зрозуміти наслідки):

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

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

І тоді ви зможете змінювати css-форму свого компонента без потреби з :: ng-deep

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

УВАГА: Якщо це зробити, усі правила css, які ви пишете для цього компонента, будуть глобальними.

Щоб обмежити область вашої css лише цим компонентом, додайте клас css до верхнього тегу вашого компонента та поставте css "всередину" цього тега:

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

Файл scss:

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

3
Це найкраща відповідь ІМО, оскільки це насправді життєздатна альтернатива скоро знищеному ::ng-deep. Як правило, компоненти так чи інакше мають свій власний селектор ( <my-component>, <div my-component>тощо), тому немає потреби навіть у обгортковому елементі зі спеціальним класом.
Алекс Уокер

@AlexWalker Це може бути найкращою відповіддю для вашої ситуації, але варто згадати, що він відповідає лише половині факту питання ОП: Цей метод дозволяє CSS поширюватися як звичайно зверху вниз, але, в силу викидання ВСЕ інкапсуляції, не не обмежуйте цю стилізацію дітьми конкретного батька . Якщо ви стилізуєте дітей батьків1 в одну сторону та дітей батьків2 іншим, ці правила CSS тепер будуть боротися між собою в обох місцях. Це може бути болісно розумним (і кутова додала інкапсуляцію, щоб уникнути цього).
ruffin

@ruffin Саме тому я додав попередження у свою відповідь, щоб зрозуміти значення використання цієї методики та як "вручну інкапсулювати", використовуючи верхній тег css на своєму компоненті
Тоніо

1
@Tonio - Так, погодився; відповідав безпосередньо на Алекса, а не на вас. Його коментар " так що навіть немає потреби в обгортковому елементі зі спеціальним класом " мене трохи налякав. Можливо, для конкретної ситуації, але є причина, що кутовий "витрачає" час на підтримку інкапсуляції. Ця відповідь є ефективним рішенням у конкретних випадках, але, як ви кажете, в цілому є потенційно небезпечним. Наприклад, рішення MatthewB стилізує дітей, зберігаючи інкапсуляцію (але воно стає справді безладним, якщо у вас є більше одного покоління дочірніх компонентів).
ruffin

19

На жаль, здається, що / deep / селектор застарілий (принаймні в Chrome) https://www.chromestatus.com/features/6750456638341120

Якщо коротко, то, здається, немає (на даний момент) іншого довгострокового рішення, крім того, щоб якось змусити ваш дитячий компонент динамічно стилювати речі.

Ви можете передати дитині об'єкт стилю та застосувати його через:
<div [attr.style]="styleobject">

Або якщо у вас є певний стиль, ви можете використовувати щось на зразок:
<div [style.background-color]="colorvar">

Більше обговорення, пов’язаного з цим: https://github.com/angular/angular/isissue/6511


16

У нього була така ж проблема, тож якщо ви використовуєте angular2-cli з scss / sass use '/ deep /' замість '>>>', останній селектор ще не підтримується (але відмінно працює з css).


11

Якщо ви хочете бути більш орієнтованими на фактичний дочірній компонент, ніж слід зробити наступне. Таким чином, якщо інші дочірні компоненти мають одне і те ж ім’я класу, це не вплине.

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

Наприклад:

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

Сподіваюсь, це допомагає!

кодематриця


9

Насправді є ще один варіант. Що досить безпечно. Ви можете використовувати ViewEncapsulation.None АЛЕ НЕ вкладайте всі стилі компонентів у свій тег (він же селектор). Але все одно завжди віддають перевагу якомусь глобальному стилю плюс інкапсульованим стилям.

Ось модифікований приклад Дениса Рибалки:

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

Є кілька варіантів, як цього досягти в Angular:

1) Ви можете використовувати селектори глибокого css

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

2) Ви також можете змінити інкапсуляцію подання, встановлену на Емуляцію за замовчуванням, але її можна легко змінити на Native, яка використовує втілений браузер Shadow DOM, у вашому випадку вам просто потрібно відключити її

Наприклад: `

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
Насправді це означає, що стилі впливають на цілий купол, а не лише на дочірні елементи.
Каспер Зімеянек

7

Не слід писати правила CSS для елементів дочірнього компонента в батьківському компоненті, оскільки кутовий компонент - це автономна сутність, яка повинна чітко заявляти, що доступно для зовнішнього світу. Якщо в майбутньому дочірнє планування зміниться, ваші стилі для елементів цього дочірнього компонента, розкидані по файлам SCSS інших компонентів, можуть легко зламатися, що зробить ваш стиль дуже крихким. Це те ViewEncapsulation, що стосується CSS. Інакше було б те саме, якби ви могли призначити значення приватним полям якогось класу з будь-якого іншого класу в об'єктно-орієнтованому програмуванні.

Тому ви повинні зробити набір класів, які ви могли б застосувати до дочірнього елемента хосту, і реалізувати, як дитина реагує на них.

Технічно це можна зробити так:

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

Іншими словами, ви використовуєте :hostпсевдоселектор, наданий Angular + набором класів CSS, щоб визначити можливі дочірні стилі в самому дочірньому компоненті. Потім у вас є можливість запускати ці стилі ззовні, застосовуючи попередньо визначені класи до <child>хост-елемента.


Це добре рішення, чи є файл parent.component.scss? якщо так, то подбайте про це?
Манохар Редді Поредді

@ManoharReddyPoreddy Не повинно бути стилів, parent.component.scssпов'язаних зі стилем дочірнього компонента. Це єдина мета такого підходу. Навіщо вам це потрібно parent.component.scss?
Олександр Абакумов

Не впевнений, просто знайте трохи css. Чи можете ви поділитися повним рішенням на jsbin чи іншому. Ваше рішення може стати майбутнім рішенням для всіх.
Манохар Редді Поредді

2
@ManoharReddyPoreddy Я б запропонував спершу спробувати ці фрагменти коду на практиці. Тоді, якщо ви зіткнулися з будь-якими проблемами, у вас виникне конкретне запитання, на яке я можу відповісти чи порадити, щоб заглянути в певну тему, щоб зрозуміти, як виправити свою проблему. Я згадував ViewEncapsulationлише тому, що його значення за замовчуванням - це те, що призводить до питання про ОП. Вам не потрібно призначити інший, ViewEncapsulationщоб вищевказаний код працював.
Олександр Абакумов

1
+1 Дякую Знову повернемося, щоб прийняти це рішення в майбутньому, вирішивши сьогодні : ng-deep stackoverflow.com/a/36528769/984471 .
Манохар Редді Поредді

5

Я вважаю, що набагато чистіше передавати змінну @INPUT якщо у вас є доступ до дочірнього кодового компонента:

Ідея полягає в тому, що батько повідомляє дитині, яким повинен бути його зовнішній вигляд, а дитина вирішує, як відобразити стан. Це приємна архітектура

Шлях SCSS:

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

Кращий спосіб: - використовувати selectedзмінну:

<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
Також важко підтримувати, особливо для рекурсивних компонентів.
Ерік Філіпс

2

На сьогоднішній день (Angular 9) Angular використовує Shadow DOM для відображення компонентів як користувацьких елементів HTML . Одним елегантним способом стилювання цих користувальницьких елементів може бути використання спеціальних змінних CSS . Ось загальний приклад:

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>

Як ми бачимо з наведеного вище коду, ми створюємо спеціальний елемент, як і Angular, який би робив для нас кожен компонент, і тоді ми змінюємо змінну, відповідальну за колір тла в тіньовому корені користувацького елемента, з глобальної області застосування .

У програмі Angular це може бути щось на зразок:

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

Швидка відповідь - ти взагалі не повинен цього робити. Це порушує інкапсуляцію компонентів і підриває користь, яку ви отримуєте від автономних компонентів. Розглянемо можливість передачі опорного прапора дочірньому компоненту, то він може самостійно вирішити, як відобразити інакше, або застосувати різні CSS, якщо необхідно.

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

Кутовий знецінює всі способи впливу на дитячі стилі з боку батьків.

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


Добре, що вони прямо сказали у своїх документах, вони роблять це зрештою, що, мабуть, означає, що вони будуть. Я погоджуюся, але не відбудеться незабаром.
Джед Річардс

Таким чином, вони здебільшого зроблять власну бібліотеку матеріалів непотрібною. Я ніколи не міг використовувати тему за замовчуванням у будь-якій бібліотеці, оскільки кожен клієнт потребує власного дизайну. Зазвичай ви просто хочете функціонувати компонент. Я не можу сказати, що я розумію загальну логіку цього рішення.
Chrillewoodz

0

У мене також була ця проблема, і я не хотів використовувати застаріле рішення, тому я закінчив:

на батьківщині

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

дочірній компонент

@Input() ContainerCustomStyle: string;

у дитини в html div

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

і в коді

constructor(private sanitizer: DomSanitizer) {  }

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

працює як очікувалося, і не має бути застарілим;)


Цікаво! Я закінчився чимось подібним (поки що). Звідки ви берете DomSanitizer? Редагувати: Знайдено: angular.io/api/platform-browser/DomSanitizer
Zaphoid

так, в v7 він є рідним, ви просто повинні вимагати введення його в конструктор. ;), у старших я не маю уявлення, чи існував він - я починав з v7;)
d00lar

0

По мірі оновлень в Інтернеті я натрапив на рішення.

Спочатку кілька застережень.

  1. Все-таки не робіть цього. Для уточнення, я б не планував дочірні компоненти, що дозволяють вам їх стилювати. SOC. Якщо ви як дизайнер компонентів хочете дозволити це, тоді вам все більше влади.
  2. Якщо ваша дитина не живе в тіньовому домі, то це не буде працювати для вас.
  3. Якщо вам доведеться підтримувати браузер, який не може мати тіньовий купол, то це також не буде працювати для вас.

По-перше, позначте інкапсуляцію свого дочірнього компонента тінню, щоб вона опинилася у фактичному тіньовому домі. По-друге, додайте атрибут частини до елемента, який ви хочете дозволити батькові стилю. У таблиці стилів вашого батьківського компонента ви можете використовувати метод :: part () для доступу


-1

Я пропоную приклад, щоб зробити його більш зрозумілим, оскільки в angular.io/guide/component-styles зазначено:

Комбінатор нащадків на тіні, що пронизує тінь, застарів, і підтримка видаляється з основних браузерів та інструментів. Як такий, ми плануємо відмовитись від підтримки у Angular (для всіх 3 з / deep /, >>> та :: ng-deep). До цього часу: ng-deep слід віддавати перевагу для ширшої сумісності з інструментами.

Увімкніть app.component.scss, *.scssякщо потрібно , імпортуйте свої . _colors.scssмає деякі загальні значення кольору:

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

Застосувати правило до всіх компонентів

Всі кнопки, які мають btn-redклас, будуть стилізовані.

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

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

Застосуйте правило до одного компонента

Усі кнопки, що мають btn-redклас на app-loginкомпоненті, будуть стилізовані.

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

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

-1

Я вирішив це за межами Angular. Я визначив спільний scss, який я імпортую своїм дітям.

shared.scss

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

child.scss

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

Мій запропонований підхід - це спосіб вирішити проблему, про яку поставила ОП. Як неодноразово згадувалося, :: ng-deep,: ng-хост знеціниться, а відключення інкапсуляції - це, на мій погляд, занадто багато витоку коду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.