Docs / Angular reusable UI components for Material apps
Angular reusable UI components for Material apps
Besides the data table, Easy Mat UI ships small standalone components that every business application ends up rewriting. Each one takes its data with inputs, reports with outputs and has a typed model. None of them contains business logic.
Six components in one component
This complete example uses all of them. Copy it into an Angular 20 project that has Angular Material and easy-mat-ui-ng20.
import { Component, signal } from '@angular/core';
import {
EasyCardConfig,
EasyCardGrid,
EasyChipNav,
EasyChipNavItem,
EasyLanguage,
EasyLanguageSwitch,
EasyPageHeader,
EasySegmented,
EasySegmentedOption,
EasyStat
} from 'easy-mat-ui-ng20';
/** Six reusable components, each fed by its inputs and reporting by its outputs. No business logic inside. */
@Component({
selector: 'app-toolbox',
imports: [EasyPageHeader, EasyStat, EasyChipNav, EasySegmented, EasyLanguageSwitch, EasyCardGrid],
template: `
<lib-easy-page-header [config]="{ icon: 'inventory_2', title: 'Warehouse', subtitle: 'Stock by category' }">
<lib-easy-language-switch [languages]="languages" [selected]="language()" (selectedChange)="language.set($event)" />
</lib-easy-page-header>
<lib-easy-stat [config]="{ label: 'Items', value: 1284, icon: 'inventory', tone: 'accent' }" />
<lib-easy-stat [config]="{ label: 'Low stock', value: 12, tone: 'warning', hint: 'Below the reorder level' }" />
<lib-easy-chip-nav [items]="categories" [selected]="category()" ariaLabel="Category" (selectedChange)="category.set($event)" />
<lib-easy-segmented [options]="views" [selected]="view()" ariaLabel="View" (selectedChange)="view.set($event)" />
<lib-easy-card-grid [items]="cards" [columns]="3" (cardClick)="open($event)" />
`
})
export class Toolbox {
// Buttons to browse by category: icon, colour and count are optional.
readonly categories: EasyChipNavItem[] = [
{ id: 'all', label: 'All', icon: 'apps', count: 1284 },
{ id: 'tools', label: 'Tools', icon: 'build', color: '#b45309', count: 420 },
{ id: 'parts', label: 'Parts', icon: 'settings', color: '#1d4ed8', count: 864 }
];
readonly category = signal('all');
// A setting or a view: one choice among a few.
readonly views: EasySegmentedOption[] = [
{ id: 'list', label: 'List', icon: 'view_list' },
{ id: 'grid', label: 'Grid', icon: 'grid_view' }
];
readonly view = signal('list');
// A language picker that knows nothing about any translation library.
readonly languages: EasyLanguage[] = [
{ code: 'en', name: 'English', flag: 'gb' },
{ code: 'fr', name: 'Français', flag: 'fr' },
{ code: 'ar', name: 'العربية', flag: 'dz' }
];
readonly language = signal('en');
readonly cards: EasyCardConfig[] = [
{ id: 'receive', icon: 'move_to_inbox', title: 'Receive goods', description: 'Record a delivery.', clickable: true, cta: 'Open' },
{ id: 'count', icon: 'fact_check', title: 'Count stock', description: 'Start an inventory.', clickable: true, cta: 'Open' },
{ id: 'report', icon: 'insights', title: 'Reports', description: 'Stock movements by month.' }
];
open(card: EasyCardConfig): void {
// card.id tells which card was used.
console.log(card.id);
}
}
The components
| Component | Selector | Inputs | Outputs | Use it for |
|---|---|---|---|---|
| EasySegmented | lib-easy-segmented | options, selected, ariaLabel, size, iconOnly, collapseInactive, stretch, semantics, panelId, idPrefix | selectedChange | A setting, a view or the pages of an application |
| EasyChipNav | lib-easy-chip-nav | items, selected, ariaLabel | selectedChange | Browsing by category, with icon, colour and count |
| EasyLanguageSwitch | lib-easy-language-switch | languages, selected, layout, display, ariaLabel | selectedChange | Choosing the language, as a menu or inline |
| EasyCard, EasyCardGrid | lib-easy-card, lib-easy-card-grid | config; items, minWidth, columns | cardClick, actionClick | Cards with icon, title, description and actions |
| EasyStat | lib-easy-stat | config | none | A key figure with a label and a tone |
| EasyPageHeader | lib-easy-page-header | config | none | Icon, title, subtitle and a slot for controls |
Design rules
- Standalone components with OnPush change detection: import the class and use the selector.
- Data goes in through inputs and interactions come out through outputs. Nothing reads a service or a route.
- Every shape of data is a TypeScript interface exported from easy-mat-ui-ng20, for example EasyChipNavItem or EasyCardConfig.
- The language picker knows nothing about any translation library: you give it languages and react to selectedChange.
- Colours come from the CSS variables of the theme, so the components follow the light and dark themes.
Keyboard, screen readers and right to left
- The segmented control follows the pattern of a radio group, or of a tab list with semantics="tab": the arrow keys, Home and End move the choice, and the arrows follow the text direction.
- The category buttons expose their pressed state with aria-pressed.
- The demo runs in Arabic with a right-to-left layout, and the language picker and the other components follow the direction of the page.
Read next
- Generate an Angular page from the command line
- Angular Material dark mode and theming
- Angular Material table with filters and dialogs
Open the live demo or read the source on GitHub.