A typed, fluent client for OData services in Angular.
Handbook
·
API reference
·
Demo
·
Contributing
OData is a standard for REST APIs: every service describes its
data in a $metadata document and accepts the same query options ($filter, $select,
$expand, $orderby, …). angular-odata lets your Angular application talk to any OData
service without writing URLs by hand: you build requests with typed TypeScript code and get
back typed results.
// GET People?$select=UserName,FirstName&$filter=contains(UserName, 'russell')&$top=10
client
.entitySet<Person>('People')
.query((q) => {
q.filter(({ e, t }) => e().contains(t.UserName, 'russell'));
q.select(['UserName', 'FirstName']);
q.top(10);
})
.fetch()
.subscribe(({ entities }) => console.log(entities));- Typed query builder:
$filter,$select,$expand,$orderby,$search,$compute,$applyand paging, with field names checked by the compiler. - Complete OData API: entity sets, entities, navigation properties, functions,
actions, singletons, media and
$batchrequests. - Code generation: one command creates interfaces, services and models from your
service's
$metadata. - Models and collections: change tracking, validation and
save()/destroy(). - Caching: in-memory, Web Storage or IndexedDB, with fetch policies such as
cache-firstandcache-and-network. - Several APIs in the same application, each with its own configuration.
npm i angular-odata// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideODataClient } from 'angular-odata';
export const appConfig: ApplicationConfig = {
providers: [
provideODataClient({
config: { serviceRootUrl: 'https://services.odata.org/V4/TripPinServiceRW/' },
}),
],
};Applications that use NgModules can import ODataModule.forRoot({ config }) instead.
import { Component, inject, signal } from '@angular/core';
import { ODataClient } from 'angular-odata';
interface Person {
UserName: string;
FirstName: string;
LastName: string;
}
@Component({
selector: 'app-people',
template: `
@for (person of people(); track person.UserName) {
<p>{{ person.FirstName }} {{ person.LastName }}</p>
}
`,
})
export class PeopleComponent {
private client = inject(ODataClient);
people = signal<Person[]>([]);
ngOnInit() {
this.client
.entitySet<Person>('People')
.query((q) => {
q.orderBy('LastName');
q.top(10);
})
.fetch()
.subscribe(({ entities }) => this.people.set(entities ?? []));
}
}Instead of writing interfaces and services by hand, generate them from the $metadata of
your service:
ng generate angular-odata:apigen --name=TripPin --metadata='https://services.odata.org/V4/TripPinServiceRW/$metadata'import { PeopleService, TripPinConfig } from './trip-pin';
// app.config.ts
provideODataClient({ config: TripPinConfig });
// Any component or service
const people = inject(PeopleService);
people
.entity('russellwhyte')
.fetch()
.subscribe(({ entity }) => console.log(entity));const people = client.entitySet<Person>('People');
// Create, update and delete
people.create({ UserName: 'jdoe', FirstName: 'John', LastName: 'Doe' }).subscribe();
people.entity('jdoe').modify({ FirstName: 'Johnny' }).subscribe();
people.entity('jdoe').destroy().subscribe();
// Expand related entities: $expand=Trips($select=Name,Budget)
people
.entity('russellwhyte')
.query((q) =>
q.expand(({ e, t }) =>
e().field(t.Trips, (f) => f.select(({ e, t }) => e().field(t.Name).field(t.Budget))),
),
)
.fetch()
.subscribe(({ entity }) => console.log(entity?.Trips));
// Follow the server's next links and get every entity
people.fetchAll().subscribe(({ entities }) => console.log(entities.length));- Handbook: guides for configuration, resources, the query builder, services, models, caching and code generation.
- API reference: every class and method, generated from the source code.
- Demo application: a complete Angular application that uses the TripPin and Northwind sample services.
- Angular: 20 or later.
- OData: version 4 is fully supported; versions 3 and 2 have basic support.
Bug reports and pull requests are welcome. Read the contributing guide to get started.