Creating a UI Library
Let’s start by creating a UI library. We will create a new Angular library project.
ng g lib @app/uiCreating stats component
The stats component will be a simple component that displays a list of pending and completed tasks.
ng g c components/stats --project=@app/ui --style=scss<div class="stats-container"> @for (item of list; track item) { <div class="stats-item stats-item-{{ item }}" [class.done]="count()[item].done === count()[item].total" > <div class="stats-item-title">{{ item }}</div> <div class="stats-item-value"> {{ count()[item].done }} <span class="stats-item-value-total">/ {{ count()[item].total }}</span> </div> </div> }</div>import { Component, input } from '@angular/core';
@Component({ selector: 'lib-stats', imports: [], templateUrl: './stats.component.html', styleUrl: './stats.component.scss',})export class StatsComponent { readonly list = ['high', 'medium', 'low']; count = input<Record<string, { total: number; done: number }>>({ high: { total: 0, done: 0 }, medium: { total: 0, done: 0 }, low: { total: 0, done: 0 }, });}.stats-container { display: grid; overflow: hidden; grid-template-columns: repeat(1, 1fr); margin: 1rem;
@media (min-width: 960px) { grid-template-columns: repeat(3, 1fr); }}
.stats-item { padding: 8px 16px;
&.done { opacity: 0.5; }}Exporting the component
export * from './lib/components/stats/stats.component';Script for library
Add below scripts in root package.json file.
{ "scripts": { "prebuild": "ng build --project=@app/ui", "watch:lib": "ng build @app/ui --watch --configuration development" }}Then run npm run watch:lib to start watching for changes and rebuilding the library.
Applying Angular Material styles
@use "sass:map";@use "@angular/material" as mat;
.stats-container { display: grid; border-radius: var(--mat-sys-corner-medium); overflow: hidden; grid-template-columns: repeat(1, 1fr); margin: 1rem; box-shadow: var(--mat-sys-level1);
@media (min-width: 960px) { grid-template-columns: repeat(3, 1fr); }}
$priority-colors: ( low: ( background: var(--mat-sys-surface-container), foreground: var(--mat-sys-on-surface), ), medium: ( background: var(--mat-sys-secondary-container), foreground: var(--mat-sys-on-secondary-container), ), high: ( background: var(--mat-sys-error-container), foreground: var(--mat-sys-on-error-container), ),);
.stats-item { padding: 8px 16px; &.done { opacity: 0.5; }}
@each $priority, $colors in $priority-colors { .stats-item-#{$priority} { background-color: map.get($colors, background); color: map.get($colors, foreground); }}
.stats-item-value { font: var(--mat-sys-headline-large); letter-spacing: var(--mat-sys-headline-large-tracking);}
.stats-item-title { font: var(--mat-sys-title-medium); letter-spacing: var(--mat-sys-title-medium-tracking); margin-bottom: 12px;}
.stats-item-value-total { font: var(--mat-sys-title-small); letter-spacing: var(--mat-sys-title-small-tracking);}Importing in app component
<main class="container"> <lib-stats [count]="count()"></lib-stats> <app-todo-list></app-todo-list></main>import { StatsComponent } from '@app/ui';
Component({ // rest remains same imports: [ // other imports, StatsComponent ]})export class AppComponent { count = computed(() => this.todoService.todos().reduce( (acc, todo) => { acc[todo.priority] = acc[todo.priority] || { total: 0, done: 0 }; acc[todo.priority].total++; acc[todo.priority].done += todo.completed ? 1 : 0; return acc; }, { high: { total: 0, done: 0 }, medium: { total: 0, done: 0 }, low: { total: 0, done: 0 }, } ) );}Taking care of density
1. Create a new file _stats.density.scss
Create a new file at projects/app/ui/src/lib/components/stats/_stats.density.scss and add the density mixin.
@use "sass:map";
@mixin density($density) { $spacing: (-1: 6px 12px, -2: 4px 8px, -3: 4px 8px, -4: 4px 8px, -5: 4px 8px); $scale-spacing: map.get($spacing, $density); .stats-item { padding: $scale-spacing !important; }}2. Export the density mixin from _index.scss
Create a new barrel file at projects/app/ui/src/_index.scss and export the density mixin.
@forward "./lib/components/stats/stats.density" as stats-*;3. (Optional) SASS entry in library’s package.json
This is needed only if you’re planning to publish your library. Add below in projects/app/ui/package.json:
{ "exports": { ".": { "sass": "./_index.scss" } }}4. Include projects path in angular.json
This change will make sure that all the styles from projects directory are included in the build process.
{ "projects": { "my-app": { "architect": { "build": { "options": { "stylePreprocessorOptions": { "includePaths": ["projects"] } } } } } }}After above change, you will have to restart the dev server.
5. Use the stats density mixin
Now we can use the density mixin in the src/styles/_density.scss file.
@use "@angular/material" as mat;@use "app/ui/src" as ui;
$density-list: (0, -1, -2, -3, -4, -5);
@each $density in $density-list { .density-#{$density} { @include mat.theme((density: $density)); @include ui.stats-density($density); }}