Let's implement a Theme Switch 🎨 like the Angular Material Site
Tags: angular, javascript, typescript, webdev
Published: 2020-03-19 12:58:36

> **DISCLAIMER:** While I write this article, I'll try to be as vocal as I can about how I implement something. So hopefully, if you're new to Angular or have just started, you should get by just fine. If there's still an issue understanding anything here, please feel to comment on this article about it. I'll be more than happy to elaborate more on it.🙂

> That said, this article is mainly intended for beginners. I've added a [**TL;DR;**](#tldr) below 👇🏻 as a precautionary measure, in case you're a more advanced Angular Developer. Please feel free to navigate to a heading that might interest you more.

## TL;DR; <a name="tldr"></a>

* [Why a Theme Switch? 🤷🏻‍♂️](#why-a-theme-switch)
* [The Setup 🧩](#the-setup)
  * [Implementing the `HeaderComponent`](#implementing-the-header-component)<br>
  * [Implementing the `MenuComponent`](#implementing-the-menu-component)
* [Implementing the Theme Switch 🎨](#implementing-the-theme-switch)
  * [How Angular Material Website does it? 🤔](#how-the-angular-material-website-implemented-the-theme-switch)
  * [Our Implementation](#our-implementation)
* [Next Steps 👣](#next-steps)
* [Closing Notes 🎉](#closing-notes)

## Why a Theme Switch? 🤷🏻‍♂️ <a name="why-a-theme-switch"></a>

Alright! So Dark Mode is a *not so new* cool feature. Most of the websites out there are implementing it as custom themes in their Apps.

And why wouldn't they do that, right? Dark Mode is awesome! It's easier on the eyes, better in terms of power consumption, and can provide an excellent user experience(especially in low-lighting conditions)

Yeah! You heard me. Low-lit ambiances are the best suited for Dark Mode. I mean, nobody's stopping you from changing to Dark Mode even in well-lit environments. But still, Dark Mode is easier on the eyes and makes more sense IN THE DARK.(see what I did there? 😉)

Also, I mentioned Dark Mode as a way to enhance UX right? Well, there are multiple aspects to it. It's always better to offer users a way to customize the look and feel of your Apps, without compromising with your design language. And most of the Web Apps(or Apps, in general), do that via themes.

The other aspect might look more or less like a gimmick to some of you. But you can take the UX of your Web App, to the next level, by intelligently switching themes, based on the ambient lighting conditions that the user is in. [I'll get back to this later](https://dev.to/angular/switch-themes-like-a-fox-based-on-ambient-light-in-your-angular-apps-nk9).

One of my favorite websites, that implement Themes is the [Angular Material Site](https://material.angular.io/). You might have seen this switch that lets you change the theme on the website.

![Theme Switch on material.angular.io](https://res.cloudinary.com/siddajmera/image/upload/v1584085103/Angular%20Material%20Theme%20Switch.gif)

We'll pretty much replicate the same effect in our Angular App. So without further ado, let's get started.

## The Setup 🧩 <a name="the-setup"></a>

I've set-up Angular Material on StackBlitz that you can use as a starter template:

{% stackblitz angular-material-boilerplate-template %}

From here on, let's add a few Angular Material Components that we can use to see something on the UI. I'll add a toolbar, an icon on it, a menu for theme options, and a button.

Since all these Angular Material Components will be used in my `AppModule`, it would make sense to create a separate `AppMaterialModule` that re-exports all the Material related modules from it.

#### `app-material.module.ts`
```ts
...
import { MatButtonModule } from "@angular/material/button";
import { MatIconModule } from "@angular/material/icon";
import { MatMenuModule } from "@angular/material/menu";
import { MatToolbarModule } from "@angular/material/toolbar";
...

@NgModule({
  exports: [
    MatButtonModule,
    MatIconModule,
    MatMenuModule,
    MatToolbarModule,
  ]
})
export class AppMaterialModule {}
```

And now I can add the `AppMaterialModule` to the `imports` array of my `AppModule`.

#### `app.module.ts`
```ts
...
import { AppMaterialModule } from "./app-material.module";
...

@NgModule({
  imports: [
    ...
    AppMaterialModule,
    ...
  ],
  ...
})
export class AppModule {}
```

> **NOTE:** I'm doing this in here because I will be using all the Angular Material Components exposed by these Angular Material Modules in my `AppModule`. This would not make much sense in a Real-World Application as we would generally not use all the Angular Material Components in all our Modules. So creating a single `AppMaterialModule` and then importing it in every Angular Module that you create can lead to performance hits. So you might want to avoid that in such cases.

Moving on, I should now be able to use these Angular Material Components in my App. The look that I'm going for is really simple. THIS 👇🏻

![The Sample App UI](https://res.cloudinary.com/siddajmera/image/upload/v1584085340/Sample%20App%20by%20SiddAjmera.png)

Judging from the image above, we need a `HeaderComponent`, a `MenuComponent` that opens upon clicking the 🎨 icon and the rest would already be accommodated by our Sample StackBlitz.

### Implementing the `HeaderComponent`: <a name="implementing-the-header-component"></a>

I plan to make this a *smart component*.

> ASIDE: You can learn more about the *Smart and Dumb Component Pattern* from [this Video 📺](https://www.youtube.com/watch?v=r9vhfsnOb9o) by [Stephen Fluin](https://dev.to/stephenfluin)

{% youtube r9vhfsnOb9o %}

Alright, now continuing with our `HeaderComponent`, it needs to pass on some options for the menu to the `MenuComponent`. Each option would have things like, `backgroundColor`, `buttonColor`, & `headingColor` for the icon to show on each menu item; and a `label`, and a `value` corresponding to each label.

Now we do know that [Angular Material has 4 such pre-built themes](https://material.angular.io/guide/theming#using-a-pre-built-theme) named:

* `deeppurple-amber.css`
* `indigo-pink.css`
* `pink-bluegrey.css`
* `purple-green.css`

So we'll need 4 items for options. To avoid hard-coding of these options in the component itself, I'll just expose this data as a json file and store it in the `assets` folder in a file named `options.json`. Doing that will allow me to fetch it with path `/assets/options.json`

This file would look something like this:

#### `options.json`
```json
[
  {
    "backgroundColor": "#fff",
    "buttonColor": "#ffc107",
    "headingColor": "#673ab7",
    "label": "Deep Purple & Amber",
    "value": "deeppurple-amber"
  },
  {
    "backgroundColor": "#fff",
    "buttonColor": "#ff4081",
    "headingColor": "#3f51b5",
    "label": "Indigo & Pink",
    "value": "indigo-pink"
  },
  {
    "backgroundColor": "#303030",
    "buttonColor": "#607d8b",
    "headingColor": "#e91e63",
    "label": "Pink & Blue Grey",
    "value": "pink-bluegrey"
  },
  {
    "backgroundColor": "#303030",
    "buttonColor": "#4caf50",
    "headingColor": "#9c27b0",
    "label": "Purple & Green",
    "value": "purple-green"
  }
]
```

> NOTE: Considering that this data was exposed as a REST API, I can then fetch it using `HttpClient` in my App. And that's what I'll be doing in this article.

> ALTERNATIVELY, you can also leverage it as a static asset instead of exposing this data via a REST API. If you do that, you can import it directly in the `HeaderComponent` using `import options from 'path-to-options.json'`.

> You'll just have to set `resolveJsonModule`, and `esModuleInterop` to `true` in the `compilerOptions`, in your `tsconfig.app.json`/`tsconfig.json`. I have an implementation for this version in **[this Sample StackBlitz](https://stackblitz.com/edit/angular-material-theme-switch-with-static-json?file=tsconfig.json)** just in case you're interested. 🙂

Okay. Let's carry on. Now, since I also have the structure of the `option` Object, I can create an `interface` for static typing. Let's store it in a file named `option.model.ts`:

#### `option.model.ts`

```ts
export interface Option {
  backgroundColor: string;
  buttonColor: string;
  headingColor: string;
  label: string;
  value: string;
}
```

Perfect! Now the responsibility of the `HeaderComponent` is to:

* Render the header(Obviously! 🤷🏻‍♂️)
* Fetch the options and give it to the `MenuComponent`.

But we do need to also change the theme at some point. So it's better that we abstract the whole business logic related to themes in a service that I'd call `ThemeService`. So let's implement that first:

#### `theme.service.ts`
```ts
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";

import { Option } from "./option.model";

@Injectable()
export class ThemeService {
  constructor(
    private http: HttpClient,
  ) {}

  getThemeOptions(): Observable<Array<Option>> {
    return this.http.get<Array<Option>>("assets/options.json");
  }

  setTheme(themeToSet) {
    // TODO(@SiddAjmera): Implement this later
  }
}
```
Sweet! We can now inject this service as a dependency in the `HeaderComponent` which would look something like this:

#### `header.component.ts`
```ts
import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs/Observable";

import { Option } from "../option.model";
import { ThemeService } from "../theme.service";

@Component({
  selector: "app-header",
  templateUrl: "./header.component.html",
  styleUrls: ["./header.component.css"]
})
export class HeaderComponent implements OnInit  {
  options$: Observable<Array<Option>> = this.themeService.getThemeOptions();

  constructor(private readonly themeService: ThemeService) {}

  ngOnInit() {
    this.themeService.setTheme("deeppurple-amber");
  }

  themeChangeHandler(themeToSet) {
    this.themeService.setTheme(themeToSet);
  }
}
```
As you can see, the `HeaderComponent` is also responsible for changing the theme now.

And the template would look like this:

#### `header.component.html`
```html
<mat-toolbar color="primary">
  <mat-toolbar-row>
    <span>Dora</span>
    <span class="spacer"></span>
    <app-menu 
      [options]="options$ | async"
      (themeChange)="themeChangeHandler($event)">
    </app-menu>
  </mat-toolbar-row>
</mat-toolbar>
```
Notice how instead of `subscribe`ing to the `options$` `Observable` in the Component Class, we've used the `async` pipe to unwrap it. This is a pattern that makes Angular reactive and as far as possible, you **should** follow this pattern. Once we get the options, we can then pass it as an input to the `MenuComponent`'s `options` `@Input` property.

Also, since the responsibility of changing the theme is also taken up by the `HeaderComponent`, we can implement the `MenuComponent` as a *dumb/presentational component*. So let's do that now.

### Implementing the `MenuComponent`:  <a name="implementing-the-menu-component"></a>

So now we can tell that the `MenuComponent` would accept `options` as an `@Input` and then iterate through them to render these options. We can also clearly see that it has a `themeChange` `@Output` property that calls the handler with the newly selected theme. So we can implement the `MenuComponent` Class like this:

#### `menu.component.ts`
```ts
import { Component, EventEmitter, Input, Output } from "@angular/core";

import { Option } from "../option.model";
import { ThemeService } from "../theme.service";

@Component({
  selector: "app-menu",
  templateUrl: "./menu.component.html",
  styleUrls: ["./menu.component.css"]
})
export class MenuComponent {
  @Input() options: Array<Option>;
  @Output() themeChange: EventEmitter<string> = new EventEmitter<string>();

  constructor(private themeService: ThemeService) {}

  changeTheme(themeToSet) {
    this.themeChange.emit(themeToSet);
  }
}
```

And the template would look something like this:

#### `menu.component.html`
```html
<mat-icon
  class="icon" 
  [matMenuTriggerFor]="menu">
  palette
</mat-icon>
<mat-menu #menu="matMenu">
  <button
    *ngFor="let option of options"
    mat-menu-item
    (click)="changeTheme(option.value)">
    <mat-icon
      role="img" 
      svgicon="theme-example"
      aria-hidden="true">
      <svg
        xmlns="http://www.w3.org/2000/svg" 
        xmlns:xlink="http://www.w3.org/1999/xlink" 
        width="100%" 
        height="100%"
        viewBox="0 0 80 80" 
        fit="" 
        preserveAspectRatio="xMidYMid meet" 
        focusable="false">
        <defs>
          <path
            d="M77.87 0C79.05 0 80 .95 80 2.13v75.74c0 1.17-.95 2.13-2.13 2.13H2.13C.96 80 0 79.04 0 77.87V2.13C0 .95.96 0 2.13 0h75.74z"
            id="a">
          </path>
          <path
            d="M54 40c3.32 0 6 2.69 6 6 0 1.2 0-1.2 0 0 0 3.31-2.68 6-6 6H26c-3.31 0-6-2.69-6-6 0-1.2 0 1.2 0 0 0-3.31 2.69-6 6-6h28z"
            id="b">
          </path>
          <path d="M0 0h80v17.24H0V0z" id="c"></path>
        </defs>
        <use xlink:href="#a" [attr.fill]="option.backgroundColor"></use>
        <use xlink:href="#b" [attr.fill]="option.buttonColor"></use>
        <use xlink:href="#c" [attr.fill]="option.headingColor"></use>
      </svg>
    </mat-icon>
    <span>{{ option.label }}</span>
  </button>
</mat-menu>
```
Alright! Now we have everything in place. We just need a way to switch themes. How do we do that?

## Implementing the Theme Switch 🎨 <a name="implementing-the-theme-switch"></a>

This is the final piece of the puzzle. And we can do this in several different ways. But the Angular Material Website has already implemented this right? And the good thing is, it's open-source. So we do have access to the source code.

So instead of trying to re-invent the wheel, I'm going to cheat a little bit and see how Angular Material Docs App did it.

### How Angular Material Website does it? 🤔 <a name="how-the-angular-material-website-implemented-the-theme-switch"></a>

If you check the actual implementation, they have implemented something called a [`ThemePicker`](https://github.com/angular/material.angular.io/blob/master/src/app/shared/theme-picker/theme-picker.ts). This is what we see at the top-right, in the header. 

![Theme Picker on material.angular.io](https://res.cloudinary.com/siddajmera/image/upload/v1584617323/Theme%20Picker%20-%20Angular%20Material.png)

This(as the name suggests) is responsible for switching the theme on the website. This component calls a service called `StyleManager`.

What does this service do, you might ask. Well, when you change the theme from the `ThemePicker` it:

* Checks whether there's a link tag on the HTML Document with a `class` attribute, the value of which is: `style-manager-theme`:
  * If there isn't such a `link` tag, it adds this `link` tag to the head of the document, and then set the `href` property with the selected theme path on it.
  * If there is such a `link` tag, it then, it simply sets the `href` property on this `link` tag to the selected theme path.

Great, now that we understand what the `StyleManager` does, I can just copy the `StyleManager` service in my project. Once I do that, I can just inject this in my `ThemeService` and call the `setStyle` method from it with the appropriate values and it should ideally work.

So let's try it out.

### Our Implementation <a name="our-implementation"></a>

I'll first copy the style-manager.ts in a file named style-manager.service.ts:

#### `style-manager.service.ts`
```ts
/**
 * Copied from https://github.com/angular/material.angular.io/blob/master/src/app/shared/style-manager/style-manager.ts
 * TODO(@SiddAjmera): Give proper attribution here
 */

import { Injectable } from "@angular/core";

@Injectable()
export class StyleManagerService {
  constructor() {}

  /**
   * Set the stylesheet with the specified key.
   */
  setStyle(key: string, href: string) {
    getLinkElementForKey(key).setAttribute("href", href);
  }

  /**
   * Remove the stylesheet with the specified key.
   */
  removeStyle(key: string) {
    const existingLinkElement = getExistingLinkElementByKey(key);
    if (existingLinkElement) {
      document.head.removeChild(existingLinkElement);
    }
  }
}

function getLinkElementForKey(key: string) {
  return getExistingLinkElementByKey(key) || createLinkElementWithKey(key);
}

function getExistingLinkElementByKey(key: string) {
  return document.head.querySelector(
    `link[rel="stylesheet"].${getClassNameForKey(key)}`
  );
}

function createLinkElementWithKey(key: string) {
  const linkEl = document.createElement("link");
  linkEl.setAttribute("rel", "stylesheet");
  linkEl.classList.add(getClassNameForKey(key));
  document.head.appendChild(linkEl);
  return linkEl;
}

function getClassNameForKey(key: string) {
  return `app-${key}`;
}
```
Great. So now that I have this service in place, as planned, I'll inject this service as a dependency in my `ThemeService` and implement the `setTheme` method:

#### theme.service.ts
```ts
...
import { StyleManagerService } from "./style-manager.service";

@Injectable()
export class ThemeService {
  constructor(
    ...
    private styleManager: StyleManagerService
  ) {}

  ...

  setTheme(themeToSet) {
    this.styleManager.setStyle(
      "theme",
      `node_modules/@angular/material/prebuilt-themes/${themeToSet}.css`
    );
  }
}
```
All I'm doing here is calling the `setStyle` method from the `StyleManagerService` with the name of the style key(theme in this case), and the value of the `href` attribute that it has to set.

The `setStyle` method, again, either creates a new `link` tag and then sets the `href` attribute on it; or updates the `href` attribute on a pre-existing `link` tag.

And that's pretty much it. This 👇🏻 is what our final code looks like.

{% stackblitz angular-material-theme-switch view=preview %}

Purrfect! We now have a theme switch, just like the one on the Angular Material Website. And it works as expected 😍

## Next Steps 👣 <a name="next-steps"></a>

This is all great. But wouldn't it be awesome if our App could automatically switch themes based on the ambient light?🤔 Well, that's exactly what we're going to do in the next article.

Can't wait? You can read it now. Here's the link:

{% link https://dev.to/angular/switch-themes-like-a-fox-based-on-ambient-light-in-your-angular-apps-nk9 %}

## Closing Notes 🎉<a name="closing notes"></a>

Awwww! You're still here? Thanks for sticking around. I hope you liked it.

I’m extremely grateful to [Martina Kraus](https://dev.to/martinakraus11), and [Rajat Badjatya](https://dev.to/rajatbadjatya) for taking the time to proofread it and providing all the constructive feedback in making this article better.

I hope this article has taught you something new related to Angular. If it did hit that 🧡/🦄 icon, and add it to your reading list(🔖). Also, [share](https://twitter.com/intent/tweet?text=%22Angular%20Apps%20with%20a%20Theme%20Switch%20%F0%9F%8E%A8%20like%20the%20%F0%9F%85%B0ngular%20Material%20Site%20%23DarkMode%22%20by%20@SiddAjmera%20%23DEVcommunity%20https://dev.to/siddajmera/angular-apps-with-a-theme-switch-like-the-ngular-material-site-darkmode-3mno) this article with your friends who are new to Angular and want to achieve something similar.

Please stay tuned while I work on uploading a video version of this article. 📺

Icon Courtesy: [AngularIO Press Kit](https://angular.io/presskit#material) |  [CSS](https://thenounproject.com/search/?q=Theme&i=1947501) by monkik from the Noun Project

Until next time then. 👋🏻