# Integration Tiers | Angular AI Autocomplete Docs

> For the complete documentation index, see [llms.txt](https://ai-autocomplete.com/llms.txt). A markdown version of any page on this site is available by appending `.md` to its URL path — the homepage is at `/index.md`.

Integration Tiers guide for integrating the AI Autocomplete SDK with Angular. Code examples, setup, and API reference.

Source: https://ai-autocomplete.com/docs/angular/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**

```tsx
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**

```tsx
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**

```tsx
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**

```tsx
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
  }
}
```

The [aiaInput] directive wires your input's events into the controller and mirrors state into ARIA attributes. <ai-autocomplete-dropdown> renders the pills + options from the same controller. It's absolutely positioned, so place it inside a position: relative element.

### 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.

Create the controller in the browser

new AIAutocompleteController() must run client-side — its constructor throws during server-side rendering. For Angular Universal / SSR, gate construction with isPlatformBrowser(this.platformId) (e.g. inside ngOnInit) so the controller is only created in the browser. (Tier 1's <ai-autocomplete> handles this for you.)

Always call destroy() in ngOnDestroy

The controller owns RxJS subscriptions and a headless core instance. Always call ac.destroy() in ngOnDestroy — failing to dispose leaks them. (Tier 1 does this for you.)

Always call reset() after submit

Tier 1 auto-resets after its built-in submit. In Tier 2 and Tier 3 you own the input and the submit flow — don't pass onSubmit to the controller. Wire up submit however you like (a button click, a form submit, a custom Enter handler) and call ac.reset() at the end of that handler. It clears the input and rotates the per-session session_id so the server's history stays accurate; skipping it degrades suggestion quality on subsequent submissions.

## 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**

```tsx
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();
  }
}
```

You still feed the input the same way ([aiaInput] for a textarea, or the controller methods for a custom editor), and you still call destroy() in ngOnDestroy and reset() after submit. Tier 3 only changes who renders the suggestions.
