Skip to content

Commit 3027ee6

Browse files
Sarath1018claude
andauthored
feat(samples): add Angular data fabric sample app (#576)
* feat(samples): add Angular data fabric sample app Angular 20 counterpart of the React data-fabric-app sample. Same SDK patterns (meta-tag config, OAuth flow, cursor-looped pagination), but renders its own records grid since @uipath/ui-widgets-datatable is React-only — which lets it exercise updateRecordById and deleteRecordsById directly, alongside insert, attachments, and choice sets. Local-dev meta-tag injection is handled by an Angular indexHtmlTransformer equivalent of the coded-apps-dev Vite plugin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: address convention review comments - Guard against stale async responses when switching entities/choice sets (entity detail, choice set view, choice set detail) - Edit mode now sends explicit null for cleared fields so clearing an optional field actually persists - DATETIME prefill formats the server instant in local time (inverse of coerce) instead of slicing the ISO string, fixing a per-save UTC-offset drift on edited records Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: address review feedback on meta-tag transformer and lint error - Guard JSON.parse of uipath.json in the index HTML transformer: malformed JSON now logs an attributed, actionable error and no-ops (matching the file-absent behavior) instead of failing the build with a bare SyntaxError - Move the pad helper to module scope (oxlint consistent-function-scoping) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: address review feedback on record editor - Track removed attachments in a local removedAttachments signal instead of mutating the parent-owned record input in place — in-place mutation bypasses the signal graph and breaks under OnPush/zoneless CD - Fetch referenced choice sets in parallel with Promise.allSettled instead of sequential awaits Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: keep record Id on one line in the records grid The table could squeeze the Id column to a sliver, and break-all then rendered the GUID one character per line, blowing up row heights. nowrap + the existing horizontal scroll container fixes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: disable Angular CLI analytics in the sample An interactive ng run wrote an opted-in analytics UUID into angular.json. A committed sample should be explicitly opted out so cloners aren't silently enrolled and the CLI prompt never blocks scripted builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: add multilogin * fix: render native form controls in the active theme Native date/datetime picker icons and popups follow color-scheme, not the CSS tokens — without it the calendar icon is dark-on-dark and invisible in dark mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: address UI review findings across dialogs, grid, and feedback surfaces Critical: - Row Inspector read record values by field name only — entities whose records key by displayName rendered every field as "empty" and hid the attachment Download button. All consumers now share a recordFieldValue helper (grid, inspector, editor prefill/attachment check). - Record editor save() had no re-entrancy guard — Enter-key implicit form submission could insert the same record twice. Important: - Modals now close on Escape, move focus into the dialog on open and restore it on close, and only close on backdrop clicks that also started on the backdrop (a text-selection drag released outside the editor no longer discards the form). - Scroll no longer chains to the records pane behind an open modal. - Sorting reference/choice-set columns compared "[object Object]" — now compares the same projection the cells display. - Sort state persisted across entity switches; it now resets when the schema changes but survives record refreshes. - Long unbroken API error tokens overflowed toasts and alerts. - Truncated entity/choice-set labels now carry title tooltips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: export failures toast an error, and CSV matches the grid's formatting - exportCsv treated a failed re-fetch as an empty table ("Nothing to export") next to the grid's error alert — it now checks recordsError and toasts the failure instead - csv-export JSON-stringified reference/choice-set objects while the grid showed their displayName; formatValue now delegates to formatCell so the exported CSV matches the grid exactly (null still exports as an empty cell rather than the grid's em dash) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: add animated preview to the Angular sample README Records grid, New record modal, and create-success flow captured from a live session (1.4 MB GIF, browser chrome cropped out). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: check the raw value for attachment presence, not the formatted sentinel The template compared cellValue() to the em-dash string formatCell returns for null — a silent breakage if the null representation ever changes. A hasAttachment() helper checks the raw value directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 8790a80 commit 3027ee6

33 files changed

Lines changed: 12336 additions & 0 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build output
5+
dist/
6+
out-tsc/
7+
8+
# Angular cache
9+
.angular/
10+
11+
# Tenant-specific credentials — copy uipath.json.example instead
12+
uipath.json
13+
14+
# Misc
15+
.DS_Store
16+
npm-debug.log*
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# UiPath Data Fabric Sample App (Angular)
2+
3+
A sample Angular + TypeScript application for browsing and managing **UiPath Data Fabric** entities, records, and choice sets via the UiPath TypeScript SDK. Deploys as a UiPath Coded App.
4+
5+
This is the Angular counterpart of the React sample in [`../data-fabric-app`](../data-fabric-app). The SDK usage is identical — only the UI framework differs. Because the `@uipath/ui-widgets-datatable` records grid is React-only, this app renders its own grid and calls the SDK's record CRUD methods (`insertRecordById`, `updateRecordById`, `deleteRecordsById`) directly, so it exercises a wider slice of the `Entities` service than the React sample does.
6+
7+
## Preview
8+
9+
![Browsing entities, viewing records, and creating a record](screenshots/preview.gif)
10+
11+
## SDK Usage
12+
13+
### Importing the SDK
14+
15+
```typescript
16+
// Core SDK for authentication
17+
import { UiPath, UiPathError } from '@uipath/uipath-typescript/core'
18+
19+
// Entities + ChoiceSets services
20+
import { Entities, ChoiceSets } from '@uipath/uipath-typescript/entities'
21+
import type {
22+
EntityGetResponse,
23+
EntityRecord,
24+
ChoiceSetGetResponse,
25+
} from '@uipath/uipath-typescript/entities'
26+
```
27+
28+
### Initializing the SDK
29+
30+
```typescript
31+
// Create SDK instance — no config needed for Coded Apps;
32+
// the SDK reads from <meta name="uipath:*"> tags injected by the platform
33+
// (or by tools/uipath-meta-tags.mjs during local dev).
34+
const sdk = new UiPath()
35+
await sdk.initialize()
36+
37+
// Create service instances
38+
const entities = new Entities(sdk)
39+
const choiceSets = new ChoiceSets(sdk)
40+
41+
// Use services
42+
const allEntities = await entities.getAll()
43+
const entitySchema = await entities.getById(entityId)
44+
const records = await entities.getAllRecords(entityId, { pageSize: 100 })
45+
const allChoiceSets = await choiceSets.getAll()
46+
const choiceValues = await choiceSets.getById(choiceSetId)
47+
```
48+
49+
### SDK methods exercised by this sample
50+
51+
| Service | Method | Where it's used |
52+
| ------- | ------ | --------------- |
53+
| `Entities` | `getAll` | Sidebar entity list (`EntitiesListComponent`) |
54+
| `Entities` | `getById` | Schema panel + page header (`EntityDetailComponent`) |
55+
| `Entities` | `getAllRecords` | Records grid + CSV export (cursor-looped to fetch every page) |
56+
| `Entities` | `getRecordById` | Row Inspector modal (`RowInspectorComponent`) |
57+
| `Entities` | `insertRecordById` | "Add data" modal (`RecordEditorComponent`) |
58+
| `Entities` | `updateRecordById` | Per-row Edit action (`RecordEditorComponent` in edit mode) |
59+
| `Entities` | `deleteRecordsById` | Batch delete of selected rows (`EntityDetailComponent`) |
60+
| `Entities` | `uploadAttachment` / `downloadAttachment` / `deleteAttachment` | File-type fields in `RecordEditorComponent` + Row Inspector |
61+
| `ChoiceSets` | `getAll` | Sidebar "Choice sets" section + choice set header metadata |
62+
| `ChoiceSets` | `getById` | Choice set values table + choice-set field pickers |
63+
64+
## Installation
65+
66+
```bash
67+
npm install
68+
```
69+
70+
## Setup Instructions
71+
72+
### 1. Prerequisites
73+
74+
- [Node.js 20+](https://uipath.github.io/uipath-typescript/getting-started/#prerequisites)
75+
- UiPath Cloud tenant access
76+
- An OAuth External Application configured in UiPath Admin Center (Data Fabric scopes)
77+
78+
### 2. Configure OAuth Application
79+
80+
1. In UiPath Cloud: **Admin → External Applications**
81+
2. Click **Add Application → Non Confidential Application**
82+
3. Configure:
83+
- **Name**: e.g., "Data Fabric Sample App (Angular)"
84+
- **Redirect URI**: `http://localhost:4200` (for development)
85+
- **Scopes**: `DataFabric.Schema.Read`, `DataFabric.Data.Read`, `DataFabric.Data.Write`
86+
4. Save and copy the **Client ID**
87+
88+
### 3. Local Configuration
89+
90+
Copy the template and fill in your tenant values:
91+
92+
```bash
93+
cp uipath.json.example uipath.json
94+
```
95+
96+
Edit `uipath.json`:
97+
98+
```json
99+
{
100+
"clientId": "<your-oauth-external-app-client-id>",
101+
"scope": "DataFabric.Schema.Read DataFabric.Data.Read DataFabric.Data.Write",
102+
"orgName": "<your-org-name>",
103+
"tenantName": "<your-tenant-name>",
104+
"baseUrl": "https://api.uipath.com",
105+
"redirectUri": "http://localhost:4200"
106+
}
107+
```
108+
109+
> `uipath.json` is `.gitignore`d on purpose — it carries tenant-specific credentials. Only `uipath.json.example` is committed.
110+
111+
### 4. Run
112+
113+
```bash
114+
npm run dev
115+
```
116+
117+
Open `http://localhost:4200`.
118+
119+
### 5. Authentication Flow
120+
121+
1. Click **"Sign in with UiPath"**.
122+
2. You'll be redirected to UiPath Cloud for OAuth.
123+
3. After login you return to the app, which initializes the SDK from the `<meta name="uipath:*">` tags in `index.html`.
124+
125+
## How the meta-tag injection works
126+
127+
The React sample uses the `@uipath/coded-apps-dev` Vite plugin to inject `<meta name="uipath:*">` tags into `index.html` during local dev. Angular CLI doesn't run Vite plugins, so this app uses the equivalent Angular mechanism: an [`indexHtmlTransformer`](https://angular.dev/reference/configs/workspace-config) (see `tools/uipath-meta-tags.mjs`, wired up in `angular.json`). It reads `uipath.json` and injects the same tags at serve/build time. When `uipath.json` is absent (e.g. building for deployment without local config), it's a no-op — at deploy time the UiPath platform injects production values, so the same `new UiPath()` init code works in both places.
128+
129+
## Application Structure
130+
131+
```
132+
tools/
133+
└── uipath-meta-tags.mjs # Angular indexHtmlTransformer — injects
134+
# <meta name="uipath:*"> tags from uipath.json
135+
src/
136+
├── app/
137+
│ ├── components/
138+
│ │ ├── choice-set-detail.component.ts # Right pane when a choice set is selected
139+
│ │ ├── choice-set-view.component.ts # Values table for one choice set
140+
│ │ ├── entities-list.component.ts # Left sidebar (Entities + Choice Sets sections)
141+
│ │ ├── entity-detail.component.ts # Right pane when an entity is selected
142+
│ │ ├── header.component.ts # App header + theme toggle + Sign out
143+
│ │ ├── icons.ts # Inline SVG icon components (Lucide paths)
144+
│ │ ├── login-screen.component.ts # OAuth login screen
145+
│ │ ├── record-editor.component.ts # Modal form for create + edit (+ attachments)
146+
│ │ ├── records-table.component.ts # Records grid: sort, paginate, select, edit
147+
│ │ ├── row-inspector.component.ts # Read-only modal showing all fields incl. system
148+
│ │ └── toast-container.component.ts # Toast feedback UI
149+
│ ├── core/
150+
│ │ ├── auth.service.ts # SDK init + auth state (signals)
151+
│ │ ├── theme.service.ts # Light/dark mode persistence
152+
│ │ └── toast.service.ts # Toast queue
153+
│ ├── lib/
154+
│ │ ├── csv-export.ts # Records → CSV download
155+
│ │ ├── download.ts # Blob → file helper
156+
│ │ ├── entity-types.ts # Entity-type classification (VDO / ChoiceSet / read-only)
157+
│ │ └── format.ts # Cell value formatting
158+
│ └── app.component.ts # Shell: routes between EntityDetail / ChoiceSetDetail
159+
├── index.html
160+
├── main.ts # Entry point
161+
└── styles.css # Design tokens (light + dark) + shared primitives
162+
```
163+
164+
## Key Features
165+
166+
### Catalog sidebar
167+
- Two collapsible sections — **Entities** and **Choice Sets** — sourced from `Entities.getAll()` and `ChoiceSets.getAll()` respectively.
168+
- Search filters both sections at once; entity-type badges (VDO / SystemEntity) help distinguish non-standard entities.
169+
- Single refresh button reloads both lists in parallel.
170+
171+
### Entity detail
172+
- Schema panel (collapsible) listing every field with type, required marker, and system flag.
173+
- Records grid with client-side sorting, pagination, and multi-row selection. The full record set is fetched by looping `Entities.getAllRecords` cursors.
174+
- "Add data" opens a modal form (`RecordEditorComponent`) with required-field validation, choice-set pickers, and attachment upload.
175+
- Per-row **Edit** opens the same modal in edit mode, saving via `Entities.updateRecordById`. Existing attachments can be replaced or removed (`deleteAttachment`).
176+
- Selecting rows reveals a **Delete (N)** button backed by `Entities.deleteRecordsById`, with partial-failure reporting.
177+
- Click the `Id` cell to open a read-only **Row Inspector** that loads the full record via `Entities.getRecordById` (including system fields).
178+
- "Export CSV" downloads the freshly-fetched records (RFC 4180 escaping, UTF-8 BOM).
179+
- VDOs and InternalEntity are detected up-front and rendered with a friendly notice instead of a failing API call. SystemEntity renders in read-only mode.
180+
181+
### Choice set detail
182+
- Header pulls metadata (description, last updated) from `ChoiceSets.getAll()`.
183+
- Values table rendered from `ChoiceSets.getById()`, cursor-looped for the full set. Read-only (the SDK doesn't expose mutation methods for choice-set values in this app's scope).
184+
185+
### Theme & polish
186+
- Semantic design tokens for light + dark mode (persisted, defaults to the OS preference).
187+
- Inter (sans) + IBM Plex Mono (mono).
188+
- Toast feedback, skeleton loaders, tooltips on truncated labels.
189+
190+
## Technologies Used
191+
192+
- **Angular 20** (standalone components, signals, built-in control flow) + **TypeScript**
193+
- **@uipath/uipath-typescript** — the SDK under test
194+
- **OAuth 2.0** (handled by the SDK) for authentication
195+
196+
## Building for Production
197+
198+
```bash
199+
npm run build
200+
```
201+
202+
Built bundle lives in `dist/`. From there:
203+
204+
```bash
205+
# One-time: install the codedapp tool plugin for the uip CLI.
206+
uip tools install codedapp
207+
208+
# Sign in with the uip CLI (interactive — pick the org + tenant to deploy to).
209+
uip login --it
210+
211+
uip codedapp pack ./dist --name data-fabric-app-angular --version 1.0.0
212+
uip codedapp publish
213+
uip codedapp deploy
214+
```
215+
216+
See the [Coded Apps CLI reference](https://uipath.github.io/uipath-typescript/coded-apps/cli-reference/) for full options.
217+
218+
## Troubleshooting
219+
220+
### Common issues
221+
222+
1. **Authentication fails / "Invalid redirect URI"** — verify the redirect URI on the External App matches `http://localhost:4200` for dev, and your deployed URL for production.
223+
224+
2. **Login screen shows but sign-in does nothing** — the meta tags probably weren't injected. Confirm `uipath.json` exists at the project root and restart `npm run dev` (the transformer runs at serve time). View the page source and check for `<meta name="uipath:client-id" …>`.
225+
226+
3. **Records grid errors immediately** — usually means the SDK couldn't initialize or the OAuth app is missing a Data Fabric scope. Check the browser console for `UiPathError` messages and verify `uipath.json` has the right `orgName` / `tenantName`.
227+
228+
### Getting help
229+
230+
- [UiPath TypeScript SDK documentation](https://uipath.github.io/uipath-typescript/)
231+
- [UiPath Data Fabric documentation](https://docs.uipath.com/data-service/automation-cloud/latest)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3+
"version": 1,
4+
"newProjectRoot": "projects",
5+
"projects": {
6+
"data-fabric-app-angular": {
7+
"projectType": "application",
8+
"schematics": {},
9+
"root": "",
10+
"sourceRoot": "src",
11+
"prefix": "app",
12+
"architect": {
13+
"build": {
14+
"builder": "@angular-builders/custom-esbuild:application",
15+
"options": {
16+
"outputPath": {
17+
"base": "dist",
18+
"browser": ""
19+
},
20+
"index": "src/index.html",
21+
"browser": "src/main.ts",
22+
"polyfills": ["zone.js"],
23+
"tsConfig": "tsconfig.app.json",
24+
"assets": [
25+
{
26+
"glob": "**/*",
27+
"input": "public"
28+
}
29+
],
30+
"styles": ["src/styles.css"],
31+
"indexHtmlTransformer": "tools/uipath-meta-tags.mjs",
32+
"allowedCommonJsDependencies": [
33+
"@opentelemetry/api",
34+
"@opentelemetry/semantic-conventions"
35+
]
36+
},
37+
"configurations": {
38+
"production": {
39+
"budgets": [
40+
{
41+
"type": "initial",
42+
"maximumWarning": "1MB",
43+
"maximumError": "2MB"
44+
},
45+
{
46+
"type": "anyComponentStyle",
47+
"maximumWarning": "8kB",
48+
"maximumError": "16kB"
49+
}
50+
],
51+
"outputHashing": "all"
52+
},
53+
"development": {
54+
"optimization": false,
55+
"extractLicenses": false,
56+
"sourceMap": true
57+
}
58+
},
59+
"defaultConfiguration": "production"
60+
},
61+
"serve": {
62+
"builder": "@angular-builders/custom-esbuild:dev-server",
63+
"options": {
64+
"port": 4200
65+
},
66+
"configurations": {
67+
"production": {
68+
"buildTarget": "data-fabric-app-angular:build:production"
69+
},
70+
"development": {
71+
"buildTarget": "data-fabric-app-angular:build:development"
72+
}
73+
},
74+
"defaultConfiguration": "development"
75+
}
76+
}
77+
}
78+
},
79+
"cli": {
80+
"analytics": false
81+
}
82+
}

0 commit comments

Comments
 (0)