Integration Tiers

Three ways to integrate, from drop-in to fully headless — pick the one matching how much UI you want to own.

Tier 1

Full component

Drop-in <ai-autocomplete>. Owns the input, pills, dropdown, and state. Zero template you have to write.

Use when you want the complete widget with no setup.

Tier 2

Controller + dropdown

AIAutocompleteController + [aiaInput] + <ai-autocomplete-dropdown>. You own the input element; we render the dropdown and pills.

Use when you need a custom input but want our dropdown UI.

Tier 3

Headless

AIAutocompleteController alone — RxJS observables + action methods. You render the dropdown too.

Use when you need full control over every piece of the UI.

Tier 1 — drop-in component

The default. <ai-autocomplete> owns the editor, dropdown, pills, and keyboard handling. Bind [apiConfig] + (submitted) and you're done. It also implements ControlValueAccessor, so [formControl] and [(ngModel)] work.

search.component.ts
import { Component } from "@angular/core";
import { AIAutocompleteComponent } from "@magicx-eng/ai-autocomplete-angular";
@Component({
selector: "app-search",
standalone: true,
imports: [AIAutocompleteComponent],
template: `
<ai-autocomplete
[apiConfig]="apiConfig"
mode="dark"
(submitted)="onSubmit($event)"
></ai-autocomplete>
`,
})
export class SearchComponent {
apiConfig = { apiKey: "pk_v1_your_public_key" };
onSubmit(result) { console.log(result); }
}

Reactive Forms

Bind a Reactive Forms control directly — the component is a ControlValueAccessor. [(ngModel)] works too.

reactive-forms.component.ts
import { ReactiveFormsModule, FormControl } from "@angular/forms";
import { AIAutocompleteComponent } from "@magicx-eng/ai-autocomplete-angular";
@Component({
standalone: true,
imports: [AIAutocompleteComponent, ReactiveFormsModule],
template: `
<ai-autocomplete
[formControl]="query"
[apiConfig]="apiConfig"
></ai-autocomplete>
`,
})
export class FormComponent {
query = new FormControl("");
apiConfig = { apiKey: "pk_v1_your_public_key" };
}

Imperative handle

Grab the component with a template reference variable + @ViewChild to call focus(), blur(), reset(), or setMode() imperatively.

imperative.component.ts
import { Component, ViewChild } from "@angular/core";
import { AIAutocompleteComponent } from "@magicx-eng/ai-autocomplete-angular";
@Component({
standalone: true,
imports: [AIAutocompleteComponent],
template: `<ai-autocomplete #ac [apiConfig]="apiConfig"></ai-autocomplete>`,
})
export class HostComponent {
@ViewChild("ac") ac!: AIAutocompleteComponent;
apiConfig = { apiKey: "pk_v1_your_public_key" };
// this.ac.focus();
// this.ac.blur();
// this.ac.reset();
// this.ac.setMode("dark");
}

Tier 2 — controller + dropdown

Create an AIAutocompleteController, bind your own <textarea> with [aiaInput], and drop in <ai-autocomplete-dropdown>. You own the input element and layout; the controller owns state and exposes it as RxJS observables.

headless.component.ts
import { Component, OnInit, OnDestroy } from "@angular/core";
import {
AIAutocompleteController,
AIAutocompleteDropdownComponent,
AIAutocompleteInputDirective,
} from "@magicx-eng/ai-autocomplete-angular";
@Component({
selector: "app-search",
standalone: true,
imports: [AIAutocompleteDropdownComponent, AIAutocompleteInputDirective],
template: `
<textarea [aiaInput]="ac" placeholder="Ask anything..."></textarea>
<ai-autocomplete-dropdown
[controller]="ac"
mode="auto"
></ai-autocomplete-dropdown>
<button type="button" (click)="submit()">Submit</button>
`,
})
export class SearchComponent implements OnInit, OnDestroy {
ac!: AIAutocompleteController;
ngOnInit(): void {
this.ac = new AIAutocompleteController({
apiConfig: { apiKey: "pk_v1_your_public_key" },
});
}
submit(): void {
sendToBackend(this.ac.getState().text, this.ac.getState().completedParams);
this.ac.reset(); // start a new session
}
ngOnDestroy(): void {
this.ac.destroy(); // release subscriptions + core
}
}

Color mode & position

Pass [mode] ("light", "dark", or "auto") to style a standalone dropdown — it scopes the design tokens to its own root, so no .magicx-aia wrapper is needed. "auto" follows the OS theme. To open it above the input, set optionsPosition: "above" in the controller options — the dropdown reads it from the controller automatically, and arrow-key direction matches since it's the same option. Leave mode unset inside Tier 1, which themes for you.

Custom & rich-text inputs

[aiaInput] is for a <textarea> or <input>. For a contentEditable or rich-text editor, skip the directive and call the controller directly: handleTextChange(text) on every edit, setFocused(bool) on focus/blur, handleKeyDown(event) for Arrow/Enter/Tab/Escape while the dropdown is open, and handleCaretMove(offset) so arrow keys can move into the dropdown.

Tier 3 — headless (bring your own UI)

Skip <ai-autocomplete-dropdown> and render the suggestions UI yourself. Subscribe to dropdown$ (or the individual observables) for the data — the active suggestion's options, activeIndex, and isOpen — and call selectOption() and setActiveDropdownIndex() for the actions. The controller keeps owning state, fetching, filtering, and keyboard logic.

custom-dropdown.component.ts
import { Component, OnInit, OnDestroy } from "@angular/core";
import { CommonModule } from "@angular/common";
import {
AIAutocompleteController,
AIAutocompleteInputDirective,
type SuggestionOption,
} from "@magicx-eng/ai-autocomplete-angular";
@Component({
selector: "app-search",
standalone: true,
imports: [CommonModule, AIAutocompleteInputDirective],
template: `
<textarea [aiaInput]="ac" placeholder="Ask anything..."></textarea>
<!-- Your own dropdown, driven by the controller's dropdown$ stream -->
<ng-container *ngIf="ac.dropdown$ | async as dd">
<ul class="my-dropdown" role="listbox" *ngIf="dd.isOpen">
<li
*ngFor="let option of dd.suggestions[0]?.options ?? []; let i = index"
role="option"
[attr.aria-selected]="i === dd.activeIndex"
(mouseenter)="ac.setActiveDropdownIndex(i)"
(mousedown)="pick($event, option)"
>
{{ option.text }}
</li>
</ul>
</ng-container>
`,
})
export class SearchComponent implements OnInit, OnDestroy {
ac!: AIAutocompleteController;
ngOnInit(): void {
this.ac = new AIAutocompleteController({
apiConfig: { apiKey: "pk_v1_your_public_key" },
});
}
pick(event: MouseEvent, option: SuggestionOption): void {
event.preventDefault(); // keep focus in the input
this.ac.selectOption(option);
}
ngOnDestroy(): void {
this.ac.destroy();
}
}