Mit Angular 18 hat endlich das Material Design v.3 einzug in die Angular Welt gehalten. Hier ist insbesondere die verstärkte Verwendung von CSS Variablen ein großer Schritt in Richtung stabiler Designs auch über größere Versions-Sprünge hinweg.
Ein eigenes Design ist erst einmal schnell erstellt, bietet doch die anguar-cli ein entsprechendes Kommando an:
ng generate @angular/material:m3-theme
Nach Angabe der benötigten Basis-Farben erhalten wir eine SCSS Datei, die ein entsprechendes Theme (bzw. ein Dark- und Light-Theme) beinhaltet. Diese müssen wir nun lediglich in unserer globalen style-Datei registrieren:
@use '@angular/material' as mat;
@use '../m3-theme.scss';
@include mat.core();
:root{
@include mat.all-component-themes($light-theme);
}
Die Registrierung eines Dark-Themes für die Materialkomponenten würde nun ergänzend so aussehen:
.dark{
@include mat.all-component-colors($dark-theme);
}
Soweit so gut. Spannender wird es nun, wenn wir unsere eigenen Komponenten auf Basis dieses Themes entwickeln wollen. Speziell, wenn wir ein Light- und Dark-Theme unterstützen wollen. Die flexibelste Lösung, die eine beliebige Anzahl von Themes unterstützt, sieht offiziell nun vor, dass alle Farb-Informationen der Komponente in eine separate SCSS Datei gezogen und mittels Mixin parametrisierbar gemacht werden (auf Basis eines übergebenen Themes). Zusätzlich muss nun für jede dieser Komponente die entsprechende Registrierung, analog zu den Material-Components, in der globalen style-Datei, durchgeführt werden (offizielle Docs). Gerade bei fachlichen Komponenten ist dieses Vorgehen im Projekt oftmals sehr unhandlich. Eine alternative Herangehensweise, die sich einfach in Projekten einsetzen lässt, ist die Verwendung von CSS Variablen anstatt die Farb-Definition auf Basis von übergebenen Themes zu definieren
:root {
@include mat.all-component-themes($light-theme);
--primary: #{mat.get-theme-color($light-theme, primary)};
}
.dark{
@include mat.all-component-colors($dark-theme);
--primary: #{mat.get-theme-color($dark-theme, primary)};
}
Innerhalb der Komponenten verwenden wir nun lediglich diese Variablen:
article{
....
border: 2px solid var(--primary)
}
Nachteile:
- für jedes zu unterstützende Theme muss eine entsprechende CSS Klasse mit allen Variablen definiert werden, was eine nahtlose Integration wie bei Angular Material erschwert
- die Verwendung von CSS-Variablen schließt einige („in die Jahre gekommene“) Browser aus
- Chrome bis v48 (release 2016)
- Edge bis v14 (release 2016)
- Firefox bis v30 (release 2014)
- IE (release 2013)
Eigenes Mixin
Getreu dem Motto „dry“ (Dont Repeat Yourself) erlaubt uns ein eigenes Mixin den Aufwand bei unserer Entwicklung zu minimieren. Hier ein Beispiel in dem alle Farb Rollen, welche durch das Material Design definiert werden in entsprechende Variablen gemappt werden
@mixin materialRoleColorToVariable($theme){
--primary: #{mat.get-theme-color($theme, primary)};
--on-primary: #{mat.get-theme-color($theme, on-primary)};
--primary-container: #{mat.get-theme-color($theme, primary-container)};
--on-primary-container: #{mat.get-theme-color($theme, on-primary-container)};
--primary-fixed: #{mat.get-theme-color($theme, primary-fixed)};
--primary-fixed-dim: #{mat.get-theme-color($theme, primary-fixed-dim)};
--on-primary-fixed: #{mat.get-theme-color($theme, on-primary-fixed)};
--on-primary-fixed-variant: #{mat.get-theme-color($theme, on-primary-fixed-variant)};
--secondary: #{mat.get-theme-color($theme, secondary)};
--on-secondary: #{mat.get-theme-color($theme, on-secondary)};
--secondary-container: #{mat.get-theme-color($theme, secondary-container)};
--on-secondary-container: #{mat.get-theme-color($theme, on-secondary-container)};
--secondary-fixed: #{mat.get-theme-color($theme, secondary-fixed)};
--secondary-fixed-dim: #{mat.get-theme-color($theme, secondary-fixed-dim)};
--on-secondary-fixed: #{mat.get-theme-color($theme, on-secondary-fixed)};
--on-secondary-fixed-variant: #{mat.get-theme-color($theme, on-secondary-fixed-variant)};
--tertiary: #{mat.get-theme-color($theme, tertiary)};
--on-tertiary: #{mat.get-theme-color($theme, on-tertiary)};
--tertiary-container: #{mat.get-theme-color($theme, tertiary-container)};
--on-tertiary-container: #{mat.get-theme-color($theme, on-tertiary-container)};
--tertiary-fixed: #{mat.get-theme-color($theme, tertiary-fixed)};
--tertiary-fixed-dim: #{mat.get-theme-color($theme, tertiary-fixed-dim)};
--on-tertiary-fixed: #{mat.get-theme-color($theme, on-tertiary-fixed)};
--on-tertiary-fixed-variant: #{mat.get-theme-color($theme, on-tertiary-fixed-variant)};
--error: #{mat.get-theme-color($theme, error)};
--on-error: #{mat.get-theme-color($theme, on-error)};
--error-container: #{mat.get-theme-color($theme, error-container)};
--on-error-container: #{mat.get-theme-color($theme, on-error-container)};
--surface-dim: #{mat.get-theme-color($theme, surface-dim)};
--surface: #{mat.get-theme-color($theme, surface)};
--surface-bright: #{mat.get-theme-color($theme, surface-bright)};
--surface-container-lowest: #{mat.get-theme-color($theme, surface-container-lowest)};
--surface-container-low: #{mat.get-theme-color($theme, surface-container-low)};
--surface-container: #{mat.get-theme-color($theme, surface-container)};
--surface-container-high: #{mat.get-theme-color($theme, surface-container-high)};
--surface-container-highest: #{mat.get-theme-color($theme, surface-container-highest)};
--on-surface: #{mat.get-theme-color($theme, on-surface)};
--on-surface-variant: #{mat.get-theme-color($theme, on-surface-variant)};
--outline: #{mat.get-theme-color($theme, outline)};
--outline-variant: #{mat.get-theme-color($theme, outline-variant)};
--inverse-surface: #{mat.get-theme-color($theme, inverse-surface)};
--inverse-on-surface: #{mat.get-theme-color($theme, inverse-on-surface)};
--inverse-primary: #{mat.get-theme-color($theme, inverse-primary)};
--scrim: #{mat.get-theme-color($theme, scrim)};
--shadow: #{mat.get-theme-color($theme, shadow)};
}
:root {
...
@include materialRoleColorToVariable($light-theme);
}
.dark {
...
@include materialRoleColorToVariable($dark-theme);
}
Unterm Strich ist dieses Vorgehen für viele fachlich getriebene Projekte sicherlich die besser zu handhabende Herangehensweise. Lediglich bei der Entwicklung von eigenständigen Komponenten, die Projekt-Unabhängig verwendet werden sollen, ist das offizielle Vorgehen die besser Wahl.