Useful Development

Angular 9 update: creating a library that supports Angular Material theming is easier now

posted on Feb 14, 2020

With the release of Angular 9 creating a themeable library has gotten a bit easier. If you are familiar with the original post the tl;dr is that ng-packgr now supports copying assets as part of the build. This means using scss-bundle is no longer necessary.

The code to accompany this post can be found here.

Setting up the project

Get started by setting up the workspace and projects using Angular cli.


# create the project
ng n ng9-themeable-library-sample --interactive=false --createApplication=false

# go to the project root
cd ng9-themeable-library-sample

# add the app
ng g application web-app --style=scss --routing=false

# add Angular Material
ng add @angular/material --interactive=false

# add the library
ng g library test-lib

You can create the application when creating the workspace but it will add the app in the root I prefer it to be under the projects folder.

Adding project wide styles

The first task is to add support for sharing scss files between the different projects. Start by adding a folder shared-styles under the projects folder. For any files it contains to be included in the app edit angular.json and add the path to it in the stylePreprocessorOptions.


"projects": {
    "web-app": {
     ...
      },
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "stylePreprocessorOptions": {
              "includePaths": ["projects/shared-styles"]
            }
            ...

If you are using Universal then add the stylePreprocessorOptions to the server.options as well.

To include the folder in a library edit ng-package.json and a styleIncludePaths setting as shown below.


{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/bcd-shell",
  "deleteDestPath": false,
  "lib": {
    "entryFile": "src/public-api.ts",
    "styleIncludePaths": ["../shared-styles"]
  }
}

Files in shared-styles can not be imported without specifiying the full path.


@import 'partials/test';

Adding a theme to the app

Add a new file theme.scss (just take the sample theme from the Angular Material documentation). to the web-app/src folder.


// Import library functions for theme creation.
@import '~@angular/material/theming';

// Include non-theme styles for core.
@include mat-core();

// Define your application's custom theme.
$primary: mat-palette($mat-indigo);
$accent: mat-palette($mat-pink, A200, A100, A400);
$theme: mat-light-theme($primary, $accent);

// Include theme styles for Angular Material components.
@include angular-material-theme($theme);

Include the theme in the angular.json styles for the app, and remove the prebuilt theme.


"projects": {
    "web-app": {
     ...
      "architect": {
        "build": {         
          "options": {
           ...
            "styles": [
              "projects/web-app/src/theme.scss",
              "projects/web-app/src/styles.scss"
            ],
            "scripts": []
          }...

To finish up add a material component or two to the app component and run the app to check that the theme is working. I'm not going to add details for doing that in this tutorial but it is in the code sample.

Adding theme support to the library

To make the library themeable we need to create a mixin which will import all the individual component themes and pass the material theme into the component themes (sort of like one big component theme). Add a file called test-lib.theme.scss to the test-lib root fiolder of the test-lib.


@import 'src/lib/test/test.component.theme.scss';

@mixin test-lib-theme($test-app-theme) {
  @include test-component-theme($test-app-theme);
}

The builder for a library doesn't do any scss processing itself but ng-pckagr now has an assets config setting which can be used to include all the library theme files in the library.


{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/test-lib",
  "assets": ["**/*.theme.scss"],
  "lib": {
    "entryFile": "src/public-api.ts",
    "styleIncludePaths": ["../shared-styles"]
  }
}

Use the library theme in the app

Import the library into the web app and add a component from the library to the app component (see the sample). Then include the library theme in the app theme.scss


@import 'test-lib/test-lib.theme.scss';
@include test-lib-theme($theme);

Finally go back to angular.json and add the dist folder the web app stylePreprocessorOptions.


"projects": {
    "web-app": {
     ...
      },
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "stylePreprocessorOptions": {
              "includePaths": ["projects/shared-styles","dist"]
            }
            ...

Now you can build the library and app.


npm run build

Limitations

There is still the limitation that changes to themes in a library will not cause a reload when using ng build lib-name --watch, but I've opened a feature request to change this (please upvote it).

angular
angular-cli
angular-material
SCSS