angular-odata 0.161.0 → 0.162.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,18 @@
1
1
  <h1 align="center">Angular OData</h1>
2
2
 
3
3
  <p align="center">
4
- <em>A fluent API for querying, creating, updating and deleting OData resources in Angular.</em>
4
+ <em>A typed, fluent client for OData services in Angular.</em>
5
5
  <br>
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <a href="CONTRIBUTING.md">Contributing</a>
9
+ <a href="https://diegomvh.github.io/angular-odata/docs/api/additional-documentation/introduction.html">Handbook</a>
10
10
  ·
11
- <a href="./docs/api/index.html">Documentation</a>
11
+ <a href="https://diegomvh.github.io/angular-odata/docs/api/index.html">API reference</a>
12
12
  ·
13
13
  <a href="https://github.com/diegomvh/AngularODataEntity">Demo</a>
14
+ ·
15
+ <a href="https://github.com/diegomvh/angular-odata/blob/main/CONTRIBUTING.md">Contributing</a>
14
16
  <br>
15
17
  <br>
16
18
  </p>
@@ -26,14 +28,170 @@
26
28
 
27
29
  <hr>
28
30
 
29
- ## Installation
31
+ [OData](https://www.odata.org/) is a standard for REST APIs: every service describes its
32
+ data in a `$metadata` document and accepts the same query options (`$filter`, `$select`,
33
+ `$expand`, `$orderby`, …). **angular-odata** lets your Angular application talk to any OData
34
+ service without writing URLs by hand: you build requests with typed TypeScript code and get
35
+ back typed results.
36
+
37
+ ```typescript
38
+ // GET People?$select=UserName,FirstName&$filter=contains(UserName, 'russell')&$top=10
39
+ client
40
+ .entitySet<Person>('People')
41
+ .query((q) => {
42
+ q.filter(({ e, t }) => e().contains(t.UserName, 'russell'));
43
+ q.select(['UserName', 'FirstName']);
44
+ q.top(10);
45
+ })
46
+ .fetch()
47
+ .subscribe(({ entities }) => console.log(entities));
48
+ ```
49
+
50
+ ## Features
51
+
52
+ - **Typed query builder**: `$filter`, `$select`, `$expand`, `$orderby`, `$search`,
53
+ `$compute`, `$apply` and paging, with field names checked by the compiler.
54
+ - **Complete OData API**: entity sets, entities, navigation properties, functions,
55
+ actions, singletons, media and `$batch` requests.
56
+ - **Code generation**: one command creates interfaces, services and models from your
57
+ service's `$metadata`.
58
+ - **Models and collections**: change tracking, validation and `save()` / `destroy()`.
59
+ - **Caching**: in-memory, Web Storage or IndexedDB, with fetch policies such as
60
+ `cache-first` and `cache-and-network`.
61
+ - **Several APIs** in the same application, each with its own configuration.
30
62
 
31
- Install from npm:
63
+ ## Quick start
64
+
65
+ ### 1. Install
32
66
 
33
67
  ```bash
34
68
  npm i angular-odata
35
69
  ```
36
70
 
37
- ## OData Version
71
+ ### 2. Register the client
72
+
73
+ ```typescript
74
+ // app.config.ts
75
+ import { ApplicationConfig } from '@angular/core';
76
+ import { provideODataClient } from 'angular-odata';
77
+
78
+ export const appConfig: ApplicationConfig = {
79
+ providers: [
80
+ provideODataClient({
81
+ config: { serviceRootUrl: 'https://services.odata.org/V4/TripPinServiceRW/' },
82
+ }),
83
+ ],
84
+ };
85
+ ```
86
+
87
+ Applications that use NgModules can import `ODataModule.forRoot({ config })` instead.
88
+
89
+ ### 3. Query your service
90
+
91
+ ```typescript
92
+ import { Component, inject, signal } from '@angular/core';
93
+ import { ODataClient } from 'angular-odata';
94
+
95
+ interface Person {
96
+ UserName: string;
97
+ FirstName: string;
98
+ LastName: string;
99
+ }
100
+
101
+ @Component({
102
+ selector: 'app-people',
103
+ template: `
104
+ @for (person of people(); track person.UserName) {
105
+ <p>{{ person.FirstName }} {{ person.LastName }}</p>
106
+ }
107
+ `,
108
+ })
109
+ export class PeopleComponent {
110
+ private client = inject(ODataClient);
111
+ people = signal<Person[]>([]);
112
+
113
+ ngOnInit() {
114
+ this.client
115
+ .entitySet<Person>('People')
116
+ .query((q) => {
117
+ q.orderBy('LastName');
118
+ q.top(10);
119
+ })
120
+ .fetch()
121
+ .subscribe(({ entities }) => this.people.set(entities ?? []));
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### 4. Optional: generate a typed API
127
+
128
+ Instead of writing interfaces and services by hand, generate them from the `$metadata` of
129
+ your service:
130
+
131
+ ```bash
132
+ ng generate angular-odata:apigen --name=TripPin --metadata='https://services.odata.org/V4/TripPinServiceRW/$metadata'
133
+ ```
134
+
135
+ ```typescript
136
+ import { PeopleService, TripPinConfig } from './trip-pin';
137
+
138
+ // app.config.ts
139
+ provideODataClient({ config: TripPinConfig });
140
+
141
+ // Any component or service
142
+ const people = inject(PeopleService);
143
+ people
144
+ .entity('russellwhyte')
145
+ .fetch()
146
+ .subscribe(({ entity }) => console.log(entity));
147
+ ```
148
+
149
+ ## A few more examples
150
+
151
+ ```typescript
152
+ const people = client.entitySet<Person>('People');
153
+
154
+ // Create, update and delete
155
+ people.create({ UserName: 'jdoe', FirstName: 'John', LastName: 'Doe' }).subscribe();
156
+ people.entity('jdoe').modify({ FirstName: 'Johnny' }).subscribe();
157
+ people.entity('jdoe').destroy().subscribe();
158
+
159
+ // Expand related entities: $expand=Trips($select=Name,Budget)
160
+ people
161
+ .entity('russellwhyte')
162
+ .query((q) =>
163
+ q.expand(({ e, t }) =>
164
+ e().field(t.Trips, (f) => f.select(({ e, t }) => e().field(t.Name).field(t.Budget))),
165
+ ),
166
+ )
167
+ .fetch()
168
+ .subscribe(({ entity }) => console.log(entity?.Trips));
169
+
170
+ // Follow the server's next links and get every entity
171
+ people.fetchAll().subscribe(({ entities }) => console.log(entities.length));
172
+ ```
173
+
174
+ ## Documentation
175
+
176
+ - **[Handbook](https://diegomvh.github.io/angular-odata/docs/api/additional-documentation/introduction.html)**:
177
+ guides for configuration, resources, the query builder, services, models, caching and code
178
+ generation.
179
+ - **[API reference](https://diegomvh.github.io/angular-odata/docs/api/index.html)**: every
180
+ class and method, generated from the source code.
181
+ - **[Demo application](https://github.com/diegomvh/AngularODataEntity)**: a complete Angular
182
+ application that uses the TripPin and Northwind sample services.
183
+
184
+ ## Compatibility
185
+
186
+ - **Angular**: 20 or later.
187
+ - **OData**: version 4 is fully supported; versions 3 and 2 have basic support.
188
+
189
+ ## Contributing
190
+
191
+ Bug reports and pull requests are welcome. Read the
192
+ [contributing guide](https://github.com/diegomvh/angular-odata/blob/main/CONTRIBUTING.md)
193
+ to get started.
194
+
195
+ ## License
38
196
 
39
- The library works mainly with OData Version 4, however, it incorporates basic support for versions 3 and 2.
197
+ [MIT](https://github.com/diegomvh/angular-odata/blob/main/LICENSE)
@@ -1593,7 +1593,7 @@ const Objects = {
1593
1593
  },
1594
1594
  resolveKey(key, { single = true } = {}) {
1595
1595
  const type = Types.rawType(key);
1596
- if (['number', 'string'].indexOf(type) !== -1)
1596
+ if (['Number', 'String'].indexOf(type) !== -1)
1597
1597
  return key;
1598
1598
  if (type !== 'Map' && type !== 'Object') {
1599
1599
  return undefined;
@@ -3214,7 +3214,7 @@ class ODataSchema extends ODataAnnotatable {
3214
3214
  this.containers = (config.containers ?? []).map((config) => new ODataEntityContainer(config, this));
3215
3215
  }
3216
3216
  isNamespaceOf(type) {
3217
- return type.startsWith(this.namespace) ?? (this.alias && type.startsWith(this.alias));
3217
+ return type.startsWith(this.namespace) || (this.alias != null && type.startsWith(this.alias));
3218
3218
  }
3219
3219
  get entitySets() {
3220
3220
  return this.containers.reduce((acc, container) => [...acc, ...container.entitySets], []);
@@ -5998,8 +5998,7 @@ class ODataRequest {
5998
5998
  if (apiOptions.prefer?.maxPageSize !== undefined && ['GET'].indexOf(this._method) !== -1)
5999
5999
  prefer.push(`odata.maxpagesize=${apiOptions.prefer?.maxPageSize}`);
6000
6000
  // Annotations
6001
- if (apiOptions.prefer?.includeAnnotations !== undefined &&
6002
- ['GET'].indexOf(this._method) !== -1)
6001
+ if (apiOptions.prefer?.includeAnnotations !== undefined && ['GET'].indexOf(this._method) !== -1)
6003
6002
  prefer.push(`odata.include-annotations=${apiOptions.prefer?.includeAnnotations}`);
6004
6003
  // Omit Null Values
6005
6004
  if (apiOptions.prefer?.omitNullValues === true && ['GET'].indexOf(this._method) !== -1)
@@ -11348,7 +11347,7 @@ class ODataModel {
11348
11347
  resource instanceof ODataNavigationPropertyResource))
11349
11348
  return throwError(() => new Error('save: Resource type ODataEntityResource/ODataNavigationPropertyResource needed'));
11350
11349
  // Resolve method and resource key
11351
- if (method === undefined && this.schema().isCompoundKey())
11350
+ if (method === undefined && this.isCompoundKey())
11352
11351
  return throwError(() => new Error('save: Composite key require a specific method, use create/update/modify'));
11353
11352
  method = method || (!resource.hasKey() ? 'create' : 'update');
11354
11353
  if (resource instanceof ODataEntityResource &&
@@ -11425,6 +11424,9 @@ class ODataModel {
11425
11424
  isNew() {
11426
11425
  return !this._meta.hasKey(this);
11427
11426
  }
11427
+ isCompoundKey() {
11428
+ return this.schema().isCompoundKey();
11429
+ }
11428
11430
  withResource(resource, ctx) {
11429
11431
  return this._meta.withResource(this, resource, ctx);
11430
11432
  }
@@ -13019,7 +13021,7 @@ class ODataApi {
13019
13021
  });
13020
13022
  return true;
13021
13023
  }
13022
- populate({ schemas, references, models, collections }) {
13024
+ populate({ schemas, references, models, collections, }) {
13023
13025
  const odataSchemas = (schemas ?? []).map((schema) => new ODataSchema(schema, this));
13024
13026
  //TODO: Merge duplicates
13025
13027
  this.schemas = [...this.schemas, ...odataSchemas];