I need guidance on how to write a mechanism in Angular to set the "Look and Feel" of components globally in my application. Please note, I'm trying to learn @ngrx/platform and I thought this would be an interesting design constraint; however, I'm willing to let it go if it just doesn't make sense.
Breakdown:
I have an application in progress with many components. Each component in my application has currently 3 possible "look and feels(L&F)":
Please note there could be a spectrum of colors based on more granular time.
These L&Fs are set by the time of day of the current user e.g if user current time is 7 AM, the calculated L&F would be set to "Morning". I'm tracking this state inside ngrx/store of an angular module called SundialModule
and gnomon
is the mechanism of reducers and actions for getting
or setting
state:
sundial/reducers/gnomon.ts:
import * as gnomon from '../actions';
export interface State {
currentHour: number,
}
const initialState: State = {
currentHour: new Date().getHours()
};
export function reducer(state = initialState,
action: gnomon.Actions) {
console.log(action, 'action');
switch(action.type) {
case gnomon.MORNING:
return {
...state,
currentHour: 6,
};
case gnomon.AFTERNOON:
return {
...state,
currentHour: 12,
};
case gnomon.EVENING:
return {
...state,
currentHour: 7,
};
default:
return state;
}
}
Now I have an Angular Attribute directive called [sundialTheme]
that will set a HostBinding('class') theme = 'light'
on the element it's placed upon.
sundial/directives/theme.ts
@Directive({
selector: '[sundialTheme]'
})
export class SundialThemeDirective implements OnInit {
@HostBinding('class') theme = 'light';
private gnomon: Observable<any>;
constructor(private store: Store<fromGnomon.State>) {
this.gnomon = store.select<any>('gnomon');
}
ngOnInit() {
this.gnomon.subscribe((theme) => {
if(theme.currentHour >= 7 && theme.currentHour <= 11){
this.theme = 'sepia';
} else if( theme.currentHour >= 12 && theme.currentHour <= 18) {
this.theme = 'light'
} else {
this.theme = 'dark'
}
});
}
}
The problem: Every component in my application will need to have this attribute of sundialTheme
; moreover, every component will have a subscription to the this.gnomon = store.select<any>('gnomon');
, which feels expensive/heavyhanded. Lastly, and as an aside gripe, is that, every component will need to have my sundialModule
injected at every feature module, and every component will need the set of themes for each Time of day:
An example of this, every template's component.
note: sundialTheme
attribute directive:
<mh-pagination sundialTheme></mh-pagination>
<canvas glBootstrap class="full-bleed" sundialTheme></canvas>
<running-head [state]="(state$ | async)" sundialTheme></running-head>
<menu-navigation sundialTheme></menu-navigation>
<folio sundialTheme></folio>
<mh-footer sundialTheme></mh-footer>
Every Feature Module with a SundialModule
dependency:
@NgModule({
imports: [
SundialModule
],
})
export class MenuModule {
}
Every component styleUrls with sundial-links
:
note the ugly sundial-morning.scss
@Component({
selector: 'running-head',
templateUrl: 'running-head.component.html',
styleUrls: [
'running-head.component.scss',
'../../../components/sundial/sundial-morning.scss', // This !
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RunningHeadComponent {
}
Finally I've had other ways offered to me:
1) Since I'm using Angular CLI, I could add my styles globally and have a directive set a class on the body. Which seems to break any kind of web component standards.
2) I could use a factory loader in each components styleUrls:[]
; which I havent gotten clarity on how to implement.
3) I could follow material design components architecture which adds the themes directly into the components which doesn't feel very dry? (however, I havent researched too deeply into)
4) I could make a custom decorator for each component (could be viable but I'm nto sure how to implement)
Any other suggestions? Any best practices with reasoning?
Angular Material's theming system lets you customize color and typography styles for components in your application. The theming system is based on Google's Material Design specification. This document describes the concepts and APIs for customizing colors.
A theme is a set of colors that will be applied to the Angular Material components. To be more specific, a theme is a composition of color palettes.
Decide which theme to applybody. classList. toggle(“dark-theme”); This code will add or remove the dark-theme class to the page body, which will result in changing the CSS variable values used by our themes, effectively switching between light and dark mode.
To read color values from a theme, you can use the get-color-config Sass function. This function returns a Sass map containing the theme's primary, accent, and warn palettes, as well as a flag indicating whether dark mode is set. @use 'sass:map'; @use '@angular/material' as mat; $color-config: mat.
@HostBinding('class') theme = 'light'
should better be
@HostBinding('class.light') isLight = theme === 'light';
to not overwriting all other classes on this element.
You can set the class only in AppComponent and use in component styles
:host-context(.light) {
// my light component styles here
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With