Creating a Packaged and Themeable Angular Material Component | Task

Ole Ersoy
May - 24  -  6 min

Scenario

We need to create an Angular component, big.component.ts, that is NPM installable and also themeable.

The component makes text contained by its inner html bigger and uses the accent theme color for the text.

Approach

Workspace

We will be using the Angular Package Format for the component and module library, so first create a workspace for the library.

ng new big-workspace --create-application=false
cd big-workspace

Library

Then create the big component library.

ng g library big

And open the project in VSCode.

code .

Delete big.component.spec.ts, big.service.ts, and big.service.spec.ts.

rm projects/big/src/lib/big.component.spec.ts
rm projects/big/src/lib/big.service.ts
rm projects/big/src/lib/big.service.spec.ts

And update public-api.ts.

export * from './lib/big.component';
export * from './lib/big.module';

Styling

Add big.component.scss.

touch projects/big/src/lib/big.component.scss

Declare the non theme dependent CSS.

:host {
    letter-spacing: 0.05em;
    text-transform: uppercase;
    font-size: 4rem;
    font-family: monospace;
}

Add theming/index.scss.

mkdir projects/big/src/lib/theming
touch projects/big/src/lib/theming/index.scss

Declare the theme dependent CSS.

@use '@angular/material' as mat;

@mixin big-component-theme($theme) {
$accent: map-get($theme, accent);
fs-big {
    color: mat.get-color-from-palette($accent);
    }
}

Note that we are naming the theme file for the component index.scss such that we can import it like this (Which is symmetric with how Angular Material does it in general).

@use "@fireflysemantics/big-component-example/theming" as theme;

Create a big.component.html.

touch projects/big/src/lib/big.component.html

Put the template in that.

<p>big works!</p>

Change the implementation of big.component.ts.

import { Component, OnInit } from '@angular/core';
@Component({
    selector: 'fs-big',
    templateUrl: './big.component.html',
    styleUrls: ['./big.component.scss']
})
export class BigComponent implements OnInit {
constructor() { }
ngOnInit(): void {
    }
}

Library Test Build

Build the library project.

ng build big

Library Assets Packaging

Create a src/CHANGELOG.md for our change log notes.

touch projects/big/src/CHANGELOG.md

In order for ng-packagr to copy our assets into the distribution when building we need to update the assets section of ng-package.json.

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/big",
  "assets": [
    "./src/CHANGELOG.md",
    "./src/lib/theming/index.scss"
  ],
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}

After the update run the build command ng build big again.

Check the dist folder. It now contains a src folder with our assets.

sandbox/big-workspace $ tree dist/big/src/
dist/big/src/
├── CHANGELOG.md
└── lib
    └── theming
        └── index.scss
2 directories, 2 files

We also need to create an exports block for index.scss in the libraries package.json file.

{
  "name": "@fireflysemantics/big-component-example",
  "version": "0.0.1",
  "exports": {
    ".": {
      "sass": "./src/lib/theming/index.scss"
    }
  },
  "peerDependencies": {
    "@angular/common": "^13.3.0",
    "@angular/core": "^13.3.0"
  },
  "dependencies": {
    "tslib": "^2.3.0"
  }
}

This enables clients of our component to import the theme like this:

@use "@fireflysemantics/big-component-example/theming" as theme;

Note that we also changed the package name.

"name": "@fireflysemantics/big-component-example",

Local Testing

Test the component. To do this we will generate a test application.

ng generate application test --style=scss --routing 

And add Angular Material.

ng add @angular/material

Add the big.module.ts BigModule to the test application app.module.ts.

import { BigModule} from 'big'
@NgModule({
declarations: [
    AppComponent
],
imports: [
    BrowserModule,
    AppRoutingModule,
    BigModule],
   providers: [],
   bootstrap: [AppComponent]
})
export class AppModule { }

Declare the fs-big component inside app.component.html.

<div>
   <fs-big></fs-big>
</div>

Declare the CSS for the test rendering within app.component.scss.

div {
    margin-top: 10rem;
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: bolder;
}

Within the test project create a themes.scss file.

@use '@angular/material' as mat;
$my-theme-primary: mat.define-palette(mat.$indigo-palette, 500, 200, 800);
$my-theme-accent: mat.define-palette(mat.$cyan-palette);
$my-theme-warn: mat.define-palette(mat.$deep-orange-palette, A200);
$light-theme: mat.define-light-theme(
    $my-theme-primary,
    $my-theme-accent,
    $my-theme-warn
);

And add the themes to styles.scss.


/* You can add global styles to this file, and also import other style files */

@use '@angular/material' as mat;
@use '../../big/src/lib/theming' as theme;

// =====================================
// always include only once per project
// =====================================
@include mat.core();
// =====================================
// import our custom themes
// =====================================
@import 'themes';
// =====================================
// Import the big component theme
// =====================================

// =====================================
// Initialize custom component themes
// =====================================
@mixin custom-components-theme($theme) {
    @include theme.big-component-theme($theme);
// ====================================
// If we had more component themes
// we could include them here as well.
// ====================================
}
@include mat.core-theme($light-theme);
@include custom-components-theme($light-theme);

We can now run ng serve -o and we can see that our custom component is rendered with the accent color applied.

Packaging

Open package.json for the library and change the name.

"name": "@fireflysemantics/big-component-example",

Workspace Package Scripts

In order to build and publish the project add the following package.json scripts.

Install Global

This will install @jsdevtools/version-bump-prompt for semantic versioning.

"ig": "npm install -g @jsdevtools/version-bump-prompt",    

Build

"b": "ng build big",    

Bump Patch

Bump the package patch version.

"bp": "cd projects/big && bump patch",    

Publish

We copy over README.md over the library README.md that way we only maintain the big-workspace/README.md file.

"p": "cp README.md ./projects/big/ && npm run bp && npm run b && cd dist/big/ && npm publish"

Publish to NPM

The first time we publish we need to add --access public so we will run the p script manually the first time.

cp README.md ./projects/big && npm run b && cd dist/big/ && npm publish --access public

Now we can find @fireflysemantics/big-component-example on NPM.

Demo