Code Monkey home page Code Monkey logo

angular-odata's Introduction

Angular OData

build npm version

A fluent API for querying, creating, updating and deleting OData resources in Angular. OData service for Angular.

Please check also my other related project, OData Angular Generator

Demo:

Full examples of the library:

Table of contents

Installation

Install from npm:

npm i angular-odata

Without Schema

Import ODataModule into your application module definition and setup the module for the serviceRootUrl.

import { NgModule } from '@angular/core';
import { ODataModule } from 'angular-odata';

@NgModule({
  imports: [
    ...
    ODataModule.forRoot({
      config: {
        serviceRootUrl: 'https://services.odata.org/V4/(S(4m0tuxtnhcfctl4gzem3gr10))/TripPinServiceRW/'
      }
    })
    ...
  ]
})
export class AppModule {}

With Schema

Use OData Angular Generator for generate the <Api>Config and the <Api>Module definition.

Import ODataModule, <Api>Config and <Api>Module into your application module. Setup ODataModule with <Api>Config and import it along with <Api>Module.

import { NgModule } from '@angular/core';

import { ODataModule } from 'angular-odata';
import { TripPinConfig, TripPinModule } from './trippin';

@NgModule({
  imports: [
    ...
    ODataModule.forRoot({ config: TripPinConfig }),
    TripPinModule
  ]
  ...
})
export class AppModule {}

Usage

Inject and use the ODataServiceFactory

import { Component } from "@angular/core";
import { ODataClient, ODATA_ETAG } from "angular-odata";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent {
  title = "TripPin";
  constructor(private factory: ODataServiceFactory) {
    this.queries();
  }

  queries() {
    // Use OData Service Factory
    let airportsService = this.factory.entitySet<Airport>(
      "Airports",
      "Microsoft.OData.SampleService.Models.TripPin.Airport"
    );
    let airports = airportsService.entities();

    // Fetch airports
    airports.fetch().subscribe(({ entities }) => {
      console.log("Airports: ", entities);
    });

    // Fetch airports with count
    airports
      .fetch({ withCount: true })
      .subscribe(({ entities, annots }) =>
        console.log("Airports: ", entities, "Annotations: ", annots)
      );

    // Fetch all airports
    airports
      .fetchAll()
      .subscribe((airports) => console.log("All Airports: ", airports));

    // Fetch airport with key and fetch again from cache
    airports
      .entity("CYYZ")
      .fetch()
      .pipe(
        switchMap(() =>
          // From Cache!
          airports.entity("CYYZ").fetch({ fetchPolicy: "cache-first" })
        )
      )
      .subscribe(({ entity, annots }) =>
        console.log("Airport: ", entity, "Annotations: ", annots)
      );

    // Clone airports resource and filter new resource
    airports
      .clone()
      .query((q) =>
        q.filter({ Location: { City: { CountryRegion: "United States" } } })
      )
      .fetch()
      .subscribe(({ entities, annots }) =>
        console.log(
          "Airports of United States: ",
          entities,
          "Annotations: ",
          annots
        )
      );

    // Change query definition of airports resource and fetch again
    airports.query((q) =>
      q.filter().push({ Location: { City: { Region: "California" } } })
    );
    airports
      .fetch()
      .subscribe(({ entities, annots }) =>
        console.log(
          "Airports in California: ",
          entities,
          "Annotations: ",
          annots
        )
      );

    // Store airports resource
    var json = airports.toJson();
    // Load airports resource
    airports = this.odata.fromJson(json) as ODataEntitySetResource<Airport>;

    // Change query definition of airports resource and fetch again
    airports.query((q) => q.filter().clear());
    airports
      .fetch()
      .subscribe(({ entities, annots }) =>
        console.log("Airports: ", entities, "Annotations: ", annots)
      );

    let peopleService = this.factory.entitySet<Person>(
      "People",
      "Microsoft.OData.SampleService.Models.TripPin.Person"
    );
    let people = peopleService.entities();

    // Clone people resource and expand and fetch
    people
      .clone()
      .query((q) =>
        q.expand({
          Friends: {
            expand: { Friends: { select: ["AddressInfo"] } },
          },
          Trips: { select: ["Name", "Tags"] },
        })
      )
      .fetch({ withCount: true })
      .subscribe(({ entities, annots }) =>
        console.log(
          "People with Friends and Trips: ",
          entities,
          "Annotations: ",
          annots
        )
      );

    // Clone people resource and filter with expressions
    people
      .clone()
      .query((q) =>
        q.filter(({ e }) =>
          e().eq("Emails", "[email protected]").or(e().eq("UserName", "john"))
        )
      )
      .fetch()
      .subscribe(({ entities, annots }) =>
        console.log(
          "People with Friends and Trips: ",
          entities,
          "Annotations: ",
          annots
        )
      );

    this.odata
      .batch("TripPin")
      .exec(() =>
        forkJoin({
          airports: airports.fetch(),
          people: people.fetch({ withCount: true }),
        })
      )
      .subscribe();
  }
}

OData Version

The library works mainly with OData Version 4, however, it incorporates basic support for versions 3 and 2.

Query Builder

For a deep query customizations the library use odata-query and odata-filter-builder as a builders.

Documentation

The api documentation is generated using compodoc and can be viewed here: https://diegomvh.github.io/angular-odata/docs/

Library documentation can be viewed on the wiki here: https://github.com/diegomvh/angular-odata/wiki

angular-odata's People

Contributors

ali-modernbeauty avatar dependabot[bot] avatar diegomvh avatar kevindstanley1988 avatar ncotasoho avatar sengokyu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

angular-odata's Issues

Compile error: Cannot find module '@angular/common/http/src/client' or HttpObserve in addBody function

Hey Diego! me again :D

File node_modules/angular-odata/lib/client.d.ts. Line 13

export declare const addBody: <T>(options: {
    etag?: string;
    headers?: HttpHeaders | {
        [header: string]: string | string[];
    };
    observe?: import("@angular/common/http/src/client").HttpObserve; // <--- error here
    params?: HttpParams | {
        [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType?: "text" | "blob" | "arraybuffer" | "json";
    withCredentials?: boolean;
}, body: T) => any;

image

Looks like HttpObserve is in node_modules/@angular/common/http/http.d.ts but isn't exported.

I believe this was a result of a recent patch. It was not there in 0.7.0 which is the version I upgraded from to 0.7.2. (thank you for the other patches by the way).

For now, I solved compiling error temporarily by editing the node_modules/angular-odata/lib/client.d.ts by copying over the type values from Angular so it looks like this:
observe?: 'body' | 'events' | 'response';

FYI and probably irrelative: Application was working fine with this error in Angular 8 before Ivy. It only loaded the second time it compiles when running ng serve, then saving a changed file. Now that I upgraded to Angular 9, I can't anymore.

So, do I need to install the Angular source code maybe? Because I see you're referencing /src/client

Could not get $batch with POST working

When I use the following code I could not get below batch operation working (please note that there are NO issues/errors when I call entities().get() in my batch operation):

this.subscription.add(
	batch
	  .post(
		() => {
		  this.subscription.add(
			this.odataProfile
			 .entity()
                         .post(this.frm.value, {})
			  .subscribe((_resp: any) => {
				console.log(_resp);
			  }),
		  );  
		},
		{ responseType: 'entities' },
	)
	.subscribe((_resp: any) => {
		console.log(_resp);
		}),
	);

I am getting the following error:

The message header '--changeset_kg5wzkmm--' is invalid. The header value must be of the format '<header name>: <header value>'.

Any idea what might be causing this?
Also, I could not figure out how to pass HttpOptions in the entity().put() method above....
Any help is gratefully appreciated!

Thank you!

Collection contains

Hi,

I'm trying to produce the following request (which works just fine).

Expected

https://localhost:1234/odata/users?$filter=Groups/any(group: group eq 'some-fancy-group-id')&$orderby=LogonName asc &$top=10&$count=true

The code I'm using is like this:

    users
      .filter(oDataFilters)
      .skip($event.first)
      .top($event.rows)
      .orderBy(`${sortBy} ${sortDirection} `)
      .get({ withCount: true, withCredentials: true })
      .subscribe(
        ({ entities, meta }) => {
          this.recordCount = meta.count;
          this.items = entities;
          this.loading = false;
        },
        (err) => console.error(err));

The contents of the oDataFilters looks like this:

{
  Groups:'some-fancy-group-id'
}

However I'm actually getting a request that looks like this:

Actual

https://localhost:1234/odata/users?$filter=Groups eq 'some-fancy-group-id'&$orderby=LogonName asc &$top=10&$count=true

Do you have any suggestions as to how I can produce the expected request above?

C(R)UD Examples

Hi Diego,
Can you provide some examples of using create, update and destroy methods?
Get and fetch options are well documented!
Thanks and BR,
Vasil.

feature request: consolidate batch request

as shown in example

batch.post(() => {
      airports.get().subscribe(console.log);
      airport.get().subscribe(console.log);
      people.get({withCount: true}).subscribe(console.log);
    }).subscribe();

batch request subscriptions and inner request subscriptions are different, therefore if one needs to wait until all inner requests complete it is a bit difficult to implement, as batch completes faster than inner requests. would be great to have one observable that completes when all inner requests finish

Updated - Clone? Filter? Select?

Updated to the latest version and I see that some of the syntax has changed. Are there any good examples of how existing queries should be migrated?

I'm mainly curious about clone() and the new query() function. Should you always call clone when you query, what would be the case when you wouldn't call this? How do you select and expand with the new query while filtering?

We currently have uses like:

this.searchResults$ = this.odAdminSupportTicketService.supportTickets.filter({ or: [{ createdOn: { ge: fromDate, lt: toDate } }] })
      .select(['id', 'status', 'customer', 'messages',])
      .expand({
        customer: { select: ['email'] },
        messages: { select: ['message', 'createdOn', 'createdById'] },
      })
      .get()
      .pipe(...rxjs operators on data...)

this.odAdminSupportTicketService.supportTickets just maps to oDataServiceFactory.entitySet<ODSecureConversation>('support-tickets').entities() in a service.

So to upgrade would I'm guessing I maybe first clone, then use the new query func.

this.odAdminSupportTicketService.supportTickets.clone().query(q => q.filter({ or: [{ createdOn: { ge: fromDate, lt: toDate } }] }))
...

But I lose it after the query though because neither select nor expand exist on the ODataEntitySetResource anymore.

Writing Binaries

Hello again 👍

We can retrieve Binaries for any given resource like this:
return this.client.entitySet<Object>(ENDPOINT).entity(Object.Id).value().fetchBlob().pipe(....)

But is there any equivalent solution for posting binaries? I expected some sort of .entity(Object.Id).value().postBlob() but can't find anything. Maybe you can help?

Thanks 💯 !

Enums not posting properly?

Hi there,

I'm using the Angular Generator project to create a client library. I initially thought that this was a problem with the generator but I'm now wondering if it's a problem with angular-odata?

We're having an issue with enums.

We have a user management system. Users can login using either windows or forms based Authentication. We use a very simple enum on our backend to represent the user logon type:

public enum UserLogonType
{
    Forms,
    Windows,
}

When we use the generated client to send the request it contains the following:

AccountType: "Models.Enums.UserLogonType'Windows'"

This causes the server to return a bad request as it can't parse the Enum.

If I modify the request and send this it works:

AccountType: "Windows"

I can also send the number and that will work too:

AccountType: 1

So I think there might be a problem with serialization?

How to conveniently create an OData request with 'or' to connect filters?

Angular-Odata can create OData requests with 'and' filter, like below:

    return this._customersOdata
      .filter(filter)
      .top(top)
      .skip(skip)
      .get({ withCount: true }).pipe(map((result) => {
        return { data: result.entities, count: result.annots.count };
      }));

it will be generated

 https://localhost/OData/Customers?$filter=contains(tolower(Name),'Jack') and contains(tolower(Name),'Mike')&$top=20&$skip=0&$count=true

Is there any possibility to generate 'or' filter? like below:

 https://localhost/OData/Customers?$filter=contains(tolower(Name),'Jack') or contains(tolower(Name),'Mike')&$top=20&$skip=0&$count=true

Enum (de) serialization

When generating code, enum properties are converted to TypeScript enum types as expected. OData however returns XML with enumeration set as the enum member string (by definition). It would be nice if angular-odata would transform the OData strings to/from the typescript enum on deserialization/serialization.

Issue with serialization of collection parameter in action body

Hello, after upgrading to 0.20.0 I've run into an issue when posting to an action. The action contains two parameters, one is a simple string and the other is an array of a complex type. When serialized to the body of the POST request, the parameter that is an array is serialized as an object instead.

For example, given the following test metadata:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:DataServices>
    <Schema Namespace="Client" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityType Name="Testing">
        <Key>
          <PropertyRef Name="ID"/>
        </Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false"/>
        <Property Name="Value" Type="Edm.String"/>
      </EntityType>
      <ComplexType Name="Complex">
        <Property Name="ComplexNumber" Type="Edm.Int32" Nullable="false"/>
        <Property Name="ComplexString" Type="Edm.String"/>
      </ComplexType>
      <Action Name="TestingAction" IsBound="true">
        <Parameter Name="bindingParameter" Type="Client.Testing"/>
        <Parameter Name="Notes" Type="Edm.String"/>
        <Parameter Name="Complexes" Type="Collection(Client.Complex)"/>
        <ReturnType Type="Client.Testing"/>
      </Action>
      <EntityContainer Name="Container">
        <EntitySet Name="Testings" EntityType="Client.Testing"/>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

If I attempt to call the "TestingAction" action via the following code:

// "this.testingODataService" is the injected ODataEntityService that was generated with ODataApiGen.
this.testingODataService
  .testingAction(1)
  .call({
    Notes: "asdf",
    Complexes: [
      {
        ComplexNumber: 1,
        ComplexString: "asdf1"
      }
    ]
  })
  .subscribe(next =>
    console.log(next)
  );

The body of the request is this:

{
  "Notes": "asdf",
  "Complexes": {
    "0": {
      "ComplexNumber": 1,
      "ComplexString": "asdf1"
    }
  }
}

but it should be:

{
  "Notes": "asdf",
  "Complexes": [
    {
      "ComplexNumber": 1,
      "ComplexString": "asdf1"
    }
  ]
}

Broken .exec method since update to 0.121?

public batchDelete<T extends IEntity>(endpoint: ENDPOINT, entities: T[]): Observable<T[]> {
    return this.client
      .batch(endpoint)
      .exec(() => {
        const linkObservables: Observable<T>[] = [];
        entities.map((entity) => {
          console.log('Create batchDelete request for entity: ', entity);
          linkObservables.push(this.client.entitySet<T>(endpoint).entity(entity.Id).destroy());
        });
        return forkJoin(linkObservables);
      })
      .pipe(
        switchMap(([entries, response]) => { // This is what we had to add after updating your library
          console.log('Batchdelete switchMap complete', entries);
          return entries;
        })
      );
  }

Hello, it looks like the .exec method is broken somehow since we have updated your npm package.
Apparantely you have updated the return type of the exec method (to entries AND the corresponding response) and now it looks like the resulting observable never completes or something? When piping the result of this method (should be an observable) it does not work since the pipe is never triggered:

this.api.batchDelete<T>(ENDPOINT.T, options.XYZ).pipe(
        tap((x) => {
          console.log('Batchdelete completed . This should work, but it does not log', x);
        }),

Can you have a look it this issue is on your side? Thanks!

OrderBy url formed incorrectly

As of 0.8 the strict typing is great. I did start to get a couple of errors around my application when I upgraded to it though, focused around the OrderBy

The old way I would do orderby with ascending or descending properties.

const service = this.showService.entities();
    service.orderBy(["DateModified desc"]);
    service.get().subscribe(result => {
        ....
    });

0.7.1 url that was working with the above code
https://localhost:44317/odata/Shows?$orderby=DateModified%20desc

The new type checking is not allowing for the 'desc' part of the string, it allows them to be split like this ['PROPERTY','ORDER'].

const service = this.showService.entities();
    service.orderBy(["DateModified", "desc"]);
    // service.filter(applyFilter);
    service.get().subscribe(result => {
        ....
    });

Problem
Splitting the property and the order seems to clear up the type checking, only problem is the url is now formed incorrectly. the ,desc is the culprit here, it is acting as any other property would in OData orderby.
https://localhost:44317/odata/Shows?$orderby=DateModified,desc

Proposals
I tried to look into modifying the OrderBy type definition to allow for concatenated definitions and couldn't come up with that typescript would allow. the idea was to get it to only allow you to enter 1 of 3 ways.

  1. The Property string by itself Ex: ["DateModified"]
  2. The Property string + " asc" Ex: ["DateModified asc"]
  3. The Property string + " desc" Ex: ["DateModified desc"]

I cannot seem to come up with a type that will allow me to do something like
[keyof T + " asc" | " desc"]

I also tried tuple concatenation, but I cannot get it to come out as a type, only a variable.

The only other solution I can think of is to keep the type definition the way it is and change the way the url is formed. Basically read for the 'asc' or 'desc' properties and created spaces instead of commas. I haven't looked into that part of the code yet though.

Workaround
For the time being you can just ignore type checking on the order by with
orderBy: ['DateModified asc'] as any

How expensive is creating an oDataServiceFactory. Can it be stored in a singelton?

Very cool library! But I'm looking over the examples here and in the entity project and they both tend to just pile everything in a single method. Are there some things that can be created once in a service and reused (like the ODataEntityService)

That is, should it be:

@Injectable({ providedIn: 'root' })
export class ContactService extends ApiService {
  private contactsODataService: ODataEntitySetService<Contact>;

  constructor(private http: HttpClient, private oDataServiceFactory: ODataServiceFactory) {
    super();
    this.contactsODataService = oDataServiceFactory.entitySet<Contact>('contacts');
  }

  public getInactiveContacts() {
    const contacts = this.contactsODataService.entities();
    return contacts
      .fetchAll();
  }

  public getContact(id: number) {
    const contacts = this.contactsODataService.entities();
    return contacts
      .entity(id)
      .expand({
        messageHistory: {
          select: ['message'] as (keyof ContactMessage)[]
        }
      }).get();
  }
}

or

@Injectable({ providedIn: 'root' })
export class ContactService extends ApiService {
  constructor(private http: HttpClient, private oDataServiceFactory: ODataServiceFactory) {
    super();
  }

  public getInactiveContacts() {
    const contactsODataService = this.oDataServiceFactory.entitySet<Contact>('contacts');
    const contacts = contactsODataService.entities();
    return contacts
      .fetchAll();
  }

  public getContact(id: number) {
    const contactsODataService = this.oDataServiceFactory.entitySet<Contact>('contacts');
    const contacts = contactsODataService.entities();
    return contacts
      .entity(id)
      .expand({
        messageHistory: {
          select: ['message'] as (keyof ContactMessage)[]
        }
      }).get();
  }
}

Are there any other things that can be safely preserved? entities() seemed like it was sometimes immutable but sometimes not from the example in your repo, so I wasn't sure if it was safe to keep that collection in the service.

Also, our serviceRootUrl for the module config depends on the deployment. All other urls in our application are actually resolved via a service. Are there any plans in the future for allowing more configuration of the serviceRootUrl. Being able to register and provide our own ODATA_CONFIGURATIONS that we can setup a factory (with deps) for would be perfect.

Please provide better documentation

Hi,
Is there a chance to get better documentation? I am having trouble to get the "get" function working which I need to chain to the "skip" or "top" function. Or is there a different method to subscribe to these functions?

Thx!

How to create such an endpoint `api/Student('dany')/Girlfriends` using `angular-odata`?

Holà Diego!

In C# it is possible to write something like this:

var tripEvent = client
    .For<Person>()
    .Key("russellwhyte")
    .NavigateTo<Trip>()
    .Key(1003)
    .NavigateTo(x => x.PlanItems)
    .As<Event>()
    .Set(CreateEventDetails())
    .InsertEntryAsync();

I would like to know whether I have the posibility to create a new Girlfriend DTO for the Student called "dany".

Which means I want to send a POST request to .../api/Student('dany')/Girlfriends by using your package client-side.

Thank you very much in advance for your response!

Kind Regards,

Kapri

Angular 14 Support?

Helloo :-)

Do you plan to upgrade to angular 14 in the near future? And is it possible to not forcing the integrating ecosystem to have a specific angular version installed? I mean, when upgrading angular there are many node packages I don't have to upgrade and they still work🤔
Problem is, always we want to upgrade we have to tell you to upgrade because your package is crying on npm install :(

thanks in advance!

How can I pass 'withCount' parameter when I choose function API ?

 return this._customersOdata
      .filter(filter)
      .top(top)
      .skip(skip)
      .get({ withCount: true }).pipe(map((result) => {
        return { data: result.entities, count: result.annots.count };
      }));

I can get the total count per search from the above code.

But when I use the function API, how could I get the total count as follow

 return this._customersOdata
      .top(top)
      .skip(skip)
      .select(selectOpts) 
      .function<{ PhoneNumber: string }, Customer>('GetCustomersByPhone')
      .callEntities({ PhoneNumber: phoneNumber })
      .pipe(map(result => result));

Batch response parsing fails when newlines are defined as '\n'

I was struggling with inner request in batch callbacks not triggering, after hours of debugging I found the line

handleResponse(response: HttpResponse<any>) {
...
const lines: string[] = response.body.split(NEWLINE);

where NEWLINE is defined as '\r\n'
but my service returns just '\n', which seems to be valid as well.
please make it either configurable or detect the newline format in handleResponse yourself

Navigate to related entities

Hey Diego! Amazing work on the libraries!

Quick question. I am wondering if it's possible to translate this to code?

http://services.odata.org/V4/TripPinService/People('russellwhyte')/Friends('scottketchum')/AddressInfo

or minimum http://services.odata.org/V4/TripPinService/People('russellwhyte')/Friends

How can I only get a navigational property?

Here is my code for reference and what I tried so far.

  constructor(private _CONST : ConstantsService, odata: ODataClient) {
    this._customerOdata = odata.entitySet<Customer>(this._CONST.ODATA_URLS.CUSTOMERS);
  }

// fast forward to another function. This is where I was trying navigationProperty but got an error
let entity = this._customerOdata.entity(customer.Id);
let smth = await entity.navigationProperty<BagItem>("BagItems").all().toPromise();

The error was this:
image

Which in the library was (I think) in entity.ts

navigationProperty<N>(name: string) {
    return ODataNavigationPropertyResource.factory<N>(
      name,
      this.client, {
      segments: this.segments.clone(),
      options: this.options.clone(),
      parser: this.parser.parserFor<N>(name) //<-- right here. this.parser is null
    });
  }

BagItems is a navigational property of Customer. I'd like to only get the BagItems. You'd think expand would do it. You're right. However, looking at this piece of code:

// expanding and then selecting. Dirty hack to only get bagitems
let expand = {BagItems: {expand: "Item($expand=Prices($filter=PriceListCode eq 'MAIN'))" }};
let select = "BagItems";

I'm actually doing three nested expanding which is not allowed in dotnet core odata webapi. (max two)

P.S. I have an endpoint of Customers({key})/BagItems which returns all BagItems. So I'm trying to shortcut to get BagItems directly and avoid too many nested expands.

Let me know if you have any questions or demos that implement the Trippin example above. Much appreciated!

"this.options.helper.context is not a function"

Unable to consume oData, with error "this.options.helper.context is not a function"

o-data-backend-service.ts

import { Injectable } from '@angular/core';
import { ODataClient, ODataServiceFactory } from "angular-odata";
import { Observable } from 'rxjs';

@Injectable({providedIn: 'root'})
export class ODataBackendService {
  constructor(private factory: ODataServiceFactory) { }
  query() {
    // Use OData Service Factory
    let dataService = this.factory.entitySet(
      "Products"
      // ,"Microsoft.OData.SampleService.Models.TripPin.Airport"
    );
    let data = dataService.entities();
    data.fetch().subscribe(({ entities }) => { 
      console.log(entities); 
    });
  }
}

Call in app-component.ts:

export class AppComponent {
  constructor(oDataBackendService : ODataBackendService) { 
    oDataBackendService.query();
  }
  // ...
}

Init in app-module.ts

const oDataSettings: ApiConfig = {
  serviceRootUrl: 'https://services.odata.org/V2/(S(cnqkn3twkxnupuslkhmbwq1h))/OData/OData.svc/',
  version: '2.0'
};

Testing in Edge with "Allow CORS: Access-Control-Allow-origin" extension because I was getting CORS errors with oData version 2.

node v16.17.0 (Windows 10) running from inside Visual Studio Code

Node modules used in the project (output of npm ls):

├── __ngcc_entry_points__.json@ extraneous
├── @angular-devkit/[email protected]  
├── @angular/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @types/[email protected]
├── @ui5/[email protected]
├── [email protected]
├── [email protected] (git+ssh://[email protected]/Rob--W/cors-anywhere.git#70aaa22b3f9ad30c8566024bf25484fd1ed9bda9)
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected]

Fragment of the received oData (From the network tab in the browser console)

{
"d" : {
"results": [
{
"__metadata": {
"uri": "https://services.odata.org/(S(cnqkn3twkxnupuslkhmbwq1h))/V2/OData/OData.svc/Products(0)", "type": "ODataDemo.Product"
}, "ID": 0, "Name": "Bread", "Description": "Whole grain bread", "ReleaseDate": "\/Date(694224000000)\/", "DiscontinuedDate": null, "Rating": 4, "Price": "2.5", "Category": {
"__deferred": {
"uri": "https://services.odata.org/(S(cnqkn3twkxnupuslkhmbwq1h))/V2/OData/OData.svc/Products(0)/Category"
}
}, "Supplier": {
"__deferred": {
"uri": "https://services.odata.org/(S(cnqkn3twkxnupuslkhmbwq1h))/V2/OData/OData.svc/Products(0)/Supplier"
}
}
}, {
"__metadata": {
"uri": "https://services.odata.org/(S(cnqkn3twkxnupuslkhmbwq1h))/V2/OData/OData.svc/Products(1)", "type": "ODataDemo.Product"
}, "ID": 1, "Name": "Milk", "Description": "Low fat milk", "ReleaseDate": "\/Date(812505600000)\/", "DiscontinuedDate": null, "Rating": 3, "Price": "3.5", "Category": {
"__deferred": {
"uri": "https://services.odata.org/(S(cnqkn3twkxnupuslkhmbwq1h))/V2/OData/OData.svc/Products(1)/Category"
}
}, "Supplier": {
"__deferred": {
"uri": "https://services.odata.org/(S(cnqkn3twkxnupuslkhmbwq1h))/V2/OData/OData.svc/Products(1)/Supplier"
}

The preceding error (may be relevant):

app.module.ts:30 
 [LocaleData] Supported locale "en_GB" not configured, import the "Assets.js" module from the webcomponents package you are using.
o-data-backend.service.ts:18 

The error (from the browser console):

 ERROR TypeError: this.options.helper.context is not a function
    at get context [as context] (angular-odata.mjs:6762:49)
    at ODataInMemoryCache.tags (angular-odata.mjs:157:29)
    at ODataInMemoryCache.putResponse (angular-odata.mjs:312:25)
    at Object.next (angular-odata.mjs:278:26)
    at source.subscribe.isUnsub (tap.js:17:81)
    at OperatorSubscriber._next (OperatorSubscriber.js:13:21)
    at OperatorSubscriber.next (Subscriber.js:31:18)
    at map.js:7:24
    at OperatorSubscriber._next (OperatorSubscriber.js:13:21)
    at OperatorSubscriber.next (Subscriber.js:31:18)
handleError	@	core.mjs:7635
next	@	core.mjs:26915
next	@	Subscriber.js:91
_next	@	Subscriber.js:60
next	@	Subscriber.js:31
(anonymous)	@	Subject.js:34
errorContext	@	errorContext.js:19
next	@	Subject.js:27
emit	@	core.mjs:22742
(anonymous)	@	core.mjs:26257
invoke	@	zone.js:372
run	@	zone.js:134
runOutsideAngular	@	core.mjs:26130
onHandleError	@	core.mjs:26257
handleError	@	zone.js:376
runTask	@	zone.js:181
invokeTask	@	zone.js:487
ZoneTask.invoke	@	zone.js:476
data.args.<computed>	@	zone.js:2385
setTimeout (async)		
scheduleTask	@	zone.js:2387
scheduleTask	@	zone.js:393
onScheduleTask	@	zone.js:283
scheduleTask	@	zone.js:386
scheduleTask	@	zone.js:221
scheduleMacroTask	@	zone.js:244
scheduleMacroTaskWithCurrentZone	@	zone.js:683
(anonymous)	@	zone.js:2429
proto.<computed>	@	zone.js:973
setTimeout	@	timeoutProvider.js:7
reportUnhandledError	@	reportUnhandledError.js:4
handleUnhandledError	@	Subscriber.js:158
error	@	Subscriber.js:109
_error	@	Subscriber.js:64
error	@	Subscriber.js:40
_error	@	Subscriber.js:64
error	@	Subscriber.js:40
OperatorSubscriber._next	@	OperatorSubscriber.js:16
next	@	Subscriber.js:31
(anonymous)	@	map.js:7
OperatorSubscriber._next	@	OperatorSubscriber.js:13
next	@	Subscriber.js:31
(anonymous)	@	filter.js:6
OperatorSubscriber._next	@	OperatorSubscriber.js:13
next	@	Subscriber.js:31
subscribe.innerComplete	@	mergeInternals.js:25
OperatorSubscriber._next	@	OperatorSubscriber.js:13
next	@	Subscriber.js:31
onLoad	@	http.mjs:1844
invokeTask	@	zone.js:406
onInvokeTask	@	core.mjs:26218
invokeTask	@	zone.js:405
runTask	@	zone.js:178
invokeTask	@	zone.js:487
invokeTask	@	zone.js:1661
globalCallback	@	zone.js:1704
globalZoneAwareCallback	@	zone.js:1725
load (async)		
customScheduleGlobal	@	zone.js:1809
scheduleTask	@	zone.js:393
onScheduleTask	@	zone.js:283
scheduleTask	@	zone.js:386
scheduleTask	@	zone.js:221
scheduleEventTask	@	zone.js:247
(anonymous)	@	zone.js:1964
(anonymous)	@	http.mjs:1930
_trySubscribe	@	Observable.js:37
(anonymous)	@	Observable.js:31
errorContext	@	errorContext.js:19
subscribe	@	Observable.js:22
doInnerSub	@	mergeInternals.js:19
outerNext	@	mergeInternals.js:14
OperatorSubscriber._next	@	OperatorSubscriber.js:13
next	@	Subscriber.js:31
(anonymous)	@	innerFrom.js:51
_trySubscribe	@	Observable.js:37
(anonymous)	@	Observable.js:31
errorContext	@	errorContext.js:19
subscribe	@	Observable.js:22
mergeInternals	@	mergeInternals.js:50
(anonymous)	@	mergeMap.js:13
(anonymous)	@	lift.js:10
(anonymous)	@	Observable.js:26
errorContext	@	errorContext.js:19
subscribe	@	Observable.js:22
(anonymous)	@	filter.js:6
(anonymous)	@	lift.js:10
(anonymous)	@	Observable.js:26
errorContext	@	errorContext.js:19
subscribe	@	Observable.js:22
(anonymous)	@	map.js:6
(anonymous)	@	lift.js:10
(anonymous)	@	Observable.js:26
errorContext	@	errorContext.js:19
subscribe	@	Observable.js:22
(anonymous)	@	tap.js:15
(anonymous)	@	lift.js:10
(anonymous)	@	Observable.js:26
errorContext	@	errorContext.js:19
subscribe	@	Observable.js:22
(anonymous)	@	map.js:6
(anonymous)	@	lift.js:10
(anonymous)	@	Observable.js:26
errorContext	@	errorContext.js:19
subscribe	@	Observable.js:22
query	@	o-data-backend.service.ts:18
AppComponent	@	app.component.ts:14
AppComponent_Factory	@	app.component.ts:15
getNodeInjectable	@	core.mjs:3523
instantiateRootComponent	@	core.mjs:12550
createRootComponent	@	core.mjs:13993
create	@	core.mjs:13870
bootstrap	@	core.mjs:27300
(anonymous)	@	core.mjs:26960
_moduleDoBootstrap	@	core.mjs:26960
(anonymous)	@	core.mjs:26930
invoke	@	zone.js:372
onInvoke	@	core.mjs:26231
invoke	@	zone.js:371
run	@	zone.js:134
(anonymous)	@	zone.js:1275
invokeTask	@	zone.js:406
onInvokeTask	@	core.mjs:26218
invokeTask	@	zone.js:405
runTask	@	zone.js:178
drainMicroTaskQueue	@	zone.js:585
Promise.then (async)		
nativeScheduleMicroTask	@	zone.js:561
scheduleMicroTask	@	zone.js:572
scheduleTask	@	zone.js:396
scheduleTask	@	zone.js:221
scheduleMicroTask	@	zone.js:241
scheduleResolveOrReject	@	zone.js:1265
then	@	zone.js:1461
asyncGeneratorStep	@	asyncToGenerator.js:13
_next	@	asyncToGenerator.js:25
(anonymous)	@	asyncToGenerator.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	asyncToGenerator.js:21
(anonymous)	@	Boot.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	Boot.js:32
asyncGeneratorStep	@	asyncToGenerator.js:3
_next	@	asyncToGenerator.js:25
(anonymous)	@	asyncToGenerator.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	asyncToGenerator.js:21
boot	@	Boot.js:21
(anonymous)	@	UI5Element.js:990
asyncGeneratorStep	@	asyncToGenerator.js:3
_next	@	asyncToGenerator.js:25
(anonymous)	@	asyncToGenerator.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	asyncToGenerator.js:21
define	@	UI5Element.js:989
4361	@	Label.js:154
__webpack_require__	@	bootstrap:19
6141	@	Avatar.js:420
__webpack_require__	@	bootstrap:19
5752	@	TabSeparator.js:90
__webpack_require__	@	bootstrap:19
9067	@	app.module.ts:30
__webpack_require__	@	bootstrap:19
6747	@	app.component.html:1
__webpack_require__	@	bootstrap:19
4431	@	environment.ts:16
__webpack_require__	@	bootstrap:19
__webpack_exec__	@	main.ts:12
(anonymous)	@	main.ts:12
__webpack_require__.O	@	chunk loaded:23
(anonymous)	@	main.ts:12
webpackJsonpCallback	@	jsonp chunk loading:34
(anonymous)	@	main.js:2

No easy way to provide config asynchronously

Hello,
in the latest versions of angular, using the environment.ts is no longer the preferred option to configure your app and therefore many people like myself use the http client to get an appconfig.json file asynchronously and inject it using the APP_INITIALIZER token.

In this regard, it is not possible to provide platform-dependent config to the ODataModule using ODataModule.forRoot().

I resolved the issue by inspiring myself from the "forRoot()" implementation from your code to inject the ODATA_CONFIGURATIONS token using a factory rather than a static value, using the following template in the imports section of my AppModule : 

export function createSettings(configs: ApiConfig[]) {
  return new ODataSettings(...configs);
}

const oDataConfigFactory = (appConfigService: AppconfigService) => {
  return [{serviceRootUrl: appConfigService.config.odataApiUrl}]
}
imports([
// ...
{
      ngModule: ODataModule,
      providers: [
        {
          provide: ODATA_CONFIGURATIONS,
          useFactory: oDataConfigFactory,
          deps: [AppconfigService]
        },
        {
          provide: ODataSettings,
          useFactory: createSettings,
          deps: [ODATA_CONFIGURATIONS],
        },
        ODataClient,
        ODataServiceFactory,
      ],
    }
// ...
]

So, not really an issue but more like a feature request, it would be great if you included a native way to do this.

Other than that, cool library, very useful ;)

'"angular-odata"' has no exported member named 'ODataFunctionOptions'. Did you mean 'ODataEntityOptions'?

hello- the title says it all;

project config is mononucleorepo in a workspace with 2 libraries as per
image

both libraries were generated with variants of code like this

ng new my-workspace --no-create-application
cd my-workspace
ng generate library my-lib

then odata modules were generated with code like this

docker run -it --rm -v ${PWD}:/local diegomvh/odataapigen `
  Name=HorselessHosting `
  Metadata=file:///local/HostingCollectionMetadata.xml `
  Output=/local

the output was pasted into the libraries, with peer and dev dependencies like this

  "peerDependencies": {
    "@angular/common": "~x.x.x",
    "angular-odata": "^x.x.x",
    "@angular/core": "~x.x.x"
  },
  "devDependencies": {
    "@angular/common": "~x.x.x",
    "angular-odata": "~x.x.x",
    "@angular/core": "~x.x.x"
  },
  "dependencies": {
    "tslib": "^2.3.0"
  }

however on ng build the service classes all throw some variant of

error TS2724: '"angular-odata"' has no exported member named 'ODataFunctionOptions'. Did you mean 'ODataEntityOptions'?
21   ODataFunctionOptions,

please advise

How to build a query with object filters using enum options?

Hi Mr. van Haaster,

I am trying to build an odata-queries using your package with object filters and enums as follows:

filter = { filmClassification: { eq: 2 } }

My expectation is to produce the corresponding enum option like:

...?$filter=filmClassification eq 'Horror'

Besides the metadata is defined, it is produced as a number and not the corresponding string which should be set through metadata config from the package.

I would like to know how to build such query.

Thank you in advance!

Kind Regards

Kapri

Custom entity update endpoint address

Is this possible to add ability to specify a custom action address when updating entities? I think, that it would be really useful.

For example, there is an entity User I want to send a PUT request to change the user password.

In that case, the request would look like:

PUT /users -> to update the entire user entity
PUT users/passwordchange -> to change the password only

Linking Entities by Reference

Hello,

I need to be able to be able to link two existing entities that are related in a many-to-many. As far as I can tell, the OData spec requires that you POST or PUT an OData Reference to the NavigationProperty (plus a "$ref"). I have the endpoint working on the server side (dotNet OData WebApi) but can't seem to find a way to create the request within angular-odata.

Here is a pseudo code example of what I am trying to do:

Given the following model/data:

  • People:
    • Schema: {ID: string, Locations: Location[]}
    • Data: [{ID: "John"}]
  • Location:
    • Schema: {ID: string, People: []}
    • Data: [{ID: "London"}]

If I want to link "John" to the "London" location I would POST the following request:

POST http://odata.example.com/People('John')/Locations/$ref HTTP/1.1
<HTTP Headers here>

{"@odata.id":"http://odata.example.com/Locations('London')"}

This should create the link in the many-to-many table on the server. I have this working via Postman, but can't seem to figure out how to do it using angular-odata.

Am I missing how to create this request? Is this not currently supported? I appreciate any assistance.

Issues with guid (latest version)

Hi Diego,

I see that you had closed some issues about the same topic, but it seems to me that is still not working as intended. I'll try to explain my scenario:

I have a service created using your automatic generator; the service expose an entity called "books" with a key called "id" of type edm.guid. When i try to get a single element (using either fetchOne or get) the url that the service compose is wrong:

image

image

WIth the help of our old friend, the chrome debugger, i found out that the key is passed to the builder method guid, which converts my string into an object with type "guid" and my value as the parameter "value".

image

image

Then, the method "buildQuery" is invoked, and when the key is checked the components of the object previously created are treated as two separate keys and not as a single key with a type specification:

image

Am i calling the function in the wrong way or is there an effective issue?

Thanks again for your efforts on this library.
Luca

withCredentials

Hi
Please, tell me is there the way to set withCredentials: true for ODataClient?

Handle Guid's strings for OData v4 when used as Key

Issue
In Odata V4 if the key of a table is a Guid type the url that is being generated is incorrect. The filter function also is formed incorrectly.

The generated url that is being executed when using the fetchOne command comes out like this
https://localhost:4400/odata/League('856f217a-8b40-4637-96a1-ff39da129316')

The generated url that is being executed when using filter and get command comes out like this. If I manually remove the single quotes( %27's ) the call works.
https://localhost:44317/odata/League?$filter=Id%20eq%20%27856f217a-8b40-4637-96a1-ff39da129316%27

In OdataV4 server the guid doesn't get processed properly unless if the url is formed without the quotes around the guid.

Example
I am using fetchCollection here to verify the odata source is exactly passing back the right guid data.

this.leagueService.fetchCollection().subscribe(observer => {
  console.log('result', observer[0]);
  const id = observer[0][0].Id;

  this.leagueService.fetchOne(id).subscribe(observer2 => {
    console.log(observer2);
  });

  const leagueEntities = this.leagueService.entities();
    leagueEntities.filter({ Id: id });
    leagueEntities.get().subscribe(observer2 => {
      console.log('filter', observer2);
    });
  });

Expectation
The url that is generated for Guid's has no quotes around the key value for fetchOne.
https://localhost:4400/odata/League(856f217a-8b40-4637-96a1-ff39da129316)

Potential Fix
My proposal for a fix is 2 steps.

  1. the ODataApiGen app would need updated to change the service models schema data to guid when it reads the $metadata.
  2. During the generating url step it would be check the schema to see if it is of type guid and wrap it in nothing for Guid's, but keep single quotes for strings.

This code could be generated for the Id type to be guid instead of string.

export const LeagueSchema = {
  Id: { type: 'guid', key: true, ref: 'Id', nullable: false },
  Name: { type: 'string' },
};

value is undefined

Hi,

I tried to generated the config files and call the Webservice. I got a correct reply with d as values. But I got the error below. Because body["value"] is undefined but body["d"] should be the correct one. Did I something wrong in the configuration?

image

I called it with

let person = this.user.entities();
person.filter({ Username: 'XXXX' });
person.get().subscribe((t) => {
debugger;
console.log(t);
});

Thanks & Regards

IEEE754Compatible support

Hi Diego,

I'm trying to use sap cds as a backend for an angular app, using your library in order to consume the odata service exposed.

When i try to update or create a new element of my service with a decimal property i get an error from the backend, becouse it requires the decimals in the payload to be encoded as JSON strings, and the content-type header to be "application/json;IEEE754Compatible=true".

I can set the header for the patch operation (but not for the "create") using the httpoptions parameter, but the payload is still not compliant with the specification. Is there a way to handle the conversion (aside from setting the field as a string in the .config of the entity)?

Reference for this error:
https://answers.sap.com/questions/12991667/how-to-format-edmdecimals-in-postputpatch.html

Thanks
Luca

Server errors aren't passed through

If using the OdataEntityService (in my case the update method) and the server returns an error (in my case 422), the error message from the server is not passed through to the caller, but instead, I always get an error like "scheduler.schedule is not a function".
Update and probably the other HttpClient using methods should pass the correct error as such common error like the mentioned is very hard to debug.

Is it much work to change that?

Feature request: Simplify ODataSingeltonResource get options responseType to default to 'entity'

Hey! very minor issue. I hope I'm using the library correctly. It would be nice to have the responseType in options parameter of get in ODataSingletonResource to default to 'entity'

Here is some reference code:

  private _user : AppUser;
  private _userOdata      : ODataSingletonResource<AppUser>;

  constructor(private _CONST  : ConstantsService,
                      odata   : ODataClient) {
    this._userOdata = odata.singleton<AppUser>(this._CONST.ODATA_URLS.USER);
  }

  public get user() : Promise<AppUser> {
    return new Promise(async (resolve)=>{
      if(!this._user)
        await this.initUser();
      resolve(this._user);
    });
  }

  async initUser(){
    // is this necessary? Singelton is implied that it returns one entity
    let options = {
      responseType: 'entity' as 'entity'
    }
    this._user = await this._userOdata.get(options).toPromise().then(([user]) => user);
  }

Would like library replace single quote in Odata query by default

Currently it known if in odata query is value with single quotes this will throw server error. For Odata query all single quotes must be replaced with couple single quotes ( O'Reilly -> O''Reilly ). Would like so this will be default behaviour in library query builder but not customer code.
image

$batch with CORS fails

code sample to reproduce:

const odata = new ODataClient(httpClient, new ODataSettings({
      serviceRootUrl: this.ApiOdataEndPointUrl
    }));

    odata.entitySet('Clients').get().subscribe(console.log); // <-- this works fine
    odata.entitySet('Projects').get().subscribe(console.log); // <-- this works fine

    const batch = odata.batch();
    batch.post(() => {  // <-- this fails
      odata.entitySet('Clients').get().subscribe(console.log);
      odata.entitySet('Projects').get().subscribe(console.log);
    }).subscribe(console.log);

batch produces these errors in Browser's console
image

is this a configuration issue? did I miss something?

return data object not type my user

my error..

export class AppComponent implements OnInit, AfterViewInit {
public userlist: IUser[];

constructor(
private signalRService: SignalrService,
private user: UserService
) {}

ngOnInit(): void {
this.user
.entities()
.all()
.subscribe((data) => {
this.userlist = data;
console.log(this.userlist);

//(2) [{…}, {…}] // two object return
0: {FirstName: "Mansur", LastName: "DEĞİRMENCİ", BirthDateTime: Wed Nov 17 1993 00:00:00 GMT+0200 (GMT+03:00), Location: "tr-TR", CreatedDateTime: Sat Sep 12 2020 14:37:26 GMT+0300 (GMT+03:00), …}
1: {FirstName: "Cansu", LastName: "BULUT", BirthDateTime: Wed Nov 17 1993 00:00:00 GMT+0200 (GMT+03:00), Location: "tr-TR", CreatedDateTime: Sat Sep 12 2020 14:37:32 GMT+0300 (GMT+03:00), …}//

  });

}
ngAfterViewInit(): void {}
ngOnDestroy() {}
}

Error is thrown when an OData get operation is intended

Hi again Diego!

I have updated to the latest version of your package yesterday and I saw that the following error is thrown:

ERROR TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Do you have any idea about why this happens?

Thank you!
Kapri

how to access all properties in raw json reply

given a json snippit like below that expands and counts a navigation property, created with a query like so

https://serviceroot.com/lache/ODataHosting/Tenant(ebc3f5cb-65df-4537-8279-08da9757ccd6)?$expand=AccessControlEntries($top=4;$skip=0;$count=true)

image

if this were a $query against the root entityset the library lets you get the count from annots

but how do you get nested counts of navigation properties?, as in this case

as well and related, how does one access the raw json reply?

thanks

Can't bind an entity using ODataEntitySetResource<T>.entity( string | object )

I couldn't create a valid EntityKey<T> to target a single entity. I have a composite key for T
I'm trying the following

// attempt 1 - fail (no qoutes)
let key = x.update.CustomerId + ',' + x.update.ItemId;
// attempt 2 - fail (escapeIllegalChars function)
let key = `'${x.update.CustomerId}','${x.update.ItemId}'`;
// attempt 3 - fail (entity-set.ts - never gets resolved because this.type() is always undefined and findStructuredTypeForType is never called which then this.schema?.resolveKey(key as {[name: string]: any}) is always undefined )
let key = { CustomerId: bagItem.CustomerId, ItemId: bagItem.ItemId };
let ent = itemOdata.entity(key);
let obs = ent.delete();

So what I'm guessing is, I HAVE to attach an entity schema config?

This is my import. I am passing the most minimum information. I do not want to maintain entity.schema files.

ODataModule.forRoot({ serviceRootUrl: ENVIRONMENT.odataServiceRoot, metadataUrl: `${ENVIRONMENT.odataServiceRoot}/$metadata` } as ApiConfig),

@diegomvh what do you suggest?

This package has errors during build.

I run ng build to my project and I am receiving the following error message:

ERROR in node_modules/angular-odata/lib/resources/requests/action.d.ts:32:5 - error TS2416: Property 'post' in type 'ODataActionResource<P, R>' is not assignable to the same property in base type 'ODataResource<R>'. 
  Type '{ (body: P | null, options: HttpEntityOptions): Observable<ODataEntity<R>>; (body: P | null, options: HttpEntitiesOptions): Observable<...>; (body: P | null, options: HttpPropertyOptions): Observable<...>; }' is not assignable to type '(attrs: Partial<R>, options: HttpOptions & { responseType?: "arraybuffer" | "blob" | "value" | "property" | "entity" | "entities" | undefined; withCount?: boolean | undefined; }) => Observable<...>'.
    Types of parameters 'body' and 'attrs' are incompatible.
      Type 'Partial<R>' is not assignable to type 'P'.
        'P' could be instantiated with an arbitrary type which could be unrelated to 'Partial<R>'.

32     post(body: P | null, options: HttpEntityOptions): Observable<ODataEntity<R>>;
       ~~~~
node_modules/angular-odata/lib/resources/requests/action.d.ts:33:5 - error TS2416: Property 'post' in type 'ODataActionResource<P, R>' is not assignable to the same property in base type 'ODataResource<R>'.
  Type '{ (body: P | null, options: HttpEntityOptions): Observable<ODataEntity<R>>; (body: P | null, options: HttpEntitiesOptions): Observable<...>; (body: P | null, options: HttpPropertyOptions): Observable<...>; }' is not assignable to type '(attrs: Partial<R>, options: HttpOptions & { responseType?: "arraybuffer" | "blob" | "value" | "property" | "entity" | "entities" | undefined; withCount?: boolean | undefined; }) => Observable<...>'.

33     post(body: P | null, options: HttpEntitiesOptions): Observable<ODataEntities<R>>;
       ~~~~
node_modules/angular-odata/lib/resources/requests/action.d.ts:34:5 - error[0m TS2416: Property 'post' in type 'ODataActionResource<P, R>' is not assignable to the same property in base type 'ODataResource<R>'.
  Type '{ (body: P | null, options: HttpEntityOptions): Observable<ODataEntity<R>>; (body: P | null, options: HttpEntitiesOptions): Observable<...>; (body: P | null, options: HttpPropertyOptions): Observable<...>; }' is not assignable to type '(attrs: Partial<R>, options: HttpOptions & { responseType?: "arraybuffer" | "blob" | "value" | "property" | "entity" | "entities" | undefined; withCount?: boolean | undefined; }) => Observable<...>'.

34     post(body: P | null, options: HttpPropertyOptions): Observable<ODataProperty<R>>;

SOLUTION: Please make the following changes to the action.ts file:

...
//#region Requests
  post<P>(body: P | null, options: HttpEntityOptions): Observable<ODataEntity<R>>;
  post<P>(body: P | null, options: HttpEntitiesOptions): Observable<ODataEntities<R>>;
  post<P>(body: P | null, options: HttpPropertyOptions): Observable<ODataProperty<R>>;
  post<P>(body: P | null, options: HttpEntityOptions & HttpEntitiesOptions & HttpPropertyOptions): Observable<any> {
    return super.post(body, options);
  }
  //#endregion
...

Thank you!

Return the expanded result when posting an entity

Hey,

thanks for this awesome odata-client. Since it's quite similar to the SimpleODataClient in C# I'm wondering if there is a similar way to tell the .post() that we want the expanded result as the return value?
And, in addition to that, is there a way to say "expand everything" instead of using this:

expand({NavProperty1: { expand: { NavProperty2: { expand: 'NavProperty3' } }, }}); ?

Thanks in advance and stay safe!

Update fails because URL is too long

Hi Diego,

I just tried out your OData project and it seems to be nice stuff, however, I faced with an issue:

During update I get an error:
image

The generated URL is the following:
https://services.odata.org/TripPinRESTierService/(S(qiryiepyqeb4vnqtrschujny))/People(UserName='elainestewart',FirstName='Elaine',LastName='Stewart',MiddleName=null,Gender='Female',Age=null,Emails=['[email protected]','[email protected]'],FavoriteFeature='Feature1',Features=[],AddressInfo=[Address='55%20Grizzly%20Peak%20Rd.',City=Name='Butte',CountryRegion='United%20States',Region='MT'],HomeAddress=null)
Which indeed seems to be quite long. As of the OData spec, I would use a bit more compact one (which works well in a REST client, as only the UserName is Key according to the metadata):
https://services.odata.org/TripPinRESTierService/(S(qiryiepyqeb4vnqtrschujny))/People('elainestewart')

This gets generated from the following code
this.peopleService.update(this.selectedPerson).subscribe(( entity ) => console.log("Updated: ", entity)); where selectedPerson is a complete person I got from the OData service.

Another interesting thing is that the update is a PUT request instead of the recommended (OData v4) PATCH.

Am I doing something wrong here? Could you please advise how can I use the update function correctly?
(Sidenote: get works, delete fails because of the same reason.)

Thanks and Best regards,
David

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.