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.
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.
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.
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.
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.
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 inputthis.ac.selectOption(option);}ngOnDestroy(): void {this.ac.destroy();}}