Usage
ng-oat gives you three ways to work with Oat UI in Angular: standalone components, attribute directives, and Signal Forms fields. Here's when and how to use each one.
Standalone components
Most ng-oat exports are standalone Angular components. Import them directly into your component's imports array — no NgModule needed.
import { Component } from '@angular/core';
import {
NgOatButton,
NgOatBadge,
NgOatAlert,
NgOatCard,
NgOatCardHeader,
NgOatCardFooter,
} from '@letsprogram/ng-oat';
@Component({
selector: 'app-dashboard',
imports: [
NgOatButton,
NgOatBadge,
NgOatAlert,
NgOatCard,
NgOatCardHeader,
NgOatCardFooter,
],
template: `
<ng-oat-alert variant="success">
Welcome back!
</ng-oat-alert>
<ng-oat-card>
<ng-oat-card-header>
<h3>Stats</h3>
<ng-oat-badge variant="outline">Today</ng-oat-badge>
</ng-oat-card-header>
<p>Your dashboard content here.</p>
<ng-oat-card-footer>
<ng-oat-button size="small">View details</ng-oat-button>
</ng-oat-card-footer>
</ng-oat-card>
`,
})
export class DashboardComponent {} Each component has typed input() signals for configuration and output() for events. Check the individual component docs for the full API.
Common component patterns
| Pattern | Example | Why |
|---|---|---|
| Variant styling | variant="danger" | Maps to Oat's data-variant attributes |
| Size control | size="small" | Applies Oat's size classes |
| Two-way binding | [(checked)]="isOn" | Model signals for forms and toggles |
| Content projection | <ng-oat-card>...</ng-oat-card> | Lets you control the inner markup |
| Template ref access | #sidebar="ngOatSidebar" | Call methods like sidebar.toggle() |
Attribute directives
For cases where you want full control over the HTML markup, ng-oat provides attribute directives that attach Oat behavior to your own elements.
import { Component } from '@angular/core';
import { NgOatTooltip, NgOatDropdown } from '@letsprogram/ng-oat';
@Component({
selector: 'app-toolbar',
imports: [NgOatTooltip, NgOatDropdown],
template: `
<!-- Tooltip directive on any element -->
<button ngOatTooltip="Save document"
ngOatTooltipPosition="bottom">
💾 Save
</button>
<!-- Dropdown directive on a details element -->
<details ngOatDropdown>
<summary>Actions ▾</summary>
<ul>
<li><a href="#">Edit</a></li>
<li><a href="#">Duplicate</a></li>
<li><a href="#">Delete</a></li>
</ul>
</details>
`,
})
export class ToolbarComponent {}The five directives available are:
| Directive | Selector | What it does |
|---|---|---|
NgOatDropdown | [ngOatDropdown] | Click-toggle dropdown with outside-click close |
NgOatTooltip | [ngOatTooltip] | Hover/focus tooltip with positioning |
NgOatSidebar | [ngOatSidebar] | Sidebar layout with scroll lock and ESC close |
NgOatTabs | [ngOatTabs] | Tab switching with keyboard arrow navigation |
NgOatDialog | [ngOatDialog] | Modal dialog with focus trap and backdrop close |
Directives are lower-level than components. They give you the JavaScript behavior (event handling, state management, ARIA) and leave the HTML structure entirely up to you. Use them when you need markup that doesn't fit the component's opinionated template.
Component wrappers vs. directives
ng-oat ships both a directive and a full component for Dropdown, Tooltip, Sidebar, Tabs, and Dialog. Here's when to pick which:
| Use case | Pick | Why |
|---|---|---|
| Quick standard UI | Component (NgOatDropdownComponent) | Pre-built template, less boilerplate |
| Custom HTML structure | Directive (NgOatDropdown) | Full markup control |
| Inside existing layout | Directive | Doesn't add extra wrapper elements |
| Maximum consistency | Component | Ensures Oat's expected markup is always used |
Using Oat CSS directly
Not everything needs an Angular wrapper. Oat CSS styles semantic HTML elements directly, so you can write plain HTML for many things:
<!-- These all work with zero Angular wrappers — just Oat CSS -->
<!-- Semantic table -->
<table>
<thead>
<tr><th>Name</th><th>Status</th><th>Role</th></tr>
</thead>
<tbody>
<tr><td>Alice</td><td>Active</td><td>Admin</td></tr>
<tr><td>Bob</td><td>Inactive</td><td>Editor</td></tr>
</tbody>
</table>
<!-- Native progress -->
<label>Upload progress</label>
<progress value="72" max="100"></progress>
<!-- Native details / summary -->
<details>
<summary>More information</summary>
<p>Oat styles this automatically.</p>
</details>
<!-- Semantic form -->
<form>
<label>Username <input type="text" placeholder="Enter username" /></label>
<label>Password <input type="password" /></label>
<button type="submit">Log in</button>
</form>Tables, forms, typography, progress bars, and meters all work beautifully with just semantic HTML + Oat CSS. You only need ng-oat components for interactive behavior (dropdowns, dialogs, toasts) or when you want typed Angular inputs for configuration.
Lazy loading
Since every ng-oat component is standalone, they work naturally with Angular's lazy-loaded routes. Components are tree-shaken — only the ones you import end up in your bundle.
// app.routes.ts — ng-oat components are tree-shaken per route
export const routes: Routes = [
{
path: 'dashboard',
loadComponent: () => import('./pages/dashboard')
.then(m => m.DashboardPage),
// Only NgOatCard, NgOatBadge, etc. used in DashboardPage
// are included in this chunk
},
{
path: 'settings',
loadComponent: () => import('./pages/settings')
.then(m => m.SettingsPage),
// Only NgOatInput, NgOatSelect, etc. used here
},
];Testing
ng-oat components are tested with Vitest. The library itself has 62+ tests across all directives and services. When testing your own components that use ng-oat, just import the components in your test setup as you would in a regular component.
// dashboard.spec.ts
import { render, screen } from '@testing-library/angular';
import { DashboardPage } from './dashboard';
describe('DashboardPage', () => {
it('should render the alert', async () => {
await render(DashboardPage);
expect(screen.getByText('Welcome back!')).toBeTruthy();
});
it('should render badges', async () => {
await render(DashboardPage);
const badge = screen.getByText('Active');
expect(badge).toBeTruthy();
});
});Explore the components
Now that you know the patterns, dive into the individual component demos. Each page includes a live preview, the full API reference, and a TypeScript code sample showing imports and basic usage.