deblockt / hal-rest-client Goto Github PK
View Code? Open in Web Editor NEWTypescript HAL Rest client
License: Apache License 2.0
Typescript HAL Rest client
License: Apache License 2.0
After using then
on the promise from a fetch
with my model class, the returned object's attributes are undefined. The props
attribute has all of the correct information, but it doesn't get applied to the model object like it stated in the readme:
Read props is simply call object attributes.
const name = resource.name;
It seems that library doesn't work with parsing template links. Are you going to implement templates support?
Hi again,
I've another case to analyze with you.
This is the json returned by spring application:
https://gist.github.com/baxtheman/0d954791b339647f9044e2e8640a9f96
Inside every commessa
master object there is a lavorazioni
children objects collection. In the gist you find the java definitions also.
From fetch by the hal-rest-client the resource obtained for a commessa
object is this:
Then, why the props.lavorazioni
is an not empty array of undefined object?
Let's get some list to show:
parts: Array<Part>;
If to change parts[0].description
and call parts[0].update()
- only "description" field is sent to server.
But if we update list: this.provider.loadParts().then(parts => this.parts = parts );
- we will get all fields marked as updated.
Then on parts[0].update()
all fields are sent to server.
Could you please separate HalResource certain field update from object replacement? Or some other way to keep list untouched when replace all the list.
Can we get custom fields like type from link?
For example I have following link:
txt: {href: "projects/path_to_file", type: "text/plain"}
And I want to use "type" for "Accept" header. But I can't get it from the link. I tried something like
file.link("txt").prop("type"), file.link("txt").props , but it doesn't work.
Thanks!
Hi,
Talking about version 0.2.6
I need to have authentication and this is the hal client setup:
const options = {
withCredentials: true,
auth: {
username: 'user11',
password: 'pwd44',
},
};
const halData = createClient(`${serverUrl}/data`, options);
but no authentication header appears in the first request: https://i.imgur.com/YZfK7oe.png
What's wrong?
thx
I have tried with 0.5.0 version and everything works fine. Thank you.
But also I've faced another thing:
Fetch parent resource and show it in ui.
Fetch child resouce which has parent as embedded staying on the same ui.
Errors in browser like "Cannot read property 'somename' of undefined" for parent.
As I understand, on fetching child resource parent is reseted. And than created with key-value pairs from new server response. But in embedded object there are less parent properties, than we recieved previously from fetching parent. And as we use parent resource for our ui directly, the problem occure.
Could you suggest something?
Updating from 0.2.0
to 0.3.0
breaks the build process. Neither ng build
nor running webpack after a ng eject
isn't working anymore. The compiler complains, it cannot find the methods from HalResource
, etc. It throws an exception like this:
ERROR in C:/<path>/admin-ui/src/app/app.component.ts (48,46): Property 'link' does not exist on type 'BookingList'.
BookingList
is of type HalResource
:
export class BookingList extends HalResource {
@HalProperty(Booking)
bookings: Booking[] = [];
}
Side note: Navigating in Jetbrains IDE isn't working anymore too.
Hello,
I'm wondering if it is possible to upload the file during the new Resource creation? e.g:
export class Avatar extends HalResource {
@HalProperty()
image: any; // <--- expect to handle the file
@HalProperty()
name: string;
}
On the HTML view lets assume we have something like that
<label>UploadAvatar</label>
<input class="form-control" type="file" (change)="onFileInputChange($event)">
TS method looks as below:
onFileInputChange(event) {
if(event.target.files.length > 0) {
let file = event.target.files[0];
const resource = createResource(this.http.halClient, Avatar, "avatar/");
resource.image = file;
resource.name = 'Preview';
resource.create();
}
}
So this example works and the Ajax call is being made successfully, However the payload is incorrect since image field does not send the File but empty object:
At the same time if I'll do the same without using hal rest client (via native xhr), the request will be correct! e.g:
onFileInputChange1(event) {
if(event.target.files.length > 0) {
let formData = new FormData();
formData.append('image', event.target.files[0]);
formData.append('name', 'Preview');
this.upload.sendFiles(this.http.apiUrl + '/avatar/', formData).subscribe((progress) => {
console.log(progress);
}, (error) => {
console.log(error);
});
}
}
Hey,
Getting to know the library and the docs, I think they could do with a bit of a refresh:
I've written a lot of docs in my time, e.g.:
Want me to take a look?
Iresource.update(), resource.delete() don't work in 0.3.*, but there can be also another api.
Issue is related to uri. I think these functions try to use this.uri instead of this.uri.uri
Is there any way to look at the original data returned from a request?
I just want to dump this out when getting to know the API so I can see what's what, rather than having to iterate through all embeds.
If I try to dump the instance out JSON.stringify()
complains about circular references.
Unless anyone has a better idea of how to do this?
Hello,
Recently we have faced with the situation when we need some specific logic that must be called on every HTTP request/response
Interceptors layer will fit our purposes as best.
However Angular2 interceptors won't make any effect since you are using Axios HTTP client for the communications.
However Axios also allows to set up HTTP req/resp interceptors:
https://github.com/mzabriskie/axios#interceptors
Is there any chance to allow interceptors configuring during the HAL REST client setup?
e.g.
this.halClient = createClient(this.apiUrl);
this.halClient.addHeader('Content-Type', 'application/json');
// Add a request interceptor
this.halClient.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
this.halClient.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Or similar?
Regards,
Evgeniy
I can't seem to alias property names at the same time as setting a model as the documentation says:
I want to:
apps
to appsData
AppsData
instance @HalProperty()
public data: AppsData
Can reference data via links
but links is a HalResource
(I know this is because links are raw):
@HalProperty('apps')
public appsData: AppsData
Can reference data by appsData
but property is a HalResource
:
@HalProperty(AppsData)
public apps: AppsData
Can only reference data by apps
and property is an AppsData
model
@HalProperty('apps', AppsData)
public appsData: AppsData
Can reference data by apps
but property is not an AppsData
model
This last setup should have aliased apps
to appsData
and appsData
should have been an AppsData
model, no?
When I query a resource with a templated link like /api/resource/{id}
the value for id
isn't stored in the resource.
I have this rels-object that functions as dictionary for all my endpoints in the API:
{
"_links": {
"self": {
"href": "/api/v1/rels.hal"
},
"calculation": {
"href": "/api/v1/calculation/{id}",
"templated": true
},
"customer": {
"href": "/api/v1/customer/{id}",
"templated": true
}
}
}`
In my application I fetch a calculation resource like (rels
is of type HalResource
)
let calculation = rels.links('calculation').fetch({id: 5})
Which works fine. But when I want to update the resource calculation.update()
the templated URL isn't resolved with the initial attributes {id: 5}
.
After looking through the source code I found the member URI:fetchedURI
, but it seems that the value is always empty. Shouldn't it be filled with the resolved URL of the fetched Resource?
Do Links get cached? If yes, why? I would like to show stuff based on if the Link is missing or shown. Is this possible?
Hi,
I've upgrade to last version 0.3.0 and the resource.create() gets error from the java spring data backend.
Code to save new resource:
const resource = createResource(halData, HalResource, '/rilevazione');
resource.prop('data', date);
resource.prop('commessa', order.links.commessa.uri);
resource.create()
the json POST by browser to backend, version 0.2.6
{
"commessa": "http://95.242.194.83:8080/test-api-v3/data/commessa/1641",
"data": "2017-10-02T22:00:00.000Z",
...
}
the json POST by browser to backend, version 0.3.0
{
"commessa": {
"fetchedURI": "",
"templated": false,
"uri": "http://localhost:8180/registrazioni/data/commessa/1630"
},
"data": "2017-10-02T22:00:00.000Z",
...
}
The last version is not recognized by the backend and it produces an exception (commessa field empty).
What can I do to resolve?
thx
Hi Thomas,
I've faced with a strange issue:
In my code I retrieve dashboard data as like below:
async loadDashboardData(): Promise<DashboardInfo> {
return await this.http.halClient.fetch("/dashboard", DashboardInfo);
}
So according to the documentation I expect to retrieve instance of DashboardInfo type as a result.
However, instead i get an object of base HalResource type:
In all other cases casting went well. So after some research I've found out interesting thing:
If during the previous requests to API through the halClient I will get a HAL resource with a link to the same href equal "/dashboard", then future casting won't work anymore.
It seems like halClient automatically cast all the Links to the default base HalResource type, which makes impossible future casting through the fetch method as in example above.
Can you please take a look on that?
P.S. In support of my idea - once I remove the "/dashboard" href from my API responces, code above has started to work fine and output DashboardInfo instance instead of HalResource.
Best Regards,
Evgeniy
There is this setup:
export class CustomerResource extends HalResource {
@HalProperty()
id: number
@HalProperty()
name: string
// @HalProperty(StoreResource)
// stores: Array<Store>
}
@Injectable()
export class CustomerService {
getCustomers(): Promise<CustomerResource[]> {
const uri = this.createUrl('customer')
return this.client.fetchArray(uri, CustomerResource)
}
getStores(customer: CustomerResource): Promise<StoreResource[]> {
const resource = customer.link('stores')
const link = resource.fetch()
return (<Promise<any>>link)
}
}
When I call customerService.getStores(customer)
I get the error Error: object is not hal resource [<array of stores>]
. It doesn't change a thing if I comment out the stores
field in the CustomerResource
type or not. However, fetching the customer in the first place works fine. And I don't see any difference to your example code (besides the await
statements) in the wiki.
There is a alternate approach that actually works, but I guess it wasn't meant to be (looks a bit ugly to me ;)
const link = customer.link('stores').uri.uri
return this.client.fetchArray(link, StoreResource)
As you may already have guessed by the @Injectable()
decorator, I'm using Angular 5 and calling resource.fetch()
returns a ZoneAwarePromise
. Maybe that's a clue.
Hi!
We are trying to use your library but step into the issue.
If we have prop in json and have link with similar name
{
"_links": {
"parent": { "href": "..." }
},
"title": "Test category",
"parent": "Test parent"
}
When we parse it:
resource.props.parent - undefined
resource.prop('parent') - returns HalResource, but we're expecting to get "Test parent" here
I check your code and find condition that is responsible for this bug
Also if you look closer to the parser, it's obviously have a racing issue and result depends on the json key order
When you iterating over the json the key order is not determinated
for (const key in json) {
...
}
So in case your json has "_links" key before other props – you get one result
If key order is different you will get another result (Your if-condition will go to the "else", because _links field will be parsed after "parent" :
{
"title": "Test category",
"parent": "Test parent",
"_links": {
"parent": { "href": "..." }
}
}
I gotta say, I'm loving HalResource
models and @HalProperty
decorators! Amazing stuff, well done.
However, I've found model population and loading to be a big hit and miss, I assume, because of 1) my coding but possibly 2) the data.
I had a hierarchy loading well, but it now seems to have stopped loading at appData
(I am aliasing this in the model decorator):
export default class Company extends HalResource {
@HalProperty()
public name: string
@HalProperty('apps', AppsData)
public appsData: AppsData
}
sites.sites[0].companies[0].links.appsData.isLoaded // false
The JSON / class mapping is:
- Sites
- _link: sites[]
- Site
- _embed: companies[]
- Company
- _link: appsData (originally apps)
- AppsData
- _embed: apps[]
- App
The site data is:
{
"name": "DEV",
"_embedded": {
"companies": [
{
"name": "Space Invaders Parent - DEV",
"_links": {
"self": {
"href": "https://spaceinvaders.bookingbug.com/api/v1/site/37003"
},
"install_app": {
"href": "https://spaceinvaders.bookingbug.com/api/v1/37003/apps/install_from_portal{?name}",
"templated": true
},
"apps": {
"href": "https://spaceinvaders.bookingbug.com/api/v1/site/apps"
}
}
}
]
},
"_links": {
"self": {
"href": "https://spaceinvaders.bookingbug.com/api/v1/site"
}
}
}
The link site > _embed:companies[] > _link:apps
is supposed to load:
{
"total_entries": 1,
"_embedded": {
"apps": [
{
"id": 2,
"name": "vidyo",
...
}
]
},
"_links": {
"self": {
"href": "https://spaceinvaders.bookingbug.com/api/v1/admin//apps"
}
}
}
I assume I need a model class for every link and every embed?
It seems that if I match the data hierarchy wrong, a model will be instantiated but with everything undefined
.
I'm pretty sure at some point I had the Apps loading.
My hunch is that the lib is getting confused as there are two consecutive apps
keys.
Is there any way to debug a model as it populates?
It seems that with the new 0.2.4 version the restClient field modificator was changed from protected to private. Is there any chance to get back protected restClient?
Lets say I have a model which has a field expected as link.
Model:
export class Foo extends HalResource {
@HalProperty('response.bar')
bar: Bar; // it's link
}
When fetching Foo resource hal-parser spits out bar
field as HalResorce
not as Bar
which is incorrect behavior. After some debugging I found that library passes to method Reflect.getMetadata link key as it is from hal json (e.g. 'response.bar'
) but MetadataMap contains field names and appropriate constructors in format that is specified in @HalProperty()
decorator (e.g. 'bar'
). Apparently proper constructor cannot be found by incorrect key and that leads to casting Bar
field to HalResource
.
Hi Thomas,
One of my API endpoints returns me a set of similar structures which i'd like to map on my custom client side HAL resource model.
The output structure is looks like below:
data:
set1:
count: 3
list: [type1, type1, type1]
set2:
count: 2
list: [type2, type2]
set3:
count: 4
list: [type3, type3, type3, type3]
So, I've tried to to map such a structure on the following kind of model:
class InfoSet<T extends HalResource> extends HalResource {
@HalProperty()
count: number;
@HalProperty(T) <---- ERROR: 'T' only refers to a type, but is being used as a value here
list: T[];
}
export class Data extends HalResource {
@HalProperty(InfoSet)
set1: InfoSet<Type1>;
@HalProperty(InfoSet)
set2: InfoSet<Type2>;
@HalProperty(InfoSet)
set3: InfoSet<Type3>;
}
But it shows an error as displayed above at 5th row
So I've tried to workaround that as like that:
class InfoSet<T extends HalResource> extends HalResource {
test: T;
@HalProperty()
count: number;
@HalProperty(typeof this.test) <--- No more error
list: T[];
}
After that there were no any compile time issues anymore, but on application open , i've got the next error in browser console:
Where DashboardInfo is the same InfoSet but in my real app.
But T already extends HalResource type..
Is it possible to map response data on typescript generic HAL models?
If yes, what am i doing wrong?
Regards,
Evgeniy
Hello,
Is there any chance to allow CORS requests sending using the credentials?
In XHR there is a field called withCredentials that allows that:
https://developer.mozilla.org/ru/docs/Web/API/XMLHttpRequest/withCredentials
I guess you are using the Axios HTTP client which supports this config setup:
https://github.com/mzabriskie/axios#request-config
Is there any chance to allow withCredentials config during HAL REST client setup?
e.g.
this.halClient = createClient(this.apiUrl);
this.halClient.addHeader('Content-Type', 'application/json');
this.halClient.withCredentials = true; // SOMETHING LIKE THAT
Regards,
Evgeniy
Backend is sending a response
embedded: {
signal: {
mysignal: "test"
}
},
links: {
signal: "http://mysignal.de"
}
Hal-Rest-Client is converting it:
links: signal does exist
props.signal: signal is undefined
Please, see this hal json response from Spring Data application:
https://gist.github.com/baxtheman/5873c9de7821970f2d075b733ee6a3c4
It causes a parsing error calling the service with fetchResource
Uncaught (in promise) TypeError: Cannot read property 'href' of undefined at JSONParser.jsonToResource (19:17) at JSONParser.parseJson (19:67) at eval (19:63) at Array.map (<anonymous>) at JSONParser.parseJson (19:63) at JSONParser.jsonToResource (19:45) at JSONParser.parseJson (19:67) at eval (19:63) at Array.map (<anonymous>) at JSONParser.parseJson (19:63)
This the position of the exception:
Any suggestions?
thx
Is it possible to parse nested objects, nested array, which are not embedded and doesn't have _link and href properties?
It would be useful to make HalResource.restClient
field protected
instead of private
. This allows to create additional method in model classes that will make some requests or use the base url. For example, request for images or other non-HAL resources.
If I have the following JSON, it seems that arrays of links cannot be fetched:
{
"_links": {
"self": {
"href": "https://example.com/sites"
},
"site": { "href": "https://example.com/sites/1" },
"sites": [
{ "href": "https://example.com/sites/1" },
{ "href": "https://example.com/sites/2" },
{ "href": "https://example.com/sites/3" }
]
}
}
Site:
sites.link('site').fetch().then(console.log)
HalResource {_uri: URI, links: {…}, props: {…}, isLoaded: true, settedProps: Array(0), …}
Sites:
sites.link('sites').fetch().then(console.log)
Promise {<rejected>: TypeError: Cannot read property 'replace' of undefined
at combineURLs (webpack-internal:///./nod…}
combineURLs.js?2053:11 Uncaught (in promise) TypeError: Cannot read property 'replace' of undefined
at combineURLs (combineURLs.js?2053:11)
at Axios.request (Axios.js?951a:41)
at Axios.(anonymous function) [as get] (webpack-internal:///./node_modules/hal-rest-client/node_modules/axios/lib/core/Axios.js:67:17)
at Function.wrap [as get] (bind.js?ff72:9)
at eval (hal-rest-client.js?ab6a:82)
at new Promise (<anonymous>)
at HalRestClient.fetch (hal-rest-client.js?ab6a:81)
at HalResource.fetch (hal-resource.js?dd8c:32)
at <anonymous>:2:21
The spec says this should be possible (see ea:order
):
We are using another HAL library as well, and it handles arrays of links just fine.
It seems that fetching collections of links could do with a bit of sugar.
Right now, to collect a list of resources from an array of links we need something along the lines of:
function fetch (res, name) {
const target = res.prop(name)
return Array.isArray(target)
? Promise.all(target.map(t => t.fetch()))
: target.fetch()
}
If we had a HalCollection
class, with perhaps an IFetchable
interface, both HalResource
and HalCollection
could be fetch()
ed:
const results = res.prop('site').fetch().then( /* Site or Array of Sites */ )
Of course the collection class would be iterable just like an Array
as well.
What do you think?
Hello,
It would be useful to add possibility to force data updating when using HalRestClient fetchArray method. (Currently there is a force param in HalResouce fetch method that do the trick.)
I've faced with an issue related to this updating. When I retrieve data using HalRestClient fetchArray method, I expect to see new response each time, but instead I recieve partly updated one with cached properties from previous call.
If i create multiple "sub-requests" out of the same main Request, those sub request seem to override each others properties.
this.mainRequest = await getResponseFromEntryPoint('/')
this.resource1 = await this.mainRequest.link('byKey').fetch({key: 'keyVal1'})
this.resource2 = await this.mainRequest.link('byKey').fetch({key: 'keyVal2'})
this.resource3 = await this.mainRequest.link('byKey').fetch({key: 'keyVal3'})
These resource variables override each other. Therefore the edit link on all those resources is the same, the one of the last request.
Is this a bug or am I just using the hal-rest-client wrong?
Hello. Any idea how to handle paging information when fetching an array? I mean:
{
"_embedded": {
"products": [ { ... } ]
},
"_links": { ... },
"page": {
"size": 20,
"totalElements": 100,
"totalPages": 5,
"number": 0
}
}
I figured out a workaround for that, but it requires 2 requests (one for fetching an array, one for page info).
Can we do it in one shot?
Thanks.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.