NgxMatFacetToolkit [](https://badge.fury.io/js/@drsutphin%2Fngx-mat-facet-toolkit)
December 27, 2025 · View on GitHub
Angular 19 standalone facet filtering toolkit with Material UI.
Install
npm install @drsutphin/ngx-mat-facet-toolkit @angular/material @angular/cdk
Peer dependencies:
- Angular 19
- Angular Material 19
- RxJS 7
Theme Setup
Add the toolkit theme mixins alongside your Material theme.
@use '@angular/material' as mat;
@use '@drsutphin/ngx-mat-facet-toolkit/facet-toolkit-theme' as facetToolkit;
@include mat.all-component-typographies();
@include mat.core();
$primary: mat.m2-define-palette(mat.$m2-indigo-palette, 500);
$accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400);
$theme: mat.m2-define-light-theme((
color: (
primary: $primary,
accent: $accent
)
));
@include mat.all-component-themes($theme);
@include facetToolkit.theme($theme);
Quick Start
- Provide an optional config during bootstrap.
import {bootstrapApplication} from '@angular/platform-browser';
import {
FacetIdentifierStrategy,
NgxMatFacetToolkitComponent,
provideFacetToolkitConfig
} from '@drsutphin/ngx-mat-facet-toolkit';
bootstrapApplication(AppComponent, {
providers: [
provideFacetToolkitConfig({
identifierStrategy: FacetIdentifierStrategy.ParentID,
storage: 'session'
})
]
});
- Supply facet definitions from your component.
import {FacetDataType, FacetDefinition, FacetFilterType} from '@drsutphin/ngx-mat-facet-toolkit';
import {delay, of} from 'rxjs';
public facets: FacetDefinition[] = [
{
id: 'user-name',
label: 'User Name',
type: FacetDataType.Text,
description: 'Plain text filter',
fixedFilterType: FacetFilterType.Contains
},
{
id: 'birthday',
label: 'Birthday',
type: FacetDataType.Date
},
{
id: 'license',
label: 'License(s)',
type: FacetDataType.Category,
options: of([
{value: 'a', label: 'Class A'},
{value: 'b', label: 'Class B'}
]).pipe(delay(300))
},
{
id: 'city',
label: 'City',
type: FacetDataType.Typeahead,
typeahead: {
provider: (searchText) => of([
{value: `${searchText}-a`, label: `${searchText} A`},
{value: `${searchText}-b`, label: `${searchText} B`}
]).pipe(delay(250)),
placeholder: 'Search cities'
}
}
];
- Render the component.
<ngx-mat-facet-toolkit
[facets]="facets"
[config]="{ storage: 'session' }"
(facetChange)="onFacetChange($event)"
(facetRemoved)="onFacetRemoved($event)"
(facetReset)="onFacetReset()">
</ngx-mat-facet-toolkit>
Facet Types
- Text
- Boolean
- Category (multi-select)
- CategorySingle
- Typeahead
- TypeaheadSingle
- Date
- DateRange
Use FacetDataType to pick a type and supply options or typeahead when needed.
Configuration
Component inputs:
facets:FacetDefinition[]config:Partial<FacetToolkitConfig>for per-instance overridesplaceholder:string(defaultFilter Table...)clearButtonText:stringclearButtonEnabled:booleandateFormat:stringtooltip:string | nulldisplayFilterIcon:booleanfacetWidth:string(usepxorrem)facetHasBackdrop:booleanconfirmOnRemove:booleanchipLabelsEnabled:booleanidentifier:string | null
Provider config (provideFacetToolkitConfig):
allowDebugClick:booleanchipRowScrollable:booleanidentifierStrategy:FacetIdentifierStrategyloggingCallback:(...args) => voidpresetStorage:FacetPresetStorageConfigshowFilterCount:booleanstorage:FacetStorageStrategy(session,local,none)themeMode:'auto' | 'light' | 'dark'themeOverrides:FacetToolkitThemeOverridesdarkThemeOverrides:FacetToolkitThemeOverridesthemeVariables:Record<string, string>darkThemeVariables:Record<string, string>
Theme overrides can be supplied via provider config or the component config input. Dark overrides apply when the theme mode is dark or the dark-theme body class is active, and themeMode: 'dark' forces the toolkit into dark styling even if the Material theme is light.
Outputs
facetChange: emitsFacetSelection[]whenever the selection state changes.facetRemoved: emits theFacetSelectionthat was removed.facetReset: emits when all facets are cleared.
A FacetSelection includes the facet definition plus active values and filter types.
Storage and Identity
Selections are stored by default in sessionStorage. You can switch to localStorage or disable storage.
Identity strategies:
ParentID(default) uses the parent component selector.WindowURLuseswindow.location.pathname.Randomgenerates a UUID.Manualexpectsidentifierinput.
Presets
Presets are keyed by the resolved facet identifier. Storage defaults to localStorage, but you can override with callbacks.
Preset storage config:
provideFacetToolkitConfig({
presetStorage: {
strategy: 'local',
keyPrefix: 'ngx-mat-facet-presets',
callbacks: {
loadPresets: (identifier) => [],
savePresets: (identifier, presets) => {},
clearPresets: (identifier) => {}
}
}
});
Roadmap
- Start with a single main component export.
- Expand to multiple exports as new facet tooling features land.
Repository Docs
CODEBASE.mdfor architecture, services, and data flow.V1_MIGRATION_PLAN.mdfor upgrade notes and constraints.documentation/for roadmap, release checklist, release notes, and session notes.
Docs Map
- Start here:
README.md(usage, API reference, theming, cookbook, migration/FAQ). - Architecture:
CODEBASE.md(data flow, extension points, services). - Migration details:
V1_MIGRATION_PLAN.md(upgrade plan and dependency notes).
API Reference
Core Models
FacetDefinition
| Field | Type | Notes |
|---|---|---|
id | string | Unique, stable identifier for the facet. |
label | string | Display label shown in the UI. |
description | string | Optional helper text shown in the modal. |
readonly | boolean | Prevents editing/removal when true. |
type | FacetDataType | Defines the editor and chip rendering. |
dataType | `'boolean' | 'number' |
options | FacetValue[] | Observable<FacetValue[]> | Static or async options for category facets. |
defaultValues | FacetValue[] | Pre-selected values on first load. |
typeahead | { provider, debounce?, placeholder? } | Required for typeahead facets. |
fixedFilterType | FacetFilterType | Locks the filter type. |
icon | string | Material icon name for the chip. |
iconClass | string | Additional class for the icon. |
cssClass | string | Adds a class to the facet editor. |
placeholder | string | Overrides the input placeholder. |
FacetValue
| Field | Type | Notes |
|---|---|---|
value | string | number | boolean | Date | Raw value. |
label | string | UI label for the value. |
count | number | Optional count shown in lists. |
FacetSelection
| Field | Type | Notes |
|---|---|---|
id | string | Matches the facet definition id. |
type | FacetDataType | Facet type. |
filterType | FacetFilterType | Active filter type (optional). |
values | FacetValue[] | Active selections or typed values. |
FacetToolkitConfig
| Field | Type | Notes |
|---|---|---|
allowDebugClick | boolean | Enables long-click identity logging. |
chipRowScrollable | boolean | Enables chip row horizontal scrolling. |
identifierStrategy | FacetIdentifierStrategy | How storage identity is generated. |
loggingCallback | (...args) => void | Logging hook for storage debug. |
presetStorage | FacetPresetStorageConfig | Preset storage config. |
showFilterCount | boolean | Shows active filter count badge. |
storage | FacetStorageStrategy | session, local, or none. |
FacetPreset
| Field | Type | Notes |
|---|---|---|
id | string | Preset identifier. |
name | string | Display name. |
identifier | string | Facet identifier scope. |
selections | FacetSelection[] | Stored selections. |
createdAt | string | ISO timestamp. |
updatedAt | string | ISO timestamp (optional). |
FacetPresetStorageConfig
| Field | Type | Notes |
|---|---|---|
strategy | FacetPresetStorageStrategy | local, session, or none. |
keyPrefix | string | Storage key prefix. |
callbacks | FacetPresetStorageCallbacks | Optional storage overrides. |
Enums
FacetDataType: Text, Boolean, Category, CategorySingle, Typeahead, TypeaheadSingle, Date, DateRange, Numeric
FacetFilterType: Contains, Equals, StartsWith, EndsWith, Between, Before, After, Exact, NotContains, NotEquals
Theming Variants
Dark Theme Example
.dark-theme {
@include mat.all-component-colors($dark-theme);
@include facetToolkit.color($dark-theme);
}
Custom Typography
$typography: mat.define-typography-config(
$font-family: 'IBM Plex Sans',
$headline-1: mat.define-typography-level(96px, 96px, 300),
$body-1: mat.define-typography-level(14px, 20px, 400)
);
@include mat.all-component-typographies($typography);
Density Control
$dense-theme: mat.m2-define-light-theme((
color: (primary: $primary, accent: $accent),
density: -1
));
@include mat.all-component-themes($dense-theme);
@include facetToolkit.theme($dense-theme);
Facet Type Cookbook
Text
{
id: 'name',
label: 'Name',
type: FacetDataType.Text,
fixedFilterType: FacetFilterType.Contains,
placeholder: 'Enter a name'
}
Boolean
{
id: 'active',
label: 'Active',
type: FacetDataType.Boolean
}
Category (Multi-Select)
{
id: 'status',
label: 'Status',
type: FacetDataType.Category,
options: of([
{value: 'open', label: 'Open', count: 12},
{value: 'closed', label: 'Closed', count: 3}
])
}
Category (Single)
{
id: 'priority',
label: 'Priority',
type: FacetDataType.CategorySingle,
options: [
{value: 'p1', label: 'P1'},
{value: 'p2', label: 'P2'}
]
}
Typeahead
{
id: 'city',
label: 'City',
type: FacetDataType.Typeahead,
typeahead: {
provider: (searchText) => of([
{value: `${searchText}-a`, label: `${searchText} A`},
{value: `${searchText}-b`, label: `${searchText} B`}
]).pipe(delay(250)),
placeholder: 'Search cities',
debounce: 200
}
}
Typeahead (Single)
{
id: 'owner',
label: 'Owner',
type: FacetDataType.TypeaheadSingle,
typeahead: {
provider: (searchText) => of([
{value: 'derek', label: 'Derek'},
{value: 'alex', label: 'Alex'}
])
}
}
Date
{
id: 'created',
label: 'Created Date',
type: FacetDataType.Date
}
Date Range
{
id: 'range',
label: 'Date Range',
type: FacetDataType.DateRange
}
Numeric
{
id: 'score',
label: 'Score',
type: FacetDataType.Numeric,
dataType: 'number',
placeholder: 'Enter a score'
}
Migration Notes (v1)
What's New in v1
- Standalone-only Angular 19 library built for modern control flow.
- New package name and selector (
@drsutphin/ngx-mat-facet-toolkit,ngx-mat-facet-toolkit). - Updated config/provider pattern with
provideFacetToolkitConfig. - Expanded API docs, theming guidance, and facet cookbook.
v1 Breaking Changes
- New package name:
@drsutphin/ngx-mat-facet-toolkit. - New selector:
ngx-mat-facet-toolkit. - Standalone-only: remove
NgxMatFacetSearchModuleusage. - Updated configuration model names (
FacetToolkitConfig,FacetIdentifierStrategy). - Control flow uses Angular 19 syntax (
@if,@for,@switch).
Upgrade Checklist
- Update imports to the new package name.
- Replace
<ngx-mat-facet-search>with<ngx-mat-facet-toolkit>. - Switch to standalone bootstrapping and providers.
- Move any module-scoped config to
provideFacetToolkitConfig. - Update any custom theming to include the toolkit theme mixins.
Common Upgrade Recipes
Old module import:
import {NgxMatFacetSearchModule} from 'ngx-mat-facet-search';
@NgModule({
imports: [NgxMatFacetSearchModule]
})
export class AppModule {}
New standalone import:
import {NgxMatFacetToolkitComponent} from '@drsutphin/ngx-mat-facet-toolkit';
@Component({
standalone: true,
imports: [NgxMatFacetToolkitComponent]
})
export class AppComponent {}
Old selector:
<ngx-mat-facet-search></ngx-mat-facet-search>
New selector:
<ngx-mat-facet-toolkit></ngx-mat-facet-toolkit>
Old provider pattern:
providers: [
{provide: FACET_SEARCH_CONFIG, useValue: {storage: 'session'}}
]
New provider:
providers: [
provideFacetToolkitConfig({storage: 'session'})
]
Troubleshooting & FAQ
The component does not persist filters.
- Ensure
storageis not set tonone. - Make sure a stable identifier is used (default
ParentIDrequires a stable parent selector). - Verify that
identifieris only set whenFacetIdentifierStrategy.Manualis used.
Typeahead results never show.
- The
typeahead.providermust return anObservable<FacetValue[]>. - Ensure the provider returns an array (not
null). - If using a debounce, confirm the value matches expected search timing.
Date values render as text.
- The date inputs expect
Dateobjects (not strings). - If you hydrate from JSON, convert to
Datebefore passing to the toolkit.
Facet chips show empty labels.
- Provide
labelon eachFacetValue. - Ensure
FacetSelection.valuesare set for text/date facets.
I see “duplicate theming” warnings.
- Ensure the toolkit theme mixins are only included once per theme.
- If you import the toolkit theme in multiple bundles, centralize it in a global theme file.