Reputation: 94
I am developing a Dashboard in Angular with a Plugin Architecture to keep it easily extensible. The dashboard consists of pre-defined visualizations like scatter plots. I also want to allow adding visualizations from external sources, i.e., an angular component that is capable of drawing 3D plots. I have successfully implemented the Plugin Architecture on the Angular and Typescript side. However, I'm facing challenges in making the SCSS styles easily extensible and ensuring a seamless look and feel when integrating visualizations from external sources.
Specifically, I want each component to have its own styles, such as font color and size. Additionally, I want to define a global-styles.scss
file where I can overwrite these styles. When styles are defined in global-styles.scss
, they should overwrite the local styles.
Currently, I have come up with the following (working) approach:
/* global-styles.scss */
:root {
--header-color: hotpink;
}
/* scatter-plot.scss */
:host {
--default-header-color: blue;
}
.header {
color: var(--header-color, var(--default-header-color));
}
While this approach seems to work, it involves lots of repetition because I always need to use var(--XY, var(--default-XY))
for each custom property usage. So I'm wondering whether there's a cleaner and more efficient way to achieve the desired behavior? I have attempted to directly overwrite the custom properties, but I couldn't get it working as the "outer CSS" would need to overwrite the "inner CSS" (i.e., global-styles.scss
should overwrite scatter-plot.scss
).
EDIT
Ideally, it should also support theming (light and dark mode). Thus, I guess it would be easier to stick to CSS custom properties rather than SCSS, because they can be easily overwritten at runtime.
Upvotes: 3
Views: 1017
Reputation: 654
There's one more option you could play with: style encapsulation. I see two possibilities:
It's more risky, since it could lead to unintentional styles overrides, especially with more people working on the project. I see this approach taken more on libraries.
I think this approach could work well for your case! Separate customizable CSS variables from your components scss files. You could even keep them on the same folders if the variables are closelly related to each component and import these files on a main file, which could also contain global theming variables. These global variables could be used as initial default values for the component ones, creating a hierarchy...the possibilities are endless!
You could import the tokens stylesheet at the start of your global styles file, so variables overrides done after would work properly!
Hope this helps a little! Cheers!
Upvotes: 0
Reputation: 58019
You can try change the angular.json to change the styles
"styles": [
"src/styles.css",
{
"input":"src/css/global.css",
"bundleName": "global"
}
],
See the docs
As Angular create an index with the two styles
<style="style.***.css">
<style="global.***.css">
The only is "global" should override the style.css
To override you need your global.css looks like, e.g.
//my-app is the selector
my-app.custom{
--background-color: #212a2e,
--text-color: #F7F8F8,
--link-color: red
}
//hello is the selector
hello.custom{
--text-color:yellow;
}
your component like
@Component({
selector: 'my-app',
...
host: {class: 'custom'}
})
Or use
<hello class="custom" ...></hello>
NOTE: You can also include manually the global.css in the .html and, in this way, you can simply edit the global.css without to create again the app.
Upvotes: 0
Reputation: 58019
You can change the .css variables using javascript.
Imagine a .json like
{
"--background-color": "#212a2e",
"--text-color": "#F7F8F8",
"--link-color": "red"
}
You can to have a service that change the css variables based in this file
@Injectable({
providedIn: 'root',
})
export class CssService {
json: any;
constructor(private httpCient: HttpClient) {}
setCss(el: ElementRef) {
const obs = !this.json
? this.httpCient
.get('/assets/css.json')
.pipe(tap((res) => (this.json = res)))
: of(this.json);
return obs.pipe(
tap((res: any) => {
Object.keys(res).forEach((key) => {
if (window.getComputedStyle(el.nativeElement).getPropertyValue(key))
el.nativeElement.style.setProperty(key, res[key]);
});
}),
map((_) => true)
);
}
}
Now in each component you can in constructor inject as public
constructor(public cssService: CssService, public el:ElementRef) {}
And put all under a div
<div *ngIf="cssService.setCss(el)|async">
...
</div>
Upvotes: 0
Reputation: 102
You can utilize CSS custom properties. By organizing your styles and leveraging cascading nature of CSS, you can achieve the desired behavior without the repetition of var(--XY, var(--default-XY)) for each custom property usage.
Here's an improved approach that builds upon your existing setup:
/* global-styles.scss */
:root {
--header-color: hotpink;
}
/* scatter-plot.scss */
:host {
--header-color: blue;
}
.header {
color: var(--header-color);
}
By using the var(--header-color) directly in the .header class, the pattern for each usage and rest will be taken care of with the help of CSS Cascading.
Regarding theming (light and dark mode), you can create different sets of custom properties for each theme, and swap them out dynamically. For example:
/* global-styles.scss */
:root {
--header-color-light: hotpink;
--header-color-dark: #333;
}
/* scatter-plot.scss */
:host {
--header-color: var(--header-color-light);
}
To switch to the dark theme, you can programmatically update the values of the custom properties to their corresponding dark mode values.
With this setup, you can easily extend and override styles in individual components while maintaining a seamless look and feel across your dashboard.
Try this and let me know.
Upvotes: 0