Jako frontend deweloperzy jesteśmy odpowiedzialni za różnego rodzaju funkcjonalności w aplikacjach internetowych. Moim zdaniem drugą najważniejszą rzeczą zaraz po logice biznesowej jest zarządzanie stylami w aplikacji. To właśnie dzięki wielu design systemom, nasze aplikacje mogą wyglądać tak, by zachęcić użytkowników do interakcji z produktem.
Zajrzymy jakie ficzery oferuje nam Angular i SCSS w kontekście CSS’a. Po przeczytaniu tego artykułu będziesz wiedział czym jest enkapsulacja stylów, jakie selektory SCSS możemy użyć w Angularze oraz czym jest design system.
Czym są Style Globalne?
Na początek zacznijmy od czegoś prostego – stylów globalnych. Style globalne są to style, które aplikowane są do elementów w obrębie całej aplikacji, bez znaczenia na położenie pliku HTML.
Dla przykładu: jeżeli w domyślnym pliku z globalnymi stylami styles.scss zadeklarujemy klasę .red-highlight, to będziemy mogli się do niej odnieść w każdym komponencie wewnątrz naszej aplikacji tak aby ostylować ten element (wyjątkiem są komponenty używające enkapsulacji ShadowDom).
styles.scss
1 2 3 |
.red-highlight { background-color: #ff0000; } |
app.component.html
1 |
<div class="red-highlight">Example Box</div> |
Jako rezultat powyższego kodu w przeglądarce zobaczymy tekst Example Box z czerwonym tłem. Jak możesz zauważyć używanie stylów globalnych jest banalnie proste.
W tych stylach globalnych powinniśmy zrobić takie rzeczy jak:
- zadeklarować wszelkiego rodzaju utility klasy (typografia, marginesy, padding)
- zaaplikować CSS reset, tak aby zatrzeć różnice w wyświetlaniu elementów przez różne przeglądarki. W rezultacie zaaplikowania CSS resetu margin-bottom dla paragrafów będą jednakowe zarówno w safari jak i w chrome.
- zainicjalizować zmienne CSS/SCSS
- zainicjalizować theme z bibliotek ui (@angular/material)
- nadpisać/modyfikować style z bibliotek ui (np @angular/material)
Konfiguracja Stylów Globalnych
Skoro już wiemy czym są style globalne, możemy przejść do ich konfiguracji. Ta różni się lekko w zależności od tego czy używamy biblioteki @nrwl/nx w projekcie. Style globalne skonfigurujemy w pliku:
- angular.json (Jeżeli nie używamy Nx’a)
- project.json (Jeżeli używamy Nx’a)
W przypadku pliku angular.json ścieżki do arkusza stylów globalnych zadeklarujemy w json’ie pod ścieżką:
projects > (nazwa projektu) > architect > (konfiguracja) > options > styles
angular.json
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "projects": { "ExampleProject": { "architect": { "build": { "options": { "styles": ["src/styles.scss"] } } } } } } |
W przypadku pliku project.json zadeklarujemy ścieżki pod kluczem:
targets > (konfiguracja) > options > styles
project.json
1 2 3 4 5 6 7 8 9 |
{ "targets": { "build": { "options": { "styles": ["apps/ourApplication/src/styles/styles.scss"] } } } } |
Domyślnie każda angularowa aplikacja ma zaimportowany tylko jeden plik ze stylami globalnymi – styles.scss znajdujący się w folderze src. W liście stylów powinniśmy zaimportować style z zewnętrznych bibliotek UI np takich jak @angular/material czy bootstrap.
Dodanie Stylu Globalnego (Bootstrap)
Do przykładu konfiguracji stylów globalnych posłużę się biblioteką bootstrap. Biblioteka ta dostarcza nam utility klasy oraz gotowe już komponenty, które znacznie przyśpieszają proces budowania UI dla aplikacji. Możemy ją dodać do naszego projektu za pomocą komendy:
1 |
npm install bootstrap bootstrap-icons |
Po zainstalowaniu biblioteki zarejestrujemy style globalne bootstrapa w pliku angular.json lub project.json w zależności od naszego stacku technologicznego.
1 2 3 4 5 |
"styles": [ "node_modules/bootstrap/scss/bootstrap.scss", "node_modules/bootstrap-icons/font/bootstrap-icons.css", "src/styles.scss" ] |
Każda ingerencja w konfigurację styli globalnych wymaga od nas zrestartowania skryptu ng serve w celu zaaplikowania zmian. Po ponownym uruchomieniu powinniśmy być w stanie używać klas i komponentów pochodzących z bootstrapa. (Przy instalacji bootstrapa należy również zaimportować do projektu plik js)
Jak mogłeś zauważyć konfiguracja stylów globalnych jest banalnie prosta! Przejdźmy do czegoś troszeczkę bardziej skomplikowanego – importowania stylów do komponentu.
Stylowanie Komponentów
W większości przypadków każdy komponent w Angularze posiada osobny “lokalny” plik ze stylami. Dzięki takiemu podejściu aplikacje zachowują modularność i mają przejrzystą strukturę.
Importowanie stylów w komponencie
Aby zaimportować jeden plik lub kilka plików ze stylami możemy użyć następujących propsów wewnątrz dekoratora @Component:
- styleUrls – przyjmuje listę względnych ścieżek do plików ze stylami.
- styleUrl – przyjmuje ścieżkę do pliku jako w string. Warto zaznaczyć, że props jest dostępny dopiero od angulara 17.0.0-next.4
1 2 3 4 5 |
@Component({ ..., styleUrls: ['./example-component.scss'], }) export class ExampleComponent { } |
W przypadkach gdzie chcemy podać CSS bezpośrednio w pliku z komponentem to możemy użyć propsa styles wewnątrz dekoratora @Component. Props ten przyjmuje listę stylów. Warto jednak dodać, że od Angulara 17.0.0-next.4 wartość propsa może również być stringiem.
1 2 3 4 5 6 7 8 9 |
@Component({ ..., styles: [` .box { height: 1000px !important; } `] }) export class AppComponent {} |
Od siebie chciałbym dodać, że w świecie Angulara pisanie stylów w pliku z komponentem jest rzadką praktyką. Po to rozwiązanie powinniśmy sięgać tylko wtedy, gdy kod CSS naszego komponentu jest bardzo krótki.
Różne Rodzaje Enkapsulacji Stylów
Skoro już wiemy jak zaimportować style do komponentu, możemy przejść do typów enkapsulacji stylów. Każdy typ enkapsulacji działa zupełnie inaczej – warto znać różnice pomiędzy nimi, aby uniknąć przykrych niespodzianek w przyszłości.
Na chwilę obecną możemy skorzystać z następujących enkapsulacji:
- Emulated (Domyślna)
- ShadowDOM
- None
Enkapsulacja Emulated – Lokalne Style (domyślna)
Ta enkapsulacja sprawia, że zaimportowane style będą wpływać tylko na elementy wewnątrz templatki naszego komponentu. Dzięki takiemu zachowaniu styli możemy być pewni, że style komponentu A nie nadpiszą styli komponentu B. Ta cecha sprawia, że aplikacje oparte o tą enkapsulację styli są niezawodne – wraz z dodawaniem kolejnych ficzerów do aplikacji całkowicie unikamy błędów wizualnych spowodowanych nadpisywaniem się klas.
Enkapsulację Emulated możemy zadeklarować w property encapsulation dekoratora @Component. Jest to jednak opcjonalne, ponieważ Angular wykorzystuje ten typ enkapsulacji domyślnie dla każdego komponentu.
1 2 3 4 5 |
@Component({ ..., encapsulation: ViewEncapsulation.Emulated }) export class AppComponent {} |
Enkapsulacja ShadowDOM
Enkapsulacja ShadowDOM – W przypadku wykorzystania tej enkapsulacji komponent zostanie stworzony w specjalnym Shadow Roocie. Komponenty wykorzystujące ten typ enkapsulacji są izolowane od głównego DOM’u przez co NIE BĘDĄ mogły używać stylów globalnych.
Przy użyciu enkapsulacji ShadowDOM warto mieć pod uwagą kompatybilność przeglądarek (link).
1 2 3 4 5 |
@Component({ ..., encapsulation: ViewEncapsulation.ShadowDom }) export class AppComponent {} |
Shadow root’y w drzewie DOM są zwrappowane w element #shadow-root.
Powyższy screen przedstawia DOM po włączeniu enkapsulacji ShadowDOM w komponencie app-root.
Enkapsulacja None
Ostatnim typem enkapsulacji jest Enkapsulacja None, która sprawia, że wszystkie style w naszym komponencie będą globalne.
Wykorzystywanie enkapsulacji none jest bardzo ryzykowne, ponieważ wraz z dodawaniem nowych ficzerów możemy doświadczyć wizualnej regresji – nieoczekiwanej zmiany wyglądu komponentu.
W przypadku enkapsulacji None wizualnej regresji doświadczymy, gdy zadeklarujemy 2 klasy o tej samej nazwie. W miarę możliwości ograniczyłbym użycie Enkapsulacji None – znacznie bardziej bezpiecznym rozwiązaniem jest korzystanie z Enkapsulacji Emulated.
1 2 3 4 5 |
@Component({ ..., encapsulation: ViewEncapsulation.None }) export class AppComponent {} |
W przypadkach gdzie decydujemy się na użycie wyłączonej enkapsulacji warto wrapować style w unikalną klasę, która nie powtórzy się w aplikacji. Dobrym pomysłem jest użycie nazwy komponentu jako nazwy dla klasy wrapującej – dzięki temu szansa na przypadkowe powtórzenie klasy jest mała. Dla przykładu:
File: color-picker.component.ts
1 2 3 4 |
<div class="color-picker"> <input class="hex-input"/> <input class="rgb-input"/> </div> |
File: styles.scss (globalne style)
1 2 3 4 5 6 7 8 |
.color-picker { .hex-input { ... } .rgb-input { ... } } |
Selektory w komponencie
W naszych aplikacjach możemy używać specjalnych selektorów w stylach, które pozwalają nam targetować tylko wybrane elementy.
Selektor :host
Ten selektor pozwala nam na stylowanie tagu naszego komponentu.
1 2 3 4 5 6 |
:host { display: block; height: 100px; width: 100px; background-color: #53e1d0; } |
Dla powyższego pliku CSS zaimportowanego do komponentu z użyciem selektora app-card powyższy kod SCSS zostanie skompilowany do poniższego kodu CSS:
Domyślnie każdy Angularowy komponent jest inline więc użycie tego selektora do ustawienia display:block jest stosunkowo popularne.
Warto zauważyć, że selektor :host ma swoje ograniczenie oraz działa tylko wtedy, kiedy nasz komponent używa enkapsulacji Emulated lub ShadowDOM. W przypadku enkapsulacji None będziemy musieli lekko zmodyfikować powyższy kod aby zaaplikować style do naszego elementu:
1 2 3 4 5 6 |
app-root { display: block; height: 100px; width: 100px; background-color: #53e1d0; } |
Selektor :host-context
Ten selektor pozwala nam na stylowanie elementu hostującego komponent na podstawie klasy w elemencie rodzica. Możemy użyć tej funkcjonalności np. do stylowania naszego komponentu w zależności od klasy theme w body.
Dla przykładu w poniższy styl zostanie zaaplikowany do klasy my-button wewnątrz naszego komponentu tylko wtedy, kiedy jeden z przodków (w naszym przypadku element body) będzie miał klasę dark-theme
1 2 3 4 5 6 7 8 |
:host-context(.dark-theme) .my-button { display: block; height: 50px; width: 100px; background-color: #4bd58d; font-weight: bold; border-radius: 12px; } |
Selektor ::ng-deep
Za pomocą selektora ::ng-deep mogliśmy stylować elementy dzieci naszego komponentu. Warto zauważyć, że ten selector jest oznaczony jako deprecated (dokumentacja ng-deep). Zdania na temat tego czy powinniśmy używać ng-deep są podzielone – znajdziemy zarówno wielu zwolenników jak i przeciwników tego selektora.
Design sytem – globalne zmienne SCSS
Bardzo dobrą praktyką w tworzeniu aplikacji internetowych jest korzystanie z design systemu – zestawu wspólnych wartości dla aplikacji. Wartościami takimi mogą być chociażby jednostki odległości, kolory czy typografia.
W tej sekcji artykułu zaimplementujemy prosty design systemu z pomocą zmiennych SCSS, które będą przechowywać kolory, jednostki długości, a nawet breakpointy!
Aby móc skorzystać z globalnych zmiennych i mixinów musimy odpowiednio skonfigurować aplikację w angular.json lub project.json. Pod sekcją w której dodawaliśmy globalne style dodajemy 2 nowe sekcje: stylePreprocessorOptions oraz includePaths. Od teraz po restarcie CLI Angular pozwoli nam na import zmiennych i utili w stylach lokalnych za pomocą ścieżki zaczynającej się w katalogu “src”
1 2 3 4 5 6 7 8 9 |
"styles": [ "src/styles.scss", "src/styles/utils/index.scss" ], "stylePreprocessorOptions": { "includePaths": [ "src" ] }, |
Zacznijmy od stworzenia struktury plików:
- Stwórzmy folder styles oraz zagnieżdżony w nim folder utils
- Wewnątrz folderu utils stwórzmy 3 pliki SCSS – _breakpoints, _colors oraz _spacing
- Dodajmy plik index.scss wewnątrz folderu utils
Nasza struktura powinna wyglądać w następujący sposób w drzewie folderów:
Po tym jak stworzyliśmy pliki możemy przejść do napisania naszego design systemu. Zacznijmy od zdefiniowania naszej palety kolorów w pliku _colors.scss. Aby ułatwić sobie proces ich dobierania możemy posłużyć się generatorami palet online czy stworzonych już palet kolorów (tailwind, material design). Przy doborze kolorów najlepiej trzymać się podejścia gdzie komponujemy nasze UI z:
- Koloru przewodzącego (od ang. primary color)
- Koloru akcentującego (od ang. accent color)
- Koloru ostrzegającego (żółty/pomarańczowy) (od ang. warn color)
- Koloru do oznaczenia błędów (czerwony) (od ang. error color)
Podczas tworzenia elementów UI powinniśmy również zaaplikować zasadę 60/30/10. Zasada ta polega na dobieraniu kolorów interfejsu w taki sposób, aby ui składało się w:
- 60% z podstawowego koloru (najczęściej jest to kolor naturalny tj. biały/szary/czarny)
- 30% z koloru przewodzącego
- 10% z koloru akcentującego
Stosując tą zasadę nasze UI będzie bardzo przejrzyste, a wszystkie elementy interaktywne (np. buttony) z kolorem do akceptacji opcji będą się z łatwością wyróżniać i wręcz prosić o kliknięcie!
Przykładowy plik _colors.scss może wyglądać tak:
File: _colors.scss
1 2 3 4 5 6 7 8 9 10 |
$primary-color: #2bec89; $accent-color: #8a5cc2; $alert-color: #ff9012; $error-color: #e11b1b; // Declaring color with shades (material design pink) $pink-color-50: #fce4ec; // ... Shades 100-700 $pink-color-800: #ad1457; $pink-color-900: #880e4f; |
Nasza aplikacja poza jednolitym systemem kolorów powinna również posiadać jednakowy spacing, który będzie odpowiednio oddzielał elementy UI. Generalna zasada jest taka, by używać zawsze spacingu 8/12px w obrębie jednego komponentu UI oraz większego spacingu w celu oddzielenia komponentów od siebie.
Przykładowy plik ze space’ingiem:
File: _spacing.scss
1 2 3 4 5 |
$space-xs: 4px; $space-s: 8px; $space-md: 12px; $space-lg: 16px; $space-xl: 20px; |
Ostatnią rzeczą, którą zadeklarujemy w naszym design systemie będą breakpointy. Przy pisaniu aplikacji powinniśmy skupić się na 3 viewportach:
- smartfony
- tablety / notebooki z małym ekranem
- monitory komputerowe / telewizory
Warto stworzyć sobie kilka zmiennych przechowujących wyżej wymienione breakpointy. Przydatne okażą się też mixiny, które z łatwością pozwolą nam pisać responsywne UI.
Przykładowy plik z breakpointami może wyglądać w taki sposób:
File: _breakpoints.scss
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$breakpoint-tablet: 768px; $breakpoint-desktop: 1024px; @mixin mobile-view { @media screen and (max-width: #{$breakpoint-tablet - 1px}) { @content; } } @mixin tablet-view { @media screen and (min-width: #{$breakpoint-tablet}) and (max-width: #{$breakpoint-desktop - 1px}) { @content; } } @mixin desktop-view { @media screen and (min-width: #{$breakpoint-desktop}) { @content; } } |
To by było na tyle z naszego małego design systemu.
Nadszedł czas na eksport zmiennych z wszystkich plików. Możemy to zrobić poprzez użycie @forward w pliku index.scss. Będziemy jeszcze musieli również użyć @forward w globalnym pliku ze stylami.
File: index.scss
1 2 3 |
@forward "./breakpoints"; @forward "./colors"; @forward "./spacing"; |
Aby użyć naszego design systemu wewnątrz komponentu skorzystamy z @use, w którym deklarujemy ścieżkę do folderu w którym znajduje się plik index.scss.
File: app.component.scss
1 2 3 4 5 6 7 8 9 10 |
@use "styles/utils" as ds; .box { padding: ds.$space-md; background-color: ds.$primary-color; @include ds.desktop-view { padding: ds.$space-lg; } } |
Warto pamiętać, że tworzenie własnego design systemu wymaga od nas pisania dużej ilości boiler-plate kodu. Na rynku są już dostępne narzędzia takie jak np: Tailwind CSS, które implementują własny design system i pozwalają nam na dostosowanie go pod nasze potrzeby.
Dyrektywy
Dyrektywy to angularowa struktura, za pomocą której możemy pisać reużywalny kod odpowiedzialny za manipulowanie zachowaniami oraz wyglądem elementu w naszej aplikacji.
W tej części artykułu stworzymy dyrektywy, które pozwolą nam na zaaplikowanie tęczowego tła dla naszego elementu. Spojrzymy na 3 różne sposoby, pozwalające nam na manipulowanie stylami elementu. Nasze dyrektywy zastosują takie technologie jak:
- Wrapper natywnego elementu HTML ElementRef
- dekorator @HostBinding
- property “host” dyrektywy/komponentu
Zacznijmy więc! Stwórzmy za pomocą CLI 3 dyrektywy
1 2 3 |
ng g d directives/rainbowElementRef --standalone --skip-tests ng g d directives/rainbowHostBinding --standalone --skip-tests ng g d directives/rainbowHostProperty --standalone --skip-tests |
Po stworzeniu dyrektyw powinniśmy mieć następującą strukturę plików:
Zanim przejdziemy do implementacji logiki dyrektyw stwórzmy mały setup dla naszego kodu. Zacznijmy od zadeklarowania globalnego stylu nadającego naszemu elementowi tęczowe tło w pliku styles.scss.
1 2 3 4 5 6 7 8 9 10 11 |
@keyframes rainbow { 0% { background-position: 0 50% } 50% { background-position: 100% 50% } 100% { background-position: 0 50% } } .rainbow-background { background: linear-gradient(238deg, #fd8800, #fd008f, #9700fd, #003dfd, #05c7e6, #4bd58d); background-size: 1200% 1200%; animation: rainbow 5s ease infinite; } |
Następnie importujemy nasze dyrektywy w app.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 |
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], standalone: true, imports: [ RainbowElementRefDirective, RainbowHostBindingDirective, RainbowHostPropertyDirective ] }) export class AppComponent {} |
Oraz finalnie dodajmy 3 divy w głównym widoku naszej aplikacji, tak aby na bieżąco widzieć jak dyrektywy wpływają na elementy HTML.
File: app.component.html
1 2 3 |
<div class="box" appRainbowHostProperty></div> <div class="box" appRainbowHostBinding></div> <div class="box" appRainbowElementRef></div> |
File: app.component.scss
1 2 3 4 5 6 |
.box { border-radius: 12px; margin: 32px; width: 64px; height: 64px; } |
Okej, to by było na tyle z setupu naszej aplikacji. Przejdźmy do implementacji dyrektyw – zaczniemy od tej używającej ElementRef.
Element Ref i Natywny Element
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import {Directive, ElementRef, inject, Input, OnInit} from "@angular/core"; @Directive({ selector: '[appRainbowElementRef]', standalone: true, }) export class RainbowElementRefDirective implements OnInit { @Input() set duration(duration: number) { this._elementRef.nativeElement.style.animationDuration = `${duration}s` } @Input() set hideBackground(hideBackground: boolean) { const CLASS_NAME = 'rainbow-background' if (hideBackground) { this._elementRef.nativeElement.classList.remove(CLASS_NAME) } else { this._elementRef.nativeElement.classList.add(CLASS_NAME) } } private readonly _elementRef = inject(ElementRef<HTMLElement>) ngOnInit(): void { this.duration = 5; this.hideBackground = false; } } |
Podzielmy sobie logikę tego komponentu na 3 sekcje:
Inicjalizacja:
- Najpierw wstrzykujemy ElementRef i przypisujemy go do zmiennej _elementRef
Logika Stylów:
- Tworzymy @Input duration, który przy bindowaniu wartości ustawi czas trwania animacji
- Tworzymy @Input hideBackground, który w zależności od wartości usunie/doda klasę “rainbow-background” do naszego elementu
Domyślne Wartości
- Dodajemy do klasy interfejs OnInit oraz implementujemy metodę ngOnInit, w której podajemy domyślne wartości inputów w naszym komponencie.
Po uruchomieniu kodu w przeglądarce powinniśmy zobaczyć, że nasz element uzyskał tęczowe tło!
W mojej opinii używanie ElementRef jest bardzo toporne – przy stylowaniu elementów trzeba wykonywać wiele rzeczy manualnie. Wyżej wymieniona klasa przyda nam się, gdy chcemy napisać logikę do bardziej zaawansowanych ficzerów np. Elementu Badge. Na całe szczęście w większości przypadków możemy skorzystać z prostszych ficzerów angulara do stylowania elementów – np dekoratora @HostBinding.
Dekorator @HostBinding
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import {Directive, HostBinding, Input} from "@angular/core"; @Directive({ selector: '[appRainbowHostBinding]', standalone: true, }) export class RainbowHostBindingDirective { @Input() duration = 5 @Input() hideBackground = false @HostBinding('style.animationDuration') get animationDuration(): string { return `${this.duration}s` } @HostBinding('class.rainbow-background') get showRainbowBackground(): boolean { return !this.hideBackground } } |
Trochę lepszym sposobem na stylowanie elementów z użyciem dyrektyw jest wykorzystanie dekoratora @HostBinding. Z jego pomocą Angular automatycznie przypisze atrybuty do naszego elementu zgodnie z wartością zmiennej/gettera.
Spójrzmy na powyższy przykład:
Inicjalizacja:
- Deklarujemy dwa inputy – duration i hideBackground.
Logika Stylów
- Bindujemy styl animationDuration do elementu za pomocą dekoratora @HostBinding. Styl animation-duration na naszym elemencie będzie miał taką samą wartość jak getter animationDuration (domyślnie “5s”).
- Bindujemy klasę rainbow-backgrond, która będzie widniała na elemencie tylko wtedy kiedy @Input hideBackground naszej dyrektywy będzie równy false.
Jak z pewnością widzisz, dekorator @HostBinding pozwala nam na znaczne skrócenie logiki stylowania. Pytanie jest, czy możemy zrobić coś jeszcze lepiej?
Host Property Dyrektywy/Komponentu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import {Directive, Input} from '@angular/core'; @Directive({ selector: '[appRainbowHostProperty]', standalone: true, host: { '[class.rainbow-background]': '!hideBackground', '[style.animationDuration]': 'duration + "s"', } }) export class RainbowHostPropertyDirective { @Input() duration = 5 @Input() hideBackground = false } |
Property host wewnątrz dekoratora naszej dyrektywy pozwala nam na bardzo czytelne bindowanie atrybutów do naszego komponentu. W powyższym przykładzie:
Inicjalizacja:
- Deklarujemy dwa inputy – duration i hideBackground.
Logika Stylów:
- Bindujemy klasę rainbow-background do elementu, kiedy hideBackground będzie równe false
- Bindujemy styl animationDuration do wartości zmiennej duration oraz dodajemy “s”.
Podsumowanie
Od teraz style w Angularze nie powinny skrywać przed tobą tajemnic. W mojej opinii Angular dostarcza nam dużo ficzerów w kontekście CSS’a. Dzięki selektorom, scope’owanych stylach czy globalnych zmiennych SCSS jesteśmy w stanie budować interfejsy użytkownika nastawione przede wszystkim na spójność.
Dziękuję 🙂
Jeżeli chodzi o ::ng-deep, to jestem jego zwolennikiem! Nie rozumiem, dlaczego oznaczono go jako “deprecated” :/
Przydatny artykuł, Dawid!
Dzięki za komentarz Paweł. Według mnie do deprykacji przyczyniło się kilka rzeczy:
– na przestrzeni ostatnich lat zwiększyła się możliwość manipulacji CSS’em w bibliotekach z zewnętrznymi komponentami tj. angular material. (a to własnie w libkach najczęściej używaliśmy ::ng-deep).
Przykładem może być chociażby materialowy dialog
– powstawanie dziwnych bugów wizualnych. Użycie ::ng-deep w lazy-loaded modułach permanentnie aplikuje style dla całej aplikacji po załadowaniu modułu. Może to powodować wizualną regresję, ciężką do zdebugowania.
Zobacz np ten przykład (dashboard -> profile -> dashboard)
Osobiście odbieram deprykację jako swojego rodzaju ostrzeżenie dla nieświadomego developera – to co próbujesz zrobić może być już niestosowne i prawdopodobnie istnieje lepszy sposób by osiągnąć zamierzony cel.
Bardzo fajny artykuł, dużo konkretnej wiedzy zebranej w jednym miejscu, kawał dobrej roboty!!