@fastann/angular
v1.0.6
Published
Annotation library — Angular 17+ adapter (ng-packagr)
Readme
@fastann/angular
Angular 17+ adapter for FastAnn — a lightweight annotation overlay library that lets you attach contextual notes, rules, warnings, tasks, and user comments to any element in your UI.
- Zero layout impact — annotations render as floating dialogs, not inline elements
- Signals-based reactivity (Angular 17+ Signals API)
- Optional REST backend for persisting annotations across users
- Supports system annotations (defined in code), database annotations (loaded from API), and user comments
- Dark mode, multi-language (
en,vi,zh,ja,ko,fr,de,es,pt,it,ru,tr,id,th,hi,ar,nl,pl), permission system
Table of Contents
- Requirements
- Installation
- Peer Dependencies
- Global Styles
- Step 1 — Provide the API URL
- Step 2 — Add the Stage Component
- Step 3 — Make Cells Annotatable
- Step 4 — Mark Any Element by Key
- Step 5 — (Optional) Add the Built-in Panel
- Step 6 — Set the Current User
- Step 7 — Multi-language Support
- Step 8 — Load Annotations from the API
- Vite Pre-bundling Note
- API Reference
- Annotation Types
- Permission Keys
Requirements
| Dependency | Version |
|---|---|
| Angular | >=17.0.0 |
| @angular/common | >=17.0.0 |
| @angular/forms | >=17.0.0 |
| @angular/router | >=17.0.0 |
| rxjs | >=7.0.0 |
| Node.js | >=18 |
Installation
npm install @fastann/angularPeer Dependencies
The library does not bundle Angular itself. Make sure your project already has:
npm install @angular/common @angular/core @angular/forms @angular/router rxjsGlobal Styles
Import the required CSS once in your global stylesheet (styles.scss / styles.css):
// styles.scss
@import '@fastann/angular/styles/annotation.css';Or in angular.json:
"styles": [
"src/styles.scss",
"node_modules/@fastann/angular/styles/annotation.css"
]This file provides dot-badge colors, dialog base styles, and dark-mode variables. Skipping it will result in unstyled badge indicators.
Step 1 — Provide the API URL
In app.config.ts, register the API base URL using the ANN_API_URL injection token. The library uses this to GET and POST annotations from your backend.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideHttpClient } from '@angular/common/http';
import { ANN_API_URL } from '@fastann/angular';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAnimations(),
provideHttpClient(), // required — services use HttpClient
{ provide: ANN_API_URL, useValue: 'https://your-api.example.com/api' },
],
};No backend? You can omit
{ provide: ANN_API_URL, ... }— the library falls back to'/api'but all HTTP calls will fail silently. System annotations (defined in code) still work fully offline.
Step 2 — Add the Stage Component
AnnotationStageComponent is the global overlay that renders floating dialogs and SVG connectors. Add it once at the application root so it sits above all page content.
// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AnnotationStageComponent } from '@fastann/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
AnnotationStageComponent, // add here
],
template: `
<main>
<router-outlet />
</main>
<!-- Place at the end of the template, outside <main> -->
<app-annotation-stage />
`,
})
export class AppComponent {}Step 3 — Make Cells Annotatable
Use the AnnotationCellDirective ([annCell] / ann-cell-key) to attach annotation metadata to any HTML element. The directive:
- Renders a colored dot-badge inside the element when an annotation exists
- Handles click-to-open dialog
- Syncs with
AnnotationServiceandCustomAnnotationService
Import the directive in the component that owns the template:
imports: [AnnotationCellDirective, DotVisiblePipe]Option A: Inline attribute API (HTML-only)
Declare a system annotation entirely in the template. The library auto-generates an id and persists it to the API if it does not exist yet.
<!-- Table header cell -->
<th
ann-cell-key="col:sales"
ann-type="info"
ann-source="Finance"
ann-title="Sales (USD)"
ann-body="Gross sales in USD, net of refunds and discounts."
ann-tag="KPI"
[ann-default-size-w]="300"
[ann-default-size-h]="160"
>
Sales (USD)
</th>All ann-* attributes:
| Attribute | Type | Description |
|---|---|---|
| ann-cell-key | string (required) | Unique key for this cell. Use a stable, path-style string: "col:revenue", "row1:margin" |
| [annCell] | Annotation \| undefined | Pass an existing Annotation object (from customService.forCell(key)) instead of declaring inline |
| ann-type | AnnotationType | info | rule | comment | warning | formula | action | task |
| ann-source | string | Source label shown in the dialog header (e.g. "Finance", "System") |
| ann-title | string | Short title |
| ann-body | string | Full description body |
| ann-tag | string | Tag/label (e.g. "KPI", "Q1 2025") |
| ann-status | TaskStatus | to_do | in_progress | done — only relevant for task type |
| [ann-default-size-w] | number | Default dialog width in px |
| [ann-default-size-h] | number | Default dialog height in px |
| [ann-default-pos-x] | number | Default dialog X offset from anchor |
| [ann-default-pos-y] | number | Default dialog Y offset from anchor |
| [ann-is-syn-db] | boolean | true (default) — sync this annotation to the database on first load |
Option B: Register from component (TypeScript)
For i18n labels or complex logic, register annotations in TypeScript using CustomAnnotationService.registerSystemAnnotation():
// my-page.component.ts
import { Component, OnInit, effect, Injector } from '@angular/core';
import { AnnotationCellDirective, CustomAnnotationService, AnnotationLabelsService } from '@fastann/angular';
@Component({
selector: 'app-my-page',
standalone: true,
imports: [AnnotationCellDirective],
template: `
<div ann-cell-key="kpi-revenue">$4.2M</div>
`,
})
export class MyPageComponent implements OnInit {
constructor(
public customService: CustomAnnotationService,
private labels: AnnotationLabelsService,
private injector: Injector,
) {
// Re-register when language changes
effect(() => {
this.labels.labels(); // reactive dependency
this.registerAnnotations();
}, { injector: this.injector, allowSignalWrites: true });
}
ngOnInit() {
this.registerAnnotations();
}
private registerAnnotations() {
const lang = this.labels.getLang();
this.customService.registerSystemAnnotation('kpi-revenue', {
type: 'info',
source: 'Finance',
title: lang === 'vi' ? 'Tổng doanh thu' : 'Total Revenue',
body: lang === 'vi'
? 'Doanh thu gộp, đã trừ hoàn trả, quy đổi về USD.'
: 'Gross revenue net of refunds, FX-normalised to USD.',
tag: lang === 'vi' ? 'Định nghĩa' : 'Definition',
});
}
}Option C: Dynamic rows from API data
For table rows where the key is built from row data:
<!-- Template -->
<tr *ngFor="let row of rows">
<td>{{ row.product }}</td>
<!-- Pass the API annotation object + a unique key per row -->
<td
[annCell]="customService.forCell(row.id + ':sales')"
[ann-cell-key]="row.id + ':sales'"
>
{{ row.sales | number }}
</td>
</tr>customService.forCell(key) returns the Annotation loaded from the API for that key, or undefined if none exists.
Step 4 — Mark Any Element by Key
AnnotationKeyDirective ([annKey]) marks any element as an annotation anchor without a pre-declared annotation. Use it on headers, labels, or any structural element where users can add their own notes at runtime.
imports: [AnnotationKeyDirective]<th [annKey]="'th:product'">Product</th>
<label [annKey]="'form:username'">Username</label>The directive sets data-ck on the element and renders a badge if a custom annotation exists for that key in the database.
Step 5 — (Optional) Add the Built-in Panel
AnnPanelComponent provides a floating panel with filter controls, dark-mode toggle, language switch, and permission management. It is optional — you can build your own toolbar using AnnotationService directly.
// my-page.component.ts
imports: [AnnPanelComponent]<!-- my-page.component.html -->
<ann-panel />
<div class="page-content">
<!-- your page -->
</div>The panel is draggable and stays fixed to the viewport. Place it once per page (or at the app root).
Step 6 — Set the Current User
User identity controls who can perform permission-gated actions (edit, delete, manage permissions). Set the current user after login:
// auth.service.ts or login.component.ts
import { AnnotationAuthService, AnnUser } from '@fastann/angular';
@Injectable({ providedIn: 'root' })
export class AuthService {
constructor(private annAuth: AnnotationAuthService) {}
onLoginSuccess(apiUser: any) {
const annUser: AnnUser = {
code: apiUser.employee_code, // e.g. "EMP001"
name: apiUser.display_name, // e.g. "Alice"
role: apiUser.role, // e.g. "admin"
permissions: apiUser.ann_permissions, // AnnPermissionItem[] from API
};
this.annAuth.setCurrentUser(annUser);
}
onLogout() {
this.annAuth.setCurrentUser(null);
}
}AnnUser is persisted to localStorage automatically and restored on next page load.
Step 7 — Multi-language Support
The library ships with these language codes: en, vi, zh, ja, ko, fr, de, es, pt, it, ru, tr, id, th, hi, ar, nl, pl.
Switch language at runtime:
import { AnnotationLabelsService } from '@fastann/angular';
@Component({ ... })
export class AppComponent {
constructor(private labels: AnnotationLabelsService) {}
switchToVietnamese() {
this.labels.setLang('vi');
}
switchToEnglish() {
this.labels.setLang('en');
}
switchToChinese() {
this.labels.setLang('zh');
this.labels.setLang('ja');
this.labels.setLang('fr');
}
}The current language is a Signal — all library UI components react automatically.
Step 8 — Load Annotations from the API
Call CustomAnnotationService.loadFromApi() in the component that owns the page, passing the current domain and route. This fetches all annotations for that page from the backend.
// my-page.component.ts
import { Component, OnInit, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Router } from '@angular/router';
import { CustomAnnotationService } from '@fastann/angular';
@Component({ ... })
export class MyPageComponent implements OnInit {
constructor(
public customService: CustomAnnotationService,
private router: Router,
@Inject(DOCUMENT) private document: Document,
) {}
ngOnInit() {
const domain = this.document.location.hostname; // e.g. "app.example.com"
const route = this.router.url; // e.g. "/sales/dashboard"
this.customService.loadFromApi(domain, route);
}
}The library calls GET /api/Ann/get_all?domain=...&router=...&ann_lang=... and merges results with any system annotations registered in code.
Vite Pre-bundling Note
If you use @angular-devkit/build-angular v17 (which uses Vite as dev server), exclude @fastann/angular from Vite's dep pre-bundling to avoid esbuild conflicts. Add to angular.json:
// angular.json — inside "serve" > "options"
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"prebundle": {
"exclude": ["@fastann/angular"]
}
}
}API Reference
Services
AnnotationService
Central store for open dialogs and global UI state.
| Member | Type | Description |
|---|---|---|
| dialogs | Signal<OpenDialog[]> | Currently open annotation dialogs |
| mode | Signal<FilterMode> | Active filter: 'all' | annotation type | 'none' |
| dark_mode | Signal<boolean> | Dark mode state |
| add_mode | Signal<boolean> | Whether "add annotation" mode is active |
| source_filter | Signal<SourceFilter> | 'all' | 'system' | 'user' |
| status_filter | Signal<StatusFilter> | 'all' | 'to_do' | 'in_progress' | 'done' |
| toggleDark() | void | Toggle dark mode |
| toggleAddMode() | void | Toggle add-annotation mode |
| setSourceFilter(f) | void | Set source filter |
| setStatusFilter(f) | void | Set status filter |
CustomAnnotationService
Manages system + database annotations.
| Member | Description |
|---|---|
| loadFromApi(domain, router) | Fetch all annotations for the given page from the backend |
| registerSystemAnnotation(cellKey, ann) | Declare a system annotation in code |
| forCell(cellKey) | Returns the active Annotation for a cell key, or undefined |
| entries | Signal<CustomEntry[]> — all loaded annotations |
UserAnnotationService
Manages per-annotation user comments/notes.
| Member | Description |
|---|---|
| items | Signal<UserAnnotation[]> — all loaded user notes |
| loadForAnnotation(id) | Fetch notes for a specific annotation |
| hasForCell(annId) | Returns true if there are user notes for this annotation |
AnnotationAuthService
User identity store.
| Member | Description |
|---|---|
| currentUser | Returns the current AnnUser or null |
| setCurrentUser(user) | Set/clear the current user (persisted to localStorage) |
AnnotationLabelsService
Language / i18n service.
| Member | Description |
|---|---|
| labels() | Signal returning the full AnnotationLabels object |
| getLang() | Returns the current Lang code |
| setLang(lang) | Switch language: 'en' | 'vi' | 'zh' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'pt' | 'it' | 'ru' | 'tr' | 'id' | 'th' | 'hi' | 'ar' | 'nl' | 'pl' |
AnnotationStageService
Internal stage/cell registration. Rarely needed directly.
Components & Directives
| Symbol | Selector | Description |
|---|---|---|
| AnnotationStageComponent | <app-annotation-stage> | Global overlay — add once at app root |
| AnnPanelComponent | <ann-panel> | Built-in floating panel with controls |
| AnnotationCellDirective | [annCell], ann-cell-key | Makes any element an annotatable cell |
| AnnotationKeyDirective | [annKey] | Marks an element by key (no pre-declared annotation) |
| DotVisiblePipe | dotVisible | Pipe for conditional dot-badge visibility |
Annotation Types
| Type | Badge Color | Use case |
|---|---|---|
| info | Blue | Definitions, descriptions |
| rule | Red-orange | Business rules, constraints |
| comment | Green | General comments |
| warning | Amber | Data quality issues, alerts |
| formula | Purple | Calculation logic |
| action | Indigo | Action items |
| task | Dark blue | Tasks with to_do / in_progress / done status |
Permission Keys
The AnnPermissionDef list used by the built-in permission panel:
| Key | Description |
|---|---|
| add_new_dialog_custom | Create a new annotation |
| edit_status | Change task status |
| edit_title | Edit title |
| edit_body | Edit body |
| edit_tag | Edit tag |
| edit_type | Change annotation type |
| edit_assignee | Assign to a user |
| edit_due_date | Set due date |
| set_position_size | Move / resize the dialog |
| delete_dialog_custom | Delete an annotation |
| view_comment | View user comments |
| send_comment | Post a user comment |
| delete_comment | Delete a comment |
| edit_permission | Manage page permissions |
| view_logs | View activity logs |
| set_anchor | Pin dialog anchor position |
Import the canonical list in your own code:
import { ANN_PERMISSION_LIST } from '@fastann/angular';
// ANN_PERMISSION_LIST: readonly AnnPermissionDef[]License
MIT
