- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Implement Generative AI Workflows in Angular
In this Code Lab, learners build an Angular feature that connects user prompts to a generative AI workflow, streams model output into the interface, and enriches responses with application context. They manage AI interaction state, route model access through a secure server-mediated boundary, and handle structured and non-deterministic AI outputs in a way that supports a clear and resilient user experience. The lab focuses on practical implementation of generative AI workflows in Angular applications.
Lab Info
Table of Contents
-
Challenge
Introduction
Welcome to the Implement Generative AI Workflows in Angular Code Lab. In this hands-on lab, you build the document-assistant feature for a customer-facing Angular application — wiring user prompts to a generative AI gateway, streaming Server-Sent Events into the interface, enriching prompts with profile context behind a secure server-mediated boundary, and defending the UI against the non-deterministic shape of AI responses. When every task is complete, you run the full stack and watch your work pay off in the browser: a live streaming response, a verified Bearer token in the request headers, and profile context flowing securely from your session to the gateway.
### PrerequisitesAbout the tools and concepts
A `BehaviorSubject` is an RxJS observable that holds a current value and emits it to every new subscriber. It is the standard Angular pattern for exposing reactive state from a service: components subscribe once, and the template re-renders whenever the service pushes a new state object. The same pattern works in every Angular version that ships RxJS, which makes it a safe default for Angular 16 and earlier where Signals are still in developer preview.Server-Sent Events (SSE) are a one-way streaming protocol where the server holds the HTTP connection open and writes events as
event: <name>\ndata: <json>\n\n. The browser reads each event as it arrives, which is what lets the UI render generated text token-by-token instead of waiting for the entire completion. Angular'sHttpClientwaits for the full response body, so streaming clients usefetchwith the response'sReadableStreaminstead.A server-mediated boundary sits between the frontend and the model provider. The provider's API key lives only on the gateway, not in the browser, and the frontend authenticates to the gateway with a session token. This is the standard pattern for production generative AI: it keeps the provider credential server-side, lets you rate-limit and audit calls in one place, and gives you a clean spot to merge user-specific context into the prompt.
An
HttpInterceptorruns on every outgoingHttpClientrequest, which makes it the right place to attach the session token in a single, auditable location instead of scattering header logic across services. A safe interceptor never adds the token to arbitrary URLs — it confirms the request targets a trusted API origin first.A type guard is a function whose return type is
parameter is SomeType. When the function returnstrue, TypeScript narrows the parameter to that type for the rest of the calling scope, which lets the caller pass anunknownvalue through a runtime shape check before treating it as the typed response.Before starting this lab, you should be comfortable with:
- Angular basics — components, templates, and routing
- TypeScript basics — classes, interfaces, and imports
- HTTP and async basics —
HttpClient, APIs, and asynchronous workflows - Reactive state basics — Observables and UI state updates
- Generative AI basics — prompts, responses, and API-based model interaction
The lab environment is ready to use. The AI gateway is already running on port 3000 in the background, and the Angular dev-server proxy forwards
/api/*calls from the app to the gateway. Runng buildfrom inside theAIWorkflowAppfolder at any time to verify your changes compile.
The Scenario
You are a frontend developer at ContentAI Solutions building a document assistant. The feature has to send user prompts to the AI gateway, render Server-Sent Events as they arrive, enrich prompts with profile context drawn from the authenticated session, and stay resilient when the gateway returns a malformed structured response. Your job is to wire the prompt-and-response workflow, manage the AI interaction state through a
BehaviorSubject, route gateway access through anHttpInterceptorthat attaches the session token only to allowlisted origins, and validate structured AI output against a runtime type guard before it reaches the UI.The Application Structure
Run the build at any point with:Key files in the lab environment
- `AIWorkflowApp/src/app/services/auth.service.ts` — an authentication service that exposes the session token and the current user's profile preferences - `AIWorkflowApp/src/app/services/ai.service.ts` — the AI workflow service that needs to build requests, drive the streaming lifecycle, validate structured responses, and expose state through a `BehaviorSubject` - `AIWorkflowApp/src/app/interceptors/auth.interceptor.ts` — an HTTP interceptor that needs to attach the session token to allowlisted API calls - `AIWorkflowApp/src/app/components/prompt-panel/` — the input panel that submits prompts and toggles profile-derived context - `AIWorkflowApp/src/app/components/response-display/` — the response panel that subscribes to AI state and renders each lifecycle phase - `AIWorkflowApp/src/app/components/assistant-page/` — the page that composes the prompt panel and the response display - `api-server/server.js` — the AI gateway that holds the provider credentials, validates the session token, and streams Server-Sent Events back to the client (already running, no changes needed) Complete the tasks in order. Each task builds on the previous one.cd AIWorkflowApp && ng buildThe first time you run
ng build, the CLI asks whether you want to enable autocompletion. TypeNand press Enter. -
Challenge
Wiring the Prompt-and-Response Workflow
Building the Outgoing Request
Before any UI streaming logic runs, the service has to assemble a clean request body. User input arriving from a textarea routinely carries leading and trailing whitespace, and the gateway treats whitespace-only prompts as billable but useless calls. Trimming the prompt before it enters the request payload is the cheapest place to enforce that rule, and it keeps every downstream component working with the same canonical value.
The
buildRequestmethod also decides whether profile-derived context travels with the prompt. The first task focuses only on the prompt itself — context-merging comes later, once the secure boundary that protects profile reads is in place.Connecting the Component to the Service
A prompt panel that lets the user click Send while the textarea is empty wastes both a network round-trip and the user's attention. The component class exposes a
canSubmitgetter the template binds to the button'sdisabledattribute, which keeps the submission rule in one readable place and the template free of inline expressions.The same
submitmethod that the button click triggers also delegates straight toAIService.sendPrompt— the component never owns the lifecycle, it just hands the prompt and the context flag to the service and lets the response display subscribe to the resulting state stream. -
Challenge
Managing AI Interaction State
Driving the Lifecycle Through a BehaviorSubject
Generative AI calls do not follow a binary loading-then-loaded model — they pass through a real lifecycle: a brief
loadingphase while the request is in flight, astreamingphase as Server-Sent Events arrive, and acompletephase once the gateway emits thedoneevent. Each phase needs a different UI treatment, and the cheapest way to drive that from a service is to push a discriminatedAIStateobject into aBehaviorSubjectand let every subscriber re-render off the same source of truth.The state object carries a
statusliteral, the accumulatedtext, and anerrorMessageslot used in the next task. EachsetStatecall replaces the whole object, which keeps every transition explicit at the call site.Surfacing Errors Without Losing Partial Output
A network failure or a gateway timeout has to surface as a real UI state, not a silent dropped subscription. The
.catchblock on the fetch promise is the right place to translate a thrown error into a state transition — the service pushes a state with status'error', preserves whatevertextaccumulated before the failure, and sets a human-readableerrorMessagethe response display can render.Preserving the in-flight text matters because partial output is often still useful — a half-streamed summary tells the user what the model managed to produce before the connection dropped, which is a better experience than blanking the panel.
-
Challenge
Securing the Server-Mediated Boundary
Centralizing the Session Token in an Interceptor
The AI gateway sits behind a session-token check, and every
HttpClientrequest from the application has to carry anAuthorization: Bearer <token>header. Adding that header inside services that useHttpClientwould scatter the rule across every method that calls the gateway, so the application centralizes it in anHttpInterceptorinstead — one function the framework runs on every outgoingHttpClientrequest.The interceptor enforces two boundaries before it touches the request. The first is an origin allowlist: the token only attaches to URLs that match a trusted API prefix, so an accidental call to an external host never leaks the credential. The second is a session check: when no token is present, the request goes through unchanged so anonymous calls to public endpoints still work. Both checks live in one place, which makes the rule auditable.
Folding Profile Context Into the Prompt
Now that the session token rides every trusted request automatically, the AI service can safely read profile preferences and fold them into the request body. The
includeContextflag from the prompt panel decides whether profile-derived context travels with the prompt — when the flag is on, the service readstone,language, andexpertiseLevelfrom the user profile and packs them into the request; when the flag is off, the context field isnulland the gateway treats the call as a context-free prompt.Keeping the profile read inside
AIServicerather than inside the component class means the trust boundary stays in one auditable place — the component never sees the token and never reads the profile directly. -
Challenge
Handling Non-Deterministic AI Outputs
Treating the Gateway Body as Untrusted
A streaming text completion has a forgiving shape — any string is a valid response, and the UI can simply render whatever arrives. A structured completion is the opposite: the gateway is supposed to return JSON shaped like the
StructuredAIResponseinterface, but a non-deterministic model occasionally returns a missing field, an extra field with the right name but a wrong type, or a response that parses as valid JSON but lacks the expected structure entirely.The
fetchStructuredmethod types the parsed body asunknownand routes it through a runtime type guard before treating it as the expected shape. When the guard rejects the payload, the method throws an error, and the observable's error path runs — downstream callers get a typed failure they can retry, log, or surface to the user, instead of a runtime crash deeper in the render pipeline.Building the Type Guard Itself
The type guard from the previous task is only as strict as the field checks inside it. A full guard inspects every required field — the model occasionally omits one, swaps a string for a number, or returns a single string where the interface expects an array — so each field gets its own boolean check, and the guard returns
trueonly when every check passes.Each check uses the narrowest possible test for the field's type.
typeofcovers primitive shapes likestringandnumber, andArray.isArrayis the only correct way to check for an array —typeof [] === 'object', sotypeofwould not catch the case where the model returns an object instead of an array. -
Challenge
View your Application
View the Application
Now that every task is complete, run the application and view it in the browser to see the full workflow.
-
Start the app from the
AIWorkflowAppdirectory withng serve --host 0.0.0.0 --port 4200. The dev-server already loadsproxy.conf.json, which forwards/api/*calls to the gateway running on port 3000. -
Open the Web Browser panel on the right, navigate to
localhost:4200and click the external window icon in the top-right corner of the panel to open the app in a new browser tab. Before typing anything, open DevTools (F12) and go to the Network tab.Note: Requests that arrive before DevTools is open do not appear in the panel.
-
In the application, type a prompt into the textarea (for example,
Tell me something about football) and confirm the Send button stays disabled until you have non-whitespace text. -
Click Send and watch the response display badge cycle from loading to streaming to complete as the SSE chunks arrive.
-
In the Network tab, select the
streamrequest under the Fetch/XHR filter and click the Payload tab. Confirm the payload carries your prompt and thatcontextcontainstone,language, andexpertiseLevelfrom your profile. -
Click Clear, uncheck Use my profile preferences, type the same prompt again, and click Send. Select the new
streamrequest, open the Payload tab, and confirmcontextis nownull. -
With that request still selected, click the Headers tab. Under Request Headers, confirm
authorization: Bearer demo-session-token-abc123is present. Under Response Headers, confirmcontent-type: text/event-stream.
Expected Result: Every part of the workflow you implemented is visible in the running application. The prompt panel forwards input only when there's real content, the response display reacts to each lifecycle phase, the interceptor attaches the session token to allowlisted calls, and the structured-response path rejects malformed payloads before they reach the UI.
-
-
Challenge
Conclusion
Congratulations on completing the Implement Generative AI Workflows in Angular lab! You have wired a Server-Sent Events streaming workflow, driven the AI interaction lifecycle through a
BehaviorSubject, centralized session-token handling in anHttpInterceptor, and validated structured AI output against a runtime type guard.What You Have Accomplished
- Trimmed the Outgoing Prompt — Cleaned up user input inside
buildRequestso the gateway never sees a whitespace-only prompt. - Wired the Prompt Panel to the Service — Bound the Send button to a
canSubmitgetter and forwarded the prompt toAIService.sendPromptfrom the component. - Drove the Streaming Lifecycle — Pushed
loading,streaming, andcompletestates into theBehaviorSubjectso the response display re-renders off a single source of truth. - Captured Failed Requests in the Error State — Translated thrown errors into a state transition that preserves any partial output already on screen.
- Attached the Session Token to Allowlisted Requests — Centralized the
Authorizationheader in anHttpInterceptorthat only touches trusted API origins. - Enriched Trusted Requests with Profile Context — Folded the user's tone, language, and expertise level into the request body when the caller opts in.
- Validated Structured Responses Through a Type Guard — Routed every parsed body through a runtime check before treating it as the typed AI response.
- Implemented Per-Field Shape Checks — Wrote the type guard's field-by-field checks so missing, mistyped, or wrongly-shaped payloads fail loudly instead of corrupting the UI.
- Saw the Full Workflow in Action — Ran both servers, watched the badge cycle from loading to streaming to complete in real time, and verified the request payload, Bearer token, and
text/event-streamcontent type in DevTools — confirming every piece you built is working end-to-end.
Key Takeaways
- A
BehaviorSubjectper service is the simplest way to drive a multi-phase reactive lifecycle in Angular 16 — every component subscribes once and re-renders off the same state object. - Server-Sent Events are the natural fit for token-by-token streaming from an AI gateway; reading the response with
fetchand aReadableStreamlets the UI update as each event arrives instead of waiting for the entire completion. - An
HttpInterceptorturns a per-call concern into a single, auditable rule — and limiting that rule to a trusted-origin allowlist prevents credential leaks to external hosts. - The trust boundary for profile-derived context belongs in the service, not the component — the component never needs to see the token or the profile to send an enriched prompt.
- Generative AI responses are non-deterministic by design; treating every parsed body as
unknownuntil a runtime type guard validates it is the cheapest defence against shape drift.
Experiment Before You Go
You still have time in the lab environment. Try these explorations:
- Add a fourth lifecycle status
'cancelled'and acancel()method onAIServicethat calls the storedstreamReader.cancel()and pushes a cancelled state into the subject - Extend the trusted-origin allowlist with a second prefix and confirm the interceptor attaches the token to both
- Add a retry-on-malformed-output branch around
fetchStructuredthat calls the endpoint again with a stricter system prompt when the type guard fails - Replace the
BehaviorSubjectwith an Angular Signal (Angular 17+) and observe how the same lifecycle reads in the template
- Trimmed the Outgoing Prompt — Cleaned up user input inside
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.