UPDATE: If you are using Angular 9 go to the updated post here.
Now angular cli supports workspaces using a monorepo structure for larger Angular applications is a good option. If you are using Angular Material as well this does present a challenge in terms of how to move components into a library and still be able to apply the app level theme to them. This tutorial shows an approach to adding a theme to a library project which supports material themes. It is aimed at people who are already familiar with Angular Material's theming capabilities. If you haven't dones so already I would recommend reading the documentation before continuing.
The code to accompany this post can be found here.
Get started by setting up the workspace and projects using Angular cli.
# create the project
ng n themeable-library-sample --interactive=false --createApplication=false
# go to the project root
cd themeable-library-sample
# add the app
ng g application web-app --style=scss
# 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 repo root and I prefer it to be under the projects folder.
The first task is to add support for sharing scss files between the different projects. Start by adding a folder called shared-styles in projects. For any files it contains to be included in the app edit angular.json and add the path to the folder in the stylePreprocessorOptions.
"projects": {
"web-app": {
...
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
...
"stylePreprocessorOptions": {
"includePaths": ["projects/shared-styles"]
}
...
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 under the shared-styles folder can no be imported without needing the full path.
@import 'partials/test';
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.
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).
@import '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 so the next step is to add an npm package called scss-bundle and configure it to bundle up the library theme so it can be imported by an app.
npm i --save-dev scss-bundle
Under the src folder for each library add a file called scss-bundle.config.json which has the library them file as the entry file and outputs to the library dist folder.
{
"entry": "./projects/test-lib/src/lib/theme/test-lib.theme.scss",
"dest": "./dist/test-lib/test-lib.theme.scss",
"includePaths": ["projects/shared-styles"]
}
The next step is to create a build task that will run scss-bundle when the library is built.
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "npm run test-lib:build && npm run web-app:build",
"test-lib:build": "ng build test-lib && scss-bundle -c projects/test-lib/src/scss-bundle.config.json",
"web-app:build": "ng build web-app",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
}
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
So this works but with one limitation and one big caveat. The limitation is that a built version of the libraries needs to exists in dist or when running ng s the app will fail to build due to the imports in the web app theme pointing to dist. The caveat is that I haven't reviewed the scss generated to see if the bundles are bloated or not.