A sample application showing how to use angular-odata, a library for consuming OData services from Angular in a typed, declarative and reactive way.
The demo connects to two public OData reference services:
- TripPin (
TripPinServiceRW): people, airlines, airports, trips and photos. This service supports both reads and writes. - Northwind (V4): categories, products, orders and employees.
| Feature | Where to look |
|---|---|
Configuring multiple OData APIs with provideODataClient |
src/app/app.config.ts |
Code generated from $metadata (entities, models, collections and services) |
src/app/trip-pin/, src/app/northwind/ |
Tables with server-side paging, sorting and filtering ($top, $skip, $orderby, $filter, $count) |
src/app/components/ |
Columns built dynamically from the schema (structuredType().fields()) |
src/app/components/ |
Nested $expand, levels and media entity downloads (photos) |
src/app/components/trippin/person.component.ts |
Queries, filters with any lambdas, $select and $expand |
src/app/examples/trippin/queries.ts |
Models and collections: fetch, save and navigating between entities |
src/app/examples/trippin/models.ts |
In-memory and IndexedDB caching, with the different fetchPolicy options |
src/app/examples/trippin/cache.ts |
Aggregations ($apply / groupBy) |
src/app/examples/trippin/aggregations.ts |
Computed properties ($compute) |
src/app/examples/northwind/compute.ts |
Bound and unbound functions (GetNearestAirport, GetFavoriteAirline, ...) |
src/app/examples/trippin/functions.ts |
Bound and unbound actions (ShareTrip, ResetDataSource) |
src/app/examples/trippin/actions.ts |
Grouping reads and writes in a single $batch request |
src/app/examples/trippin/batch.ts |
Singletons (Me): fetch, navigate and modify |
src/app/examples/trippin/singleton.ts |
Navigation properties, cast to derived types, $count and $ref links |
src/app/examples/trippin/navigation.ts |
Model and collection events, change tracking, reset and validation |
src/app/examples/trippin/events.ts |
Typed filters, sorting, $select, $expand with nested options, any/all lambdas and paging |
src/app/examples/northwind/queries.ts |
$apply with filter, groupby and aggregate |
src/app/examples/northwind/aggregations.ts |
Serializing and restoring resources (toJson / fromJson) |
src/app/examples/trippin/json.ts |
| CRUD with ETags, references, properties, EDM parsers, dynamic metadata | src/app/app.ts |
- Angular 22 (standalone components, signals,
inject(),@if/@forcontrol flow) - angular-odata
- PrimeNG 22 (tables, tabs, dialogs) and Tailwind CSS 4
Requirements: Node.js and npm.
npm install
npm startThen open http://localhost:4200/. The app has two tabs, TripPin and Northwind, with tables that run live OData queries against the public services.
npm startrunsng servewithproxy.conf.json. The proxy forwards/trippinto the TripPin service to avoid CORS issues. TripPin RW uses a session URL ((S(...))); if the session expires, get a new one by visitinghttps://services.odata.org/V4/TripPinServiceRW/and update thetargetinproxy.conf.json.
src/app/
├── app.config.ts # OData API registration (TripPin, Northwind, dynamic APIs)
├── app.ts / app.html # Tabbed shell + client usage examples
├── components/
│ ├── trippin/ # Airlines, Airports, People, Person (detail)
│ └── northwind/ # Categories, Products, Orders, Employees
├── examples/ # Self-contained examples of the angular-odata API
├── trip-pin/ # Generated code for TripPin
└── northwind/ # Generated code for Northwind
The library ships with a schematic (apigen) that reads a service's $metadata document and generates interfaces, models, collections, services and the API configuration:
npm run apigen:trippin
npm run apigen:northwindprovideODataClient({
config: [
Object.assign(NorthwindConfig, {
cache: new ODataInMemoryCache({ maxAge: 60 }),
}),
Object.assign(TripPinConfig, {
serviceRootUrl: 'http://localhost:4200/trippin/',
cache: new ODataIndexedDBCache({ maxAge: 60, name: 'TripPinCache', version: 1 }),
default: true,
}),
],
});You can also register an API without generated code and populate it at runtime from its metadata (populateFromMetadata: true). This is how TripPinDynamic and MicrosoftGraph are configured.
const peopleService = inject(PeopleService);
peopleService
.entities()
.query((q) => {
q.filter(({ e, t }) => e().contains(t.UserName, 'russell'));
q.expand(({ e, t }) => e().field(t.Friends));
q.orderBy([['LastName', 'asc']]);
q.top(10);
})
.fetch({ withCount: true })
.subscribe(({ entities, annots }) => console.log(entities, annots.count));const scott = peopleService.model<PersonModel<Person>>({ UserName: 'scottketchum' });
await firstValueFrom(scott.fetch());
scott.Gender = PersonGender.Female;
await firstValueFrom(scott.save());The examples in src/app/examples/ and the App methods print their results to the browser console. The root component instance is exposed as window.APP, so you can run them from DevTools:
APP.queries(); // runs the active example inside queries()
APP.personCRUD(); // creates, reads, updates and deletes a person in TripPin
APP.encode(); // EDM and enum parsersTo choose which example runs, comment or uncomment the corresponding lines in App.queries() (src/app/app.ts).
- angular-odata: https://github.com/diegomvh/angular-odata
- npm package: https://www.npmjs.com/package/angular-odata
- OData specification: https://www.odata.org/documentation/