Angular komponent – ViewEncapsulation

Opublikowane przez lewy w dniu

Tworząc komponenty za pomocą Angulara dostajemy możliwość zarządzania kapsułkowaniem styli dla komponentów. W rzeczywistości oznacza to, że dostajemy możliwość zarządzania tym jak style z danego komponentu mają wpływać na resztę aplikacji (inne komponenty).

Sposób kapsułkowania w komponencie ustawiamy za pomocą właściwości encapsulation obiektu metadanych komponentu

@Component({
    templateUrl: './first.component.html',
    selector: 'app-first',
    styleUrls: ['./first.component.scss'],
    encapsulation: ViewEncapsulation.Emulated, //ShadowDom, None, Native
})
export class FirstComponent { ... }

Jak widzimy Angular dostarcza 4 sposoby kapsułkowania styli dla komponentu. Domyślnym sposobem (czyli jeżeli nie podamy parametru encapsulation) jest Emulated, dodatkowo dostępne mamy ShadowDom, None i Native. Poniżej opiszę pierwsze 3, Native pominę ponieważ jest już deprecated.

ViewEncapsulation.Emulated

Jest to domyślna wartość kapsułkowania styli w Angular i charakteryzuje się tym, że style są domknięte w obrębie danego komponentu. Spójrzmy na przykład

// first.component.ts
@Component({
    templateUrl: './first.component.html',
    selector: 'app-first',
    styleUrls: ['./first.component.scss'],
    encapsulation: ViewEncapsulation.Emulated, //ShadowDom, None, Native
})
export class FirstComponent { ... }

//first.component.html
<p>
    first works!
</p>

//first.component.scss
p {
    color: #ff0000;
}

jeżeli teraz uruchomimy taką aplikację to jak można się spodziewać odpali nam się strona z czerwonym napisem ‚first works!’, jednak to co jest istotne, jest do zauważeniu w DevToolsach

jak widzimy Angular sam za nas dodał do elementów z komponentu atrybut „_ngcontent-c1” oraz w stylach zamieszczonych w sekcji head strony dodał style z komponentu również z odpowiednim atrybutem.

Jeżeli teraz zmodyfikujemy lekko nasz przykład

//first.component.html
<p>
    first works!
</p>
<app-second></app-second>

// second.component.ts
@Component({
    templateUrl: './second.component.html',
    selector: 'app-second',
    encapsulation: ViewEncapsulation.Emulated,
})
export class SecondComponent { ... }

//second.component.html
<p>
    second works!
</p>

Komponent SecondComponent nie ma pliku ze stylami ale jest zagnieżdżony wewnątrz komponentu FirstComponent, style w head nie ulegają zmianie a wygenerowany kod wygląda następująco

Jak widzimy Angular dla elementów z drugiego komponentu nie dodał już atrybutów (nie było potrzeby, nie umieściliśmy żadnych styli) i równocześnie style pierwszego komponentu – rodzica nie mają wpływu na wygląd elementów w komponencie dziecku.

ViewEncapsulation.ShadowDom

Jeżeli natomiast chcemy aby style z komponentu rodzica miały wpływ na komponenty dzieci powinniśmy użyć ViewEncapsulation.ShadowDom.

Przykład ten sam co powyżej, jedyna różnica taka że w komponencie first.component.ts zmieniam encapsulation na ViewEncapsulation.ShadowDom, to co istotne się zmienia w wygenerowanym kodzie to z sekcji head strony znikają informacje o stylach dla naszych komponentów, a zamiast tego Angular używając ShadowDom tworzy nam odpowiednią sekcję na stronie pod którą umieszcza jej wygląd oraz style

Jak widać komponenty tracą atrybuty wygenerowane wcześniej przez Angulara ale dzięki ShadowDom wszystko domknięte jest w jego obrębie komponentu i nie ma wpływu na inne (nie dzieci) komponenty naszej aplikacji.

ViewEncapsulation.None

Ostatnim opisanym sposobem kapsułkowania jest ViewEncapsulation.None, zwany przeze mnie „rakiem” – jeżeli komponent na w swoich metadanych encapsulation ustawione na ViewEncapsulation.None wówczas style z tego komponentu są dodawane do sekcji head strony beż żadnych dodatkowych atrybutów, co powoduje że są używane przez wszystkie elementy aplikacji, spójrzmy na przykład (zmiana w first.component.ts encapsulation na ViewEncapsulation.None)

Jak widzimy Angular nie generował żadnych dodatkowych atrybutów dla komponentów, kod stylu z komponentu FirstComponent przeniósł do sekcji styli w head i teraz każdy paragraf (nie posiadający dodatkowego atrybutu!) będzie miał kolor czerwony.

Jeżeli zmodyfikujemy minimalnie SecondComponent

// second.component.ts
@Component({
    templateUrl: './second.component.html',
    selector: 'app-second',
    styleUrls: ['./second.component.scss'],
    encapsulation: ViewEncapsulation.None,
})
export class SecondComponent { ... }

//second.component.html
<p>
    second works!
</p>

//second.component.scss
p {
    color: #0000ff;
}

To w wygenerowanym kodzie zmianie ulegnie sekcja head, dodany zostanie styl z SecondComponent, jednak ponownie jako paragraf (bez dodatkowych atrybutów), sekcja ta zostanie dodana jako druga przez co przysłoni pierwszą deklarację wyglądu dla paragrafu i tym razem tekst będzie wyświetlany w kolorze niebieskim.

Oczywiście, jeżeli pierwszy komponent będzie miał encapsulation ustawione na ViewEncapsulation.Emulated to Angular dla tego komponentu wygeneruje odpowiednie atrybuty i mimo, że drugi z komponentów miał ustawione None to jego style nie będą nadpisywały styli w komponencie z ViewEncapsulation.Emulated.

Problem może zacząć się rodzić gdy w komponencie nie damy styli dla jakiś elementów a style dla tych atrybutów pojawią się gdzieś indziej w aplikacji w komponencie z ViewEncapsulation.None wówczas może pojawić się słynne WTF i szukanie problemu.