Angular Mat Datepicker - 动态应用样式

Posted

技术标签:

【中文标题】Angular Mat Datepicker - 动态应用样式【英文标题】:Angular Mat Datepicker - Apply styles dynamically 【发布时间】:2022-01-15 03:49:01 【问题描述】:

我有一个包含 Material Datepicker 的 Datepicker 组件。该组件接收一个datepickerConfig 对象,该对象描述了 Datepicker 的样式。例如,此对象描述要应用的阴影级别、焦点样式、悬停样式等。

我对 Material 样式很陌生,所以我想知道如何在运行时动态应用这些样式?

例如,对于阴影,我在我的 SCSS 文件中声明了以下变量:

/* shadow */
$shadow-opacity: rgba(0, 0, 0, 0.3);
$dp-box-shadow-mobile: 0 30px 40px $shadow-opacity;
$dp-box-shadow-desktop-1: 0 3px 6px $shadow-opacity;
$dp-box-shadow-desktop-2: 0 6px 12px $shadow-opacity;
...
// more variables for the different styles

.mat-datepicker-content 
    // dynamically apply variables to these styles
    // e.g. if datepickerConfig.shadow.level === 2 
    // then $dp-box-shadow-desktop-2 should be applied 
    box-shadow: ...; 
    border: ...;
    border-radius: ...;
    ...

在运行时,我需要检查datepickerConfig 对象中的阴影级别,并动态地将正确的样式应用于元素。

我的日期选择器模板:

<mat-form-field appearance="fill">
    <mat-label>Choose a date</mat-label>
    <input matInput [matDatepicker]="picker">    
    <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-datepicker #picker (opened)="opened()">
      <mat-datepicker-actions>
        <button mat-button matDatepickerCancel>CANCEL</button>
        <button mat-raised-button matDatepickerApply>OK</button>
      </mat-datepicker-actions>
    </mat-datepicker>
</mat-form-field>

我怎样才能做到这一点,不仅是阴影,还有我必须动态设置的多种样式?我想知道用 Angular 做这件事的一种干净的方法......或者这只能通过 JS 来实现目标类吗?

谢谢。

【问题讨论】:

NgStyle 指令可能适合您吗? 【参考方案1】:

您可以使用CSS variables 实现您想要的。您不能使用 SCSS 变量,它们在运行时对您不可用,因为当 SCSS 编译成 CSS 时它们会消失。

Support for CSS variables 现在已经很不错了。虽然IE11出局了。

我在 stackblitz 中创建了一个示例,它根据通过配置提供的值使用 CSS 变量动态更改按钮阴影的样式:https://stackblitz.com/edit/angular-ivy-cuyhed?file=src/app/fancy-button.component.ts

全局样式包含 CSS 变量的定义。

styles.css

    :root 
     /* shadow-opacity variables, that will be used based on the configuration */
      --shadow-opacity-level-1: rgba(0, 0, 0, 1);
      --shadow-opacity-level-2: rgba(0, 0, 0, 0.5);

      /* default shadow opacity */
      --shadow-opacity: var(--shadow-opacity-level-1);
    

    /* global styles for a fancy-button, similar to your .mat-datepicker-content class */
    .fancy-button 
      border: 3px solid;
      padding: 0.25em 0.5em;
      /* here we use var(--shadow-opacity) to set default value, --shadow-opacity will be overridden later, based on the current configuration  */
      box-shadow: 5px 5px 0px 0px var(--shadow-opacity);
    

接下来我们需要一个按钮组件,它使用.fancy-button 类,但还要根据当前配置值覆盖--shadow-opacity 变量的值。

getButtonStyle() 方法检查config.shadowLevel 值并将--shadow-opacity 变量设置为适当的阴影级别。

fancy-button.component.ts

    @Component(
      selector: 'fancy-button',
      template: `
        <button 
          class="fancy-button"
          style="getButtonStyle()"
        >
          config.label
        </button>
      `,
      styles: [``]
    )
    export class FancyButtonComponent  
      @Input() config: FancyButtonConfig;

      getButtonStyle(): string 
        if (this.config.shadowLevel === 1) 
          return '--shadow-opacity: var(--shadow-opacity-level-1)';
        

        if (this.config.shadowLevel === 2) 
          return '--shadow-opacity: var(--shadow-opacity-level-2)';
        

        throw Error(`Unknown shadowLevel: $this.config.shadowLevel`);
      
    

总结

还可以选择使用自定义类和覆盖,正如 Nathan 在他的 answer 中所建议的那样。我认为 CSS 变量和类覆盖是相当等价的方法,当在这两者之间做出选择时,我会选择一种可以产生更清晰、更易于维护的代码的方法。

资源

Introduction to CSS Variables by Kevin Powell Overriding "parameter" of CSS class

使用 Angular Material 组件扩展答案

问题是专门询问 Angular 材质组件,但上面的答案描述的是常规 html 按钮。

我已经使用 FancyButtonMatWrapperComponent 扩展了 stackblitz 演示,它包装了 mat-raised-button,并根据提供的配置更改了它的 box-shadow

所有机制都按照上述常规 HTML 按钮的方式工作。主要区别在于,index.html 中的外部 div 具有类 material-overrides,它有助于覆盖默认的 Angular Material 样式。

全局 styles.css 然后包含以下覆盖

    .material-overrides .mat-raised-button 
      border: 3px solid black;
      padding: 0.25em 0.5em;
      box-shadow: 5px 5px 0px 0px var(--shadow-opacity);
    

【讨论】:

是的,这就是我最终要做的。虽然,我确实不得不求助于 Angular 的 Renderer2 在 OnInit 钩子中的 documentElement 上应用 CSS 变量。例如。 this.renderer.setStyle(this.doc.documentElement, '--dp-box-shadow', boxShadow, RendererStyleFlags2.DashCase)。这是必需的,因为cdk-overlay 被注入到树的顶部附近,这阻止了我将样式直接应用到 datepicker div 包装器中(因为我无法通过模板访问我想要的所有 mat 元素)。 感谢您的详细回答,如果您可以将渲染器解决方案添加到您的回答中,那就太好了,我相信它会对很多人有所帮助! 酷,感谢您的建议,一旦我有一点时间,将看看如何将这种方法与 Angular Material 组件一起使用,并将使用 Renderer2 更新 stackblitz 示例并更新答案。跨度> 我已经用 Angular Material 按钮扩展了演示,但最终我没有使用 Renderer2 来设置 CSS 变量的样式。如您所见,我使用material-overrides 类来提高自定义样式的特异性而不是材质样式。对于更复杂的材质组件,如日期选择器,全局样式中的覆盖需要更复杂,但我认为该方法应该以相同的方式工作。请让我知道这是否也适合您。【参考方案2】:

您可以使用[ngStyle] directive -- 以这个例子为例,它根据条件动态设置宽度属性。

  <div [style.width.%]="condition ? 20 : 50" ...>

一旦您开始管理多种样式,使用[ngStyle] 或上面“更简洁”的替代语法可能会变得混乱,并且不能真正用于获取模板中不可用的投影内容。

我可能会建议改用类方法(使用[ngClass] attribute directive)并使用 SCSS 覆盖来有条件地重置某些属性。

<mat-form-field class="datepicker"
                [class.datepicker--modified]="condition">
...

// in the SCSS
.datepicker 
  color: red;

  &--modified 
    color: blue;
  

【讨论】:

以上是关于Angular Mat Datepicker - 动态应用样式的主要内容,如果未能解决你的问题,请参考以下文章

如何在同一组件中重用 Material Angular Datepicker

无法编辑 Mat DatePicker 范围内的值。角 12

Mat-datepicker呈现在内容下方。

Angular 材料 Datepicker 获得改变的价值

mat-datepicker 作为 ag-grid 弹出窗口的单元格编辑器显示为空白

如何在Angular Material Datepicker中从今天的日期中移除焦点?