본문으로 건너뛰기

Quick Start: Angular

Angular 앱에 AI 채팅을 추가할 수 있습니다. 이 가이드를 마치면 TanStack AI와 OpenAI로 구동되는 스트리밍 채팅 컴포넌트를 갖게 됩니다.

팁: 개별 AI 제공자에 가입하지 않으려면 OpenRouter를 사용하세요. 단일 API 키로 300개 이상의 모델에 액세스할 수 있어 가장 쉽게 시작할 수 있습니다.

설치

npm install @tanstack/ai @tanstack/ai-angular @tanstack/ai-openai
# or
pnpm add @tanstack/ai @tanstack/ai-angular @tanstack/ai-openai
# or
yarn add @tanstack/ai @tanstack/ai-angular @tanstack/ai-openai

서버 설정

Angular 앱은 일반적으로 별도의 백엔드를 사용합니다. 다음은 채팅 응답을 스트리밍하는 Express 서버입니다.

import express from 'express'
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const app = express()
app.use(express.json())

app.post('/api/chat', async (req, res) => {
const { messages } = req.body

if (!process.env.OPENAI_API_KEY) {
res.status(500).json({ error: 'OPENAI_API_KEY not configured' })
return
}

try {
// `chat()` uses the AG-UI `threadId` for devtools correlation
// when available — no need to plumb `conversationId` manually.
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
})

const response = toServerSentEventsResponse(stream)
res.writeHead(response.status, Object.fromEntries(response.headers))

const body = response.body
if (body) {
const reader = body.getReader()
const pump = async () => {
const { done, value } = await reader.read()
if (done) {
res.end()
return
}
res.write(value)
await pump()
}
await pump()
}
} catch (error) {
res.status(500).json({
error: error instanceof Error ? error.message : 'An error occurred',
})
}
})

app.listen(3000, () => console.log('Server running on port 3000'))

팁: TanStack AI SSE 형식을 반환하는 백엔드라면 무엇이든 작동합니다. Fastify, Hono, Nitro 또는 다른 Node.js 프레임워크를 사용할 수 있습니다.

클라이언트 설정

injectChat 함수를 사용하여 독립적인 ChatComponent를 만듭니다.

import { Component, signal } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { injectChat } from '@tanstack/ai-angular'
import { fetchServerSentEvents } from '@tanstack/ai-client'

@Component({
selector: 'app-chat',
standalone: true,
imports: [FormsModule],
template: `
<div class="chat">
<div class="messages">
@for (message of chat.messages(); track message.id) {
<div [class]="message.role">
<strong>{{ message.role === 'assistant' ? 'Assistant' : 'You' }}</strong>
@for (part of message.parts; track $index) {
@if (part.type === 'text') {
<p>{{ part.content }}</p>
}
}
</div>
}
</div>

<form (ngSubmit)="handleSubmit()">
<input
[(ngModel)]="input"
name="input"
placeholder="Type a message..."
[disabled]="chat.isLoading()"
/>
<button
type="submit"
[disabled]="!input().trim() || chat.isLoading()"
>
Send
</button>
</form>
</div>
`,
})
export class ChatComponent {
// injectChat is called in a field initializer — this is a valid injection context.
chat = injectChat({
connection: fetchServerSentEvents('/api/chat'),
})

input = signal('')

handleSubmit() {
const text = this.input().trim()
if (text && !this.chat.isLoading()) {
this.chat.sendMessage(text)
this.input.set('')
}
}
}

환경 변수

API 키가 포함된 .env 파일(설정에 따라 .env.local)을 만듭니다.

# OpenRouter (recommended — access 300+ models with one key)
OPENROUTER_API_KEY=sk-or-...

# OpenAI
OPENAI_API_KEY=your-openai-api-key

서버는 런타임에 이 키를 읽습니다. 브라우저에 절대 노출하지 마세요.

Angular 관련 참고 사항

상태는 Angular Signal로 노출됩니다. injectChat 함수는 읽기 전용 Signal로 감싼 상태를 반환합니다. 함수처럼 호출하여 읽습니다.

// In component class
if (this.chat.isLoading()) { /* ... */ }
const count = this.chat.messages().length

// In template — same syntax, no .value needed
<!-- In template, call the signal as a function -->
@if (chat.isLoading()) {
<p>Thinking...</p>
}
<span>{{ chat.messages().length }} messages</span>

injectChat은 주입 컨텍스트에서 호출해야 합니다. Angular의 의존성 주입에서는 컴포넌트 생성 중에 inject()를 호출해야 합니다. 권장 방식은 위에 나온 필드 초기화입니다. 생성자나 runInInjectionContext 내부에서 호출할 수도 있습니다.

import { injectChat } from '@tanstack/ai-angular'
import { fetchServerSentEvents } from '@tanstack/ai-client'

// Field initializer (recommended)
export class MyComponentA {
chat = injectChat({ connection: fetchServerSentEvents('/api/chat') })
}

// Constructor
export class MyComponentB {
chat: ReturnType<typeof injectChat>
constructor() {
this.chat = injectChat({ connection: fetchServerSentEvents('/api/chat') })
}
}

주입 컨텍스트 밖에서 injectChat을 호출하면(예: ngOnInit 같은 생명 주기 훅에서) 런타임 오류가 발생합니다.

자동 정리. 이 함수는 내부적으로 DestroyRef를 구독하므로 컴포넌트가 제거되면 진행 중인 요청이 중지됩니다. 수동 정리는 필요하지 않습니다.

React 및 Vue와 동일한 API 형태. @tanstack/ai-react 또는 @tanstack/ai-vue에서 전환하는 경우 injectChat은 동일한 속성(messages, sendMessage, isLoading, error, status, stop, reload, clear)을 반환합니다. 유일한 차이는 각 속성이 React 상태 값이나 Vue ShallowRef가 아니라 Angular Signal이라는 점입니다.

이것으로 끝입니다!

이제 작동하는 Angular 채팅 애플리케이션이 완성되었습니다. injectChat 함수는 다음을 처리합니다.

  • 메시지 상태 관리
  • 스트리밍 응답
  • 로딩 상태
  • 오류 처리

다음 단계

  • 함수 호출을 추가하려면 Tools를 알아보세요.
  • 다른 제공자에 연결하려면 Adapters를 확인하세요.
  • 프레임워크를 비교 중이라면 React Quick Start를 확인하세요.