扩展具有插件架构的 Angular 仪表板中的 SCSS 样式

huangapple go评论73阅读模式
英文:

Extending SCSS Styles in an Angular Dashboard with Plugin Architecture

问题

我正在使用Angular开发一个仪表板,采用插件架构,以保持其易于扩展。该仪表板由预定义的可视化组件组成,如散点图。我还想允许从外部源添加可视化组件,即能够绘制3D图的Angular组件。我已成功在Angular和TypeScript端实现了插件架构。但是,在集成来自外部源的可视化组件时,我面临着使SCSS样式易于扩展并确保无缝外观和感觉的挑战。

具体来说,我希望每个组件都有自己的样式,比如字体颜色和大小。此外,我想定义一个global-styles.scss文件,我可以在其中覆盖这些样式。当样式在global-styles.scss中定义时,它们应该覆盖本地样式。

目前,我提出了以下(有效的)方法:

/* global-styles.scss */
:root {
  --header-color: hotpink;
}
/* scatter-plot.scss */
:host {
  --default-header-color: blue;
}

.header {
  color: var(--header-color, var(--default-header-color));
}

虽然这种方法似乎有效,但它涉及大量重复,因为我总是需要为每个自定义属性的使用都使用var(--XY, var(--default-XY))。所以我想知道是否有更清晰和更高效的方法来实现所需的行为?我尝试直接覆盖自定义属性,但无法使其工作,因为“外部CSS”需要覆盖“内部CSS”(即global-styles.scss应该覆盖scatter-plot.scss)。

编辑

理想情况下,它还应该支持主题(明亮和暗模式)。因此,我认为最好使用CSS自定义属性而不是SCSS,因为它们可以在运行时轻松覆盖。

英文:

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.

答案1

得分: 1

你的方法是正确的,并符合CSS自定义属性规范,但我理解它有点冗长,因为每次使用变量时都需要提供默认值。

你可以创建一个SCSS混合(utility mixin),封装回退逻辑。这可以减少每次需要使用变量时编写的代码量:

@mixin var($property, $varName, $default) {
  #{$property}: var(--#{$varName}, var(--default-#{$varName}, #{$default}));
}

你可以在组件样式中使用这个混合:

.header {
  @include var(color, 'header-color', blue);
}

这种方法仍然使用CSS变量和回退,但可以使你的SCSS代码更清晰且更易管理。

或者,由于你正在使用Angular,你可以利用::ng-deep伪类来强制对子组件的样式生效。使用这种方法,你可以在组件中定义默认样式,然后在全局样式中使用::ng-deep来覆盖它们。不过,需要注意的是,::ng-deep已经被弃用,不是推荐的长期解决方案。

或者,这是一个更适合Angular的主题处理方式,你可以创建一个ThemeService,允许你在运行时动态切换主题。每个主题可以是一个单独的SCSS文件,根据当前主题加载。对于主题,你可以使用Angular的[ngStyle][ngClass]指令根据当前主题应用样式。

考虑到你的要求,utility mixin方法可能是最合适的。它仍然遵循CSS自定义属性的方法论,不需要太多的重构。

英文:

Your approach is correct and fits the CSS custom properties specifications, but I understand that it's somewhat verbose due to the need to provide default values each time a variable is used.

You can create a SCSS mixin (utility mixin) that encapsulates the fallback logic. This could reduce the amount of code you write each time you need to use a variable:

@mixin var($property, $varName, $default) {
  #{$property}: var(--#{$varName}, var(--default-#{$varName}, #{$default}));
}

You can use this mixin in your component styles:

.header {
  @include var(color, 'header-color', blue);
}

This approach still uses CSS variables and fallbacks, but it makes your SCSS code a little cleaner and easier to manage.

OR
since you're using Angular, you could take advantage of the ::ng-deep pseudo-class to enforce styles on child components. With this approach, you could define default styles in your components, then use ::ng-deep in your global styles to override them. However, ::ng-deep is deprecated and is not a recommended long-term solution.

OR
This is a more Angular-oriented way to handle theming, you can create a ThemeService, which allows you to switch themes dynamically at runtime. Each theme could be a separate SCSS file that's loaded based on the current theme. For theming, you would use Angular's [ngStyle] or [ngClass] directives to apply styles based on the current theme.

Given your requirements, the utility mixin method might be the most suitable. It still adheres to the CSS custom properties methodology and doesn't require much refactoring from your current approach.

答案2

得分: 0

你可以利用CSS自定义属性。通过组织你的样式并利用CSS的层叠特性,你可以在不重复使用var(--XY, var(--default-XY))的情况下实现所需的行为。

以下是一个改进的方法,建立在你现有的设置基础上:

/* global-styles.scss */
:root {
--header-color: hotpink;
}

/* scatter-plot.scss */
:host {
--header-color: blue;
}

.header {
color: var(--header-color);
}

通过直接在.header类中使用var(--header-color),CSS层叠将处理每个用法和其余部分的模式。

关于主题(亮色和暗色模式),你可以为每个主题创建不同的自定义属性集,并在运行时动态切换它们。例如:

/* global-styles.scss */
:root {
--header-color-light: hotpink;
--header-color-dark: #333;
}
/* scatter-plot.scss */
:host {
--header-color: var(--header-color-light);
}

要切换到暗色主题,你可以通过编程方式更新自定义属性的值以匹配其相应的暗色模式值。

通过这个设置,你可以轻松扩展和覆盖单独组件中的样式,同时保持仪表板的统一外观和感觉。

试试这个方法,然后告诉我。

英文:

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.

答案3

得分: 0

以下是您要翻译的内容:

可以使用JavaScript更改.css变量。

想象一个.json文件如下所示:

{
"--background-color": "#212a2e",
"--text-color": "#F7F8F8",
"--link-color": "red"
}

您可以创建一个服务,根据此文件更改CSS变量。

@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)
);

}
}

现在,在每个组件中,您可以在构造函数中注入如下:

constructor(public cssService: CssService, public el:ElementRef) {}

并将所有内容放在一个div元素下:

...

stackblitz

英文:

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>

stackblitz

答案4

得分: 0

你可以尝试更改 angular.json 来修改样式

"styles": [
    "src/styles.css",
    {
        "input": "src/css/global.css",
        "bundleName": "global"
    }
],

查看文档

由于 Angular 创建了包含两种样式的索引

<style="style.***.css">
<style="global.***.css">

唯一的是 "global" 应该覆盖 style.css

要进行覆盖,你需要确保你的 global.css 看起来像这样,例如:

// my-app 是选择器
my-app.custom {
    --background-color: #212a2e,
    --text-color: #F7F8F8,
    --link-color: red
}
// hello 是选择器
hello.custom {
    --text-color: yellow;
}

然后在你的组件中使用:

@Component({
    selector: 'my-app',
    ...
    host: {class: 'custom'}
})

或者使用:

<hello class="custom" ...></hello>

注意:你也可以手动在 .html 文件中包含 global.css,这样你可以简单地编辑 global.css 而不必重新创建应用程序。

Stackblitz 示例

英文:

You can try change the angular.json to change the styles

    &quot;styles&quot;: [
          &quot;src/styles.css&quot;,
          {
            &quot;input&quot;:&quot;src/css/global.css&quot;,
            &quot;bundleName&quot;: &quot;global&quot;
          }
        ],

See the docs

As Angular create an index with the two styles

&lt;style=&quot;style.***.css&quot;&gt;
&lt;style=&quot;global.***.css&quot;&gt;

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: &#39;my-app&#39;,
  ...
  host: {class: &#39;custom&#39;}
})

Or use

&lt;hello class=&quot;custom&quot; ...&gt;&lt;/hello&gt;

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.

a stackbliz

答案5

得分: 0

有一个更多的选择可以尝试:样式封装。我看到两种可能性:

  • 完全使用 ViewEncapsulation.None(至少对于可定制的组件)

这更有风险,因为可能导致意外的样式覆盖,特别是在项目中有更多人一起工作时。我看到这种方法在库中更常见。

  • 将可定制的标记与组件封装样式分开

我觉得这种方法对你的情况很适用!将可定制的 CSS 变量与你的组件 scss 文件分开。如果这些变量与每个组件密切相关,甚至可以将它们放在相同的文件夹中,并在主文件中导入这些文件,该文件还可以包含全局主题变量。这些全局变量可以用作组件变量的初始默认值,创建一个层次结构...可能性无限!

你可以在全局样式文件的开头导入标记样式表,这样之后进行的变量覆盖就能正常工作!

希望这能有所帮助!祝好!

英文:

There's one more option you could play with: style encapsulation. I see two possibilities:

  • Go full on ViewEncapsulation.None (at least for the customizable components)

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.

  • Separate customizable tokens from components encapsulated styles

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!

huangapple
  • 本文由 发表于 2023年5月31日 22:50:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76374772.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定