Mention
A text area with @-style mentions — Syncfusion's Mention: typing a trigger character at the start or after a space opens a caret-anchored suggestion list on the suite's anchored popup, and the pick is inserted as a plain-text token. The value is the text; [(mentions)] reports what was mentioned — item, value and position — and follows every later edit.
Arrows move, Enter or Tab inserts, Escape closes until the next trigger. A single-line field is a WAI-ARIA combobox; the text area keeps its textbox role with aria-activedescendant.
Getting started
Type @ at the start or after a space. Arrows move, Enter or Tab inserts @Name , Escape closes the list until the next trigger. allowSpaces lets a query run across a space (@Ada Lo); a mention edited away drops out of mentions.
Mentioned ids: —
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { OgeMention } from '@oge-ui/inputs';
import type { OgeMentionToken } from '@oge-ui/inputs';
interface User {
id: number;
name: string;
role: string;
}
const USERS: User[] = [
{ id: 1, name: 'Ada Lovelace', role: 'Engineer' },
{ id: 2, name: 'Alan Turing', role: 'Researcher' },
{ id: 3, name: 'Grace Hopper', role: 'Admiral' },
{ id: 4, name: 'Margaret Hamilton', role: 'Director' },
];
@Component({
selector: 'demo-root',
imports: [OgeMention],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Type @ at the start or after a space: a caret-anchored list opens.
Arrows move, Enter or Tab inserts "@Name ", Escape closes it until the
next trigger. The value is plain text; [(mentions)] reports what was
mentioned and where, and follows later edits. -->
<oge-mention
label="Comment"
[items]="users"
displayExpr="name"
valueExpr="id"
[allowSpaces]="true"
[(value)]="text"
[(mentions)]="mentioned"
/>
<p>Mentioned ids: {{ ids() }}</p>
`,
})
export class Demo {
protected readonly users = USERS;
protected readonly text = signal('');
protected readonly mentioned = signal<readonly OgeMentionToken<User>[]>([]);
protected readonly ids = computed(() =>
this.mentioned().map((m) => m.value).join(', '),
);
}Several triggers
triggers takes several characters, each with its own items and expressions — people on @, tags on #. [multiline]="false" renders a single-line combobox.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeMention } from '@oge-ui/inputs';
import type { OgeMentionTrigger } from '@oge-ui/inputs';
interface Tag {
name: string;
}
@Component({
selector: 'demo-root',
imports: [OgeMention],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- One field, several trigger characters — each with its own items and
expressions. -->
<oge-mention label="Task" [triggers]="triggers" [multiline]="false" />
`,
})
export class Demo {
protected readonly triggers: OgeMentionTrigger<Tag>[] = [
{ char: '@', items: [{ name: 'ada' }, { name: 'grace' }], displayExpr: 'name' },
{ char: '#', items: [{ name: 'urgent' }, { name: 'bug' }, { name: 'docs' }], displayExpr: 'name' },
];
}Remote suggestions
items may be a function of the typed query returning items or a promise: it is debounced by searchTimeout, stale answers are dropped, and loading / error rows show meanwhile. searchChanged reports each query for your own fetching.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeMention } from '@oge-ui/inputs';
interface User {
id: number;
name: string;
role: string;
}
const USERS: User[] = [
{ id: 1, name: 'Ada Lovelace', role: 'Engineer' },
{ id: 2, name: 'Alan Turing', role: 'Researcher' },
{ id: 3, name: 'Grace Hopper', role: 'Admiral' },
{ id: 4, name: 'Margaret Hamilton', role: 'Director' },
];
@Component({
selector: 'demo-root',
imports: [OgeMention],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- items may be a function of the typed query (sync or a promise):
it is debounced by searchTimeout, stale answers are ignored, and the
list shows loading / error rows meanwhile. -->
<oge-mention
label="Message"
[items]="search"
displayExpr="name"
[searchTimeout]="200"
[minSearchLength]="1"
/>
`,
})
export class Demo {
protected readonly search = (query: string): Promise<User[]> =>
new Promise((resolve) =>
setTimeout(
() =>
resolve(
USERS.filter((u) => u.name.toLowerCase().includes(query.toLowerCase())),
),
300,
),
);
}Custom suggestion rows
An child renders each row; its context carries the item ($implicit), index, trigger, query and active.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeMention, OgeMentionItemTemplate } from '@oge-ui/inputs';
interface User {
id: number;
name: string;
role: string;
}
const USERS: User[] = [
{ id: 1, name: 'Ada Lovelace', role: 'Engineer' },
{ id: 2, name: 'Alan Turing', role: 'Researcher' },
{ id: 3, name: 'Grace Hopper', role: 'Admiral' },
{ id: 4, name: 'Margaret Hamilton', role: 'Director' },
];
@Component({
selector: 'demo-root',
imports: [OgeMention, OgeMentionItemTemplate],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- A custom suggestion row; the context carries the item, index,
trigger, query and whether the row is active. -->
<oge-mention label="Reply" [items]="users" displayExpr="name">
<ng-template ogeMentionItemTemplate let-user>
<span class="flex flex-col">
<strong>{{ user.name }}</strong>
<small>{{ user.role }}</small>
</span>
</ng-template>
</oge-mention>
`,
})
export class Demo {
protected readonly users = USERS;
}Inside a form
A FormValueControl and a ControlValueAccessor — the bound value is the text itself, so every validator works unchanged.
Valid: false
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeMention } from '@oge-ui/inputs';
import { ReactiveFormsModule, FormControl, Validators } from '@angular/forms';
interface User {
id: number;
name: string;
role: string;
}
const USERS: User[] = [
{ id: 1, name: 'Ada Lovelace', role: 'Engineer' },
{ id: 2, name: 'Alan Turing', role: 'Researcher' },
{ id: 3, name: 'Grace Hopper', role: 'Admiral' },
{ id: 4, name: 'Margaret Hamilton', role: 'Director' },
];
@Component({
selector: 'demo-root',
imports: [OgeMention, ReactiveFormsModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- A FormValueControl and a ControlValueAccessor — the value is the text. -->
<oge-mention
label="Note"
[items]="users"
displayExpr="name"
hint="Mention a reviewer with @"
[formControl]="note"
/>
`,
})
export class Demo {
protected readonly users = USERS;
protected readonly note = new FormControl('', [Validators.required]);
}