DEV Community

Azizi Yazit
Azizi Yazit

Posted on • Updated on

Angular theme provider

Providing theme provider is a MUST in component library. All the components should be able to gain access to the Theme Object. This includes palettes, spacings, sizings and many more.

In Angular, providing the theme provider is easy to be accomplished as the Angular have this ability out-of-the-box.

In this post we are using forwardRef and ngTemplate

forwardRef

Basically forwardRef is just a function that captures a class reference into closure. By using forwardRef we be able to get access to the component reference and use it in others components.

constructor(
  @Inject(forwardRef(() => YourComponentName))
  private yourComponentName: YourComponentName
) {}
Enter fullscreen mode Exit fullscreen mode

ng-template

ngTemplate can use properties of its parent context and can have its own private context.

<parent>
  <ng-template let-parentData1="data1" let-parentData2="data2">
    <!-- The "data1" and "data2" is available to be used here -->
  </ng-template>
</parent>
Enter fullscreen mode Exit fullscreen mode

In above example, the data2 and data2 is the data's from Parent Component

ThemeProviderComponent

ThemeProviderComponent is the root component or the parent component for the other components. All the components must be wrapped in this provider component in order to gain access to the theme object. It's highly recommended to wrap your entire app at the highest level (see below)

<theme-provider>
 <app>
  <layout>
   <grid>
    <div> 
    </div>
   <grid>
  <layout>
 </app>
</theme-provider>
Enter fullscreen mode Exit fullscreen mode

ThemeProviderComponent snippet code

@Component({
  selector: "theme-provider",
  template: {% raw %}`
    <ng-container *ngIf="childAsTemplate">
      <ng-container *ngTemplateOutlet="template; context: theme"></ng-container>
    </ng-container>
    <ng-content *ngIf="!childAsTemplate"></ng-content>
  `{% endraw %}
})
export class ThemeProviderComponent implements AfterContentInit {
  @ContentChild(TemplateRef, { static: false }) template!: TemplateRef<any>;
  theme: any = {
    palette,
    scales
  };
  childAsTemplate: boolean = false;

  constructor() {}

  ngAfterContentInit() {
    this.childAsTemplate = this.template && true;
  }
}
Enter fullscreen mode Exit fullscreen mode

palette snippet code

const palette = {
  uiBlueDarker: "#071d40",
  uiBlueDark: "#0d3880",
  uiBlue: "#184da8",
  uiBlueLight: "#2765cf",
  uiBlueLighter: "#e60278"
  ...
};
Enter fullscreen mode Exit fullscreen mode

scales snippet code

const scales = {
  "spacing-01": "0.125rem",
  "spacing-02": "0.25rem",
  "spacing-03": "0.5rem",
  "spacing-04": "0.75rem"
  ...
};
Enter fullscreen mode Exit fullscreen mode

palette and scales is the theme items provided for child components usage

usages

used in Component

In this example, we created a button component that accept color and scale as an inputs and use the theme object provided by ThemeProviderComponent for palette and scales values.

@Component({
  selector: "my-button",
  template: {% raw %}`
    <button type="button" [style.color]="themeProvider?.theme?.palette[color]" [style.font-size]="themeProvider?.theme?.scales['spacing-0'+scale]">
      <ng-content></ng-content>
    </button>
  `{% endraw %}
})
export class MyButtonComponent {
  @HostBinding("class") className;
  @Input() color: string;
  @Input() scale: number;

  constructor(
    @Inject(forwardRef(() => ThemeProviderComponent))
    private themeProvider: ThemeProviderComponent
  ) {}

}
Enter fullscreen mode Exit fullscreen mode

we get the reference of ThemeProviderComponent by using forwardRef. In the template markup, we utilised the theme for palette and scale.

Below is code implementation for my-button component

<theme-provider>
<my-button
[color]="'uiBlue'"
[scale]="9">
Button
</my-button>
</theme-provider>
Enter fullscreen mode Exit fullscreen mode




used in ngTemplate

In this example we customised my-button component and gain access the theme object thru ngTemplate

<theme-provider>
<ng-template let-palette="palette" let-scales="scales">
<my-button>
<div
[style.color]="palette['uiYellow']"
[style.background-color]="palette['uiPink']"
[style.font-size]="scales['spacing-09']"
>
Button
</div>
</my-button>
</ng-template>
</theme-provider>
Enter fullscreen mode Exit fullscreen mode




Summary

The theme provider let us use the theme object like palette, spacing and sizing in scripting layer rather than stylesheet layer. For those who more comfortable in styling the component in JavaScript, this pattern can help to speed up styling works.

You can get all the codes used in this post here - https://stackblitz.com/edit/theme-provider

Discussion (0)