Как стилизовать дочерние компоненты из CSS-файла родительского компонента?


266

У меня есть родительский компонент:

<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

Обновление - Новейший путь

Не делай этого, если можешь этого избежать. Как отмечает Девон Санс в комментариях: эта функция, скорее всего, будет устаревшей.

Обновление - новый путь

Начиная с версии Angular 4.3.0 , все комбинаторы для пирсинга CSS устарели. Команда Angular представила новый комбинатор::ng-deep (все еще на экспериментальном уровне, а не полным и окончательным способом), как показано ниже,

ДЕМО: 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
Команда Angular также планирует отказаться от поддержки :: ng-deep. Из их документов: «Пронзающий теневой комбинатор устарел, и поддержка удаляется из основных браузеров и инструментов. Поэтому мы планируем отказаться от поддержки в Angular (для всех 3 из / deep /, >>> и :: ng- deep). До тех пор :: ng-deep следует предпочитать для более широкой совместимости с инструментами. " angular.io/guide/component-styles#deprecated-deep--and-ng-deep .
Девон

5
Пока это остается принятым ответом, люди будут вводить в заблуждение. :: ng-deep не следует использовать в качестве точек @DevonSams в комментарии выше.
Костас Сиабанис

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

11
Снижение стоимости без предоставления альтернативы, вероятно, не лучшее решение.
Техливи

56

ОБНОВЛЕНИЕ 3:

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

Прокомментируйте, если у вас есть понимание этого.

ОБНОВЛЕНИЕ 2:

С тех пор /deep/и все остальные теневые пирсинг селекторы теперь устарели. Угловой упал, ::ng-deepкоторый следует использовать вместо для более широкой совместимости.

ОБНОВИТЬ:

Если вы используете Angular-CLI, вам нужно использовать /deep/вместо этого, >>>иначе он не будет работать.

ОРИГИНАЛ:

После перехода на страницу Github на Angular2 и случайного поиска «style» я нашел этот вопрос: Angular 2 - styleHTML

Который сказал, чтобы использовать то, что было добавлено в 2.0.0-beta.10, >>>и ::shadowселекторы.

(>>>) (и эквивалент / deep /) и :: shadow были добавлены в 2.0.0-beta.10. Они похожи на CSS-комбинаторы теневого DOM (которые устарели) и работают только с инкапсуляцией: ViewEncapsulation.Emulated, которая используется по умолчанию в Angular2. Вероятно, они также работают с ViewEncapsulation.None, но затем игнорируются только потому, что в них нет необходимости. Эти комбинаторы являются лишь промежуточным решением, пока не поддерживаются более продвинутые функции для многокомпонентного стиля.

Так просто делаешь

:host >>> .child {}

В parentроссийском файле стилей решена проблема. Обратите внимание, что, как указано в приведенной выше цитате, это решение является промежуточным, пока не поддерживается более продвинутый межкомпонентный стиль.


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

42

Вы не должны использовать ::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
Это лучший ответ IMO, так как он на самом деле является жизнеспособной альтернативой тому, который скоро будет объявлен устаревшим ::ng-deep. Как правило, компоненты в любом случае имеют свой собственный селектор ( <my-component>, <div my-component>и т. Д.), Поэтому нет необходимости в элементе-обертке со специальным классом.
Алекс Уокер

@AlexWalker Это может быть лучшим ответом для вашей ситуации, но стоит упомянуть, что он отвечает только на половину вопроса ОП: «Этот метод позволяет CSS распространяться как обычно сверху вниз, но благодаря выбрасыванию ВСЕЙ инкапсуляции не Не ограничивайте этот стиль для детей определенного родителя . Если вы стилизуете потомков parent1 одним способом, а потомками parent2 - другим, эти правила CSS теперь будут бороться друг с другом в обоих местах. Это может быть ошеломляюще болезненным (и Angular добавил инкапсуляцию, чтобы избежать этого).
Ерф

@ruffin Именно поэтому я добавил предупреждение в своем ответе, чтобы понять смысл использования этого метода и как «вручную инкапсулировать» с помощью тега top css на вашем компоненте
Тонио

1
@ Тонио - Да, согласился; отвечал прямо Алексу, а не тебе. Его комментарий « так что даже нет необходимости в элементе-обертке со специальным классом » меня немного напугал. Может быть, для конкретной ситуации, но есть причина, по которой Angular «тратит» время на поддержку инкапсуляции. Этот ответ является работоспособным решением в конкретных случаях, но, как вы говорите, в целом потенциально опасен. Решение MatthewB , например, стилирует дочерние элементы , сохраняя инкапсуляцию (но это становится действительно грязным, если у вас есть более одного поколения дочерних компонентов).
Ерф

19

К сожалению, кажется, что / deep / selector устарел (по крайней мере, в Chrome) https://www.chromestatus.com/features/6750456638341120

Короче говоря, похоже, что (в настоящее время) нет долгосрочного решения, кроме как каким-то образом заставить ваш дочерний компонент динамически стилизовать вещи.

Вы можете передать объект стиля своему ребенку и применить его через:
<div [attr.style]="styleobject">

Или, если у вас есть определенный стиль, вы можете использовать что-то вроде:
<div [style.background-color]="colorvar">

Дополнительные обсуждения, связанные с этим: https://github.com/angular/angular/issues/6511


16

Возникла та же проблема, поэтому, если вы используете angular2-cli с scss / sass, используйте «/ 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 {}

Надеюсь это поможет!

codematrix


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 для дочерних элементов компонента в родительском компоненте, поскольку компонент Angular является автономной сущностью, которая должна явно декларировать то, что доступно для внешнего мира. Если в будущем дочерний макет изменится, ваши стили для элементов этого дочернего компонента, разбросанных по файлам 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только потому, что его значение по умолчанию - то, что приводит к вопросу OP. Вам не нужно присваивать другой 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;
}

ребенок-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>

Angular осуждает все способы воздействия на дочерние стили от родителей.

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 он является родным, вы просто должны запросить его внедрение в конструкторе. ;), в более старшем
возрасте

0

По мере обновления интернета я сталкивался с решением.

Сначала несколько предостережений.

  1. Все еще не делай этого. Чтобы уточнить, я не планировал бы дочерние компоненты, позволяющие вам стилизовать их. SOC. Если вы, как разработчик компонентов, хотите разрешить это, тогда вам предоставляется больше возможностей.
  2. Если ваш ребенок не живет в тени дома, это не сработает для вас.
  3. Если вам нужно поддерживать браузер, в котором нет теневого домена, это также не сработает для вас.

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


-1

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

Пронзающий теневой комбайнатор устарел, и поддержка удаляется из основных браузеров и инструментов. Таким образом, мы планируем отказаться от поддержки в Angular (для всех 3 of / 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-host будет устаревать, и отключение инкапсуляции - это слишком большая утечка кода, на мой взгляд.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.