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 +165 -7
- package/fesm2022/angular-odata.mjs +8 -6
- package/fesm2022/angular-odata.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/apigen/angular/base.js +7 -7
- package/schematics/apigen/angular/base.js.map +1 -1
- package/schematics/apigen/angular/entity.d.ts +2 -1
- package/schematics/apigen/angular/entity.js +6 -2
- package/schematics/apigen/angular/entity.js.map +1 -1
- package/schematics/apigen/angular/model.d.ts +2 -1
- package/schematics/apigen/angular/model.js +7 -6
- package/schematics/apigen/angular/model.js.map +1 -1
- package/schematics/apigen/angular/package.js.map +1 -1
- package/schematics/apigen/files/entity/__fileName__.ts +1 -1
- package/schematics/apigen/files/model/__fileName__.ts +2 -2
- package/schematics/metadata/index.js +4 -2
- package/schematics/metadata/index.js.map +1 -1
- package/schematics/metadata/parser/csdl/csdl-structural-property.d.ts +1 -0
- package/schematics/metadata/parser/csdl/csdl-structural-property.js +5 -0
- package/schematics/metadata/parser/csdl/csdl-structural-property.js.map +1 -1
- package/types/angular-odata.d.ts +1 -0
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
|
|
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="
|
|
9
|
+
<a href="https://diegomvh.github.io/angular-odata/docs/api/additional-documentation/introduction.html">Handbook</a>
|
|
10
10
|
·
|
|
11
|
-
<a href="
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
### 1. Install
|
|
32
66
|
|
|
33
67
|
```bash
|
|
34
68
|
npm i angular-odata
|
|
35
69
|
```
|
|
36
70
|
|
|
37
|
-
|
|
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
|
-
|
|
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 (['
|
|
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)
|
|
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.
|
|
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];
|