DEV Community

Azizi Yazit
Azizi Yazit

Posted on • Edited on

Angular theme provider

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

In Angular, providing the theme provider is easy to accomplish as Angular has this ability out of the box.

In this post, we are using forwardRef and ngTemplate.

forwardRef

Basically, forwardRef is a function that captures a class reference into a closure. By using forwardRef, we can access the component reference and use it in other components.

constructor(
  @Inject(forwardRef(() => YourComponentName))
  private yourComponentName: YourComponentName
) {}
~~~{% endraw %}

## ng-template

{% raw %}`ngTemplate`{% endraw %} can use properties of its parent context and can have its own private context.{% raw %}

~~~html
<parent>
  <ng-template let-parentData1="data1" let-parentData2="data2">
    <!-- The "data1" and "data2" are available here -->
  </ng-template>
</parent>
~~~{% endraw %}

> In the above example, **data1** and **data2** are data from the Parent Component.

## ThemeProviderComponent

{% raw %}`ThemeProviderComponent`{% endraw %} is the root or parent component for other components.  
All components must be wrapped in this provider component to gain access to the theme object.  
Its highly recommended to wrap your entire app at the highest level (see below):{% raw %}

~~~html
<theme-provider>
  <app>
    <layout>
      <grid>
        <div></div>
      </grid>
    </layout>
  </app>
</theme-provider>
~~~{% endraw %}

### ThemeProviderComponent snippet{% raw %}

~~~javascript
@Component({
  selector: "theme-provider",
  template: `
    <ng-container *ngIf="childAsTemplate">
      <ng-container *ngTemplateOutlet="template; context: theme"></ng-container>
    </ng-container>
    <ng-content *ngIf="!childAsTemplate"></ng-content>
  `
})
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;
  }
}
~~~{% endraw %}

### palette snippet{% raw %}

~~~javascript
const palette = {
  uiBlueDarker: "#071d40",
  uiBlueDark: "#0d3880",
  uiBlue: "#184da8",
  uiBlueLight: "#2765cf",
  uiBlueLighter: "#e60278",
  // ...
};
~~~{% endraw %}

### scales snippet{% raw %}

~~~javascript
const scales = {
  "spacing-01": "0.125rem",
  "spacing-02": "0.25rem",
  "spacing-03": "0.5rem",
  "spacing-04": "0.75rem",
  // ...
};
~~~{% endraw %}

> **palette** and **scales** are theme items provided for child components' usage.

## Usages

### Used in a Component

In this example, we create a button component that accepts {% raw %}`color` and `scale` as inputs and uses the theme object provided by `ThemeProviderComponent` for **palette** and **scales** values.

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

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

> We get a reference to `ThemeProviderComponent` using `forwardRef`.  
> In the template markup, we utilize the theme for `palette` and `scale`.

Below is an example usage of the `my-button` component:

~~~html
<theme-provider>
  <my-button [color]="'uiBlue'" [scale]="9">
    Button
  </my-button>
</theme-provider>
~~~

### Used in ngTemplate

In this example, we customize the `my-button` component and gain access to the theme object through `ngTemplate`:

~~~html
<theme-provider>
  <ng-template let-palette="palette" let-scales="scales">
    <my-button>
      <div
        [style.color]="palette['uiYellow']"
        [style.backgroundColor]="palette['uiPink']"
        [style.fontSize]="scales['spacing-09']"
      >
        Button
      </div>
    </my-button>
  </ng-template>
</theme-provider>
~~~

## Summary

The theme provider lets us use the theme object (like **palette**, **spacing**, and **sizing**) in the scripting layer rather than the stylesheet layer.  
For those more comfortable styling components in JavaScript, this pattern can help speed up styling work.

You can get all the code used in this post here:  
👉 [https://stackblitz.com/edit/theme-provider](https://stackblitz.com/edit/theme-provider)
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
ngnam profile image
NamNguyen

like reactjs <3 thank bro