I have a an Ember Cli project that runs on the localhost:4200 and an asp.net webapi project that runs on localhost:56967. Both projects run fine seperatly: I can start up my Ember app and test several routes, looks fine and I can visit my api (for example: api/products) and i see my response.
The problem I'm having is hooking the two things up with each other.
Adapter
export default DS.RESTAdapter.extend({
host: 'http://localhost:56967',
namespace: 'api'
});
I first ran into some Cors-problems, but i fixed the contentSecurityPolicy in my Ember app and enabled Cors on my Api.
When I go to the products route I can see the request to the Api gets accepted and the Api replies the Json answer. However, I'm failing to serialize the model so I can use it in my Ember App.
This is my response from the Api
[{"ProductId":1,"Name":"Product 1","Description":"Category 1"},{"ProductId":2,"Name":"Product 2","Description":"Category 2"},{"ProductId":3,"Name":"Product 3","Description":"Category 3"}]
Ember model for a product
export default DS.Model.extend({
name : DS.attr('string'),
description: DS.attr('string')
});
Asp.net model for a product:
public class Product
{
public int ProductId { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
I know I have to serialize the Api response to make it "readable" Json for my Ember App. The question now: is it better to change the formatting on the Api? Or make a good serializer? How would I go about making the serializer? It's hard to find some decent tutorials. I tried this but that's not working:
export default DS.RESTSerializer.extend({
primaryKey: 'productId'
});
This is the error i'm getting:
Error while processing route: products No model was found for '0' Error: No model was found for '0'
EDIT
After trying the suggested serializer and some ASP.NET serializers as well I still couldn't get it to work. Today I found this project : http://jsonapi.codeplex.com/. It's a Nuget Package that helps the output of your ASP.NET API to be complient with the json.api standard. I got this working with my Ember-data in no time. Just add the correct header in your rest adapter that it looks like this:
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://localhost:57014',
namespace: 'api',
headers:{
"Accept": "application/vnd.api+json"
}
});
And in your Asp.net model just add
[JsonObject(Title="product")]
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
It will pluralize your output to this:
{
"products": [
{
"id": "1",
"name": "Product 1",
"description": "Category 1"
},
{
"id": "2",
"name": "Product 2",
"description": "Category 2"
},
{
"id": "3",
"name": "Product 3",
"description": "Category 3"
}
]
}
It's still in Alpha state but looks promising. A small note on the pluralization: it just adds the -s to your model name, something to keep in mind.
The main issue is that Asp Web API returns the following response:
[
{
"ProductId":1,
"Name":"Product 1",
"Description":"Category 1"
}
]
But Ember Data expects the server to respond with the following format instead:
{
"products": [
{
"productId": 1,
"name": "Product 1",
"description": "Category 1"
}
]
}
You could update the response from the Web API server to be in the format that Ember expects, but it's easier to create a Serializer in Ember to map the data from Asp Web API into Ember's format.
I wrote a detailed blog post that explains how to create an Ember Serializer to perform this mapping.
Be sure to read the blog post to understand what is going on in the Serializer. But as a reference, here is what I believe your serializer should look like:
App.ProductSerializer = DS.RESTSerializer.extend({
primaryKey: 'productId',
extract: function(store, primaryType, payload, id, requestType) {
if (payload.length) {
for (var i = 0; i < payload.length; i++) {
this.mapRecord(payload[i]);
}
} else {
this.mapRecord(payload);
}
payloadWithRoot = {};
payloadWithRoot[primaryType.typeKey] = payload;
this._super(store, primaryType, payloadWithRoot, id, requestType)
},
mapRecord: function(record) {
for (var property in record) {
var value = record[property];
record[property.camelize()] = value;
delete record[property];
return record;
}
},
serializeIntoHash: function(hash, type, record, options) {
var recordJSON = record.toJSON();
for (var property in recordJSON) {
var value = recordJSON[property];
hash[property.capitalize()] = value
}
}
});
Related
I am sending data from an Angular application to SpringBoot. The application creates an order and sends it to the server. My problem is that the server does not receive the correct object.
I console.log the output to be sent to the server, and looks alright:
{
"user": {
"username": "Yomerito",
"address": "Aquimerito 1234, Roswell NM 88000",
"credit": "4444-0000-0000-0001"
},
"cart": [
{
"id": 2,
"foodName": "Tacos de Barbacoa",
"price": 20,
"cuisine": "MEXICAN",
"description": "Borrego\nasado en horno de piedra ",
"isEnabled": "Y",
"imageUrl": "tinyurl\n.com/moleVerdeImage"
},
{
"id": 1,
"foodName": "Kebab",
"price": 10,
"cuisine": "ARAB",
"description": "rico Kebab",
"isEnabled": "Y",
"imageUrl": "tinyurl\n.com/moleVerdeImage"
}
],
"status": "PENDING"
}
The methods I use in Angular are
createOrder() to create the order out of client, cart, and status
onSubmit() to send the data.
onSubmit(): void {
this.purchaseService.postPurchase(this.createOrder()).subscribe({
next: (data: any) => {
let retVal = JSON.parse(JSON.stringify(data))
// let retVal = JSON.stringify(data)
if (retVal === ("OK")) {
this.router.navigate(["/purchase"])
}
},
error: err => {
console.error(err.message)
alert("onSubmit error: " + err.message);
}
});
}
createOrder(): IOrden {
this.searchService.orden = {user: this.checkoutForm.value, cart: this.searchService.cart, status: "PEND"}
}
On SpringBoot, trying to use #RequestBody or #ModelAttribute:
package com.slbootcamp.foodbox.controller;
import com.slbootcamp.foodbox.entity.Orden;
import com.slbootcamp.foodbox.entity.User;
import com.slbootcamp.foodbox.jdbc.OrdenDao;
import com.slbootcamp.foodbox.jdbc.UserDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
#CrossOrigin(origins ="*", allowedHeaders = "*")
#RestController
public class OrdenController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
OrdenDao ordenDao;
#PostMapping("/cart/placeOrden")
public int placeOrden(#RequestBody Orden orden) {
// public int placeOrden(#ModelAttribute Orden orden) {
logger.info("---------------> Orden: " + orden);
return ordenDao.placeOrden(orden);
}
}
When I use #RequestBody, the server responds with
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `com.slbootcamp.foodbox.entity.Cart` from Array value (token `JsonToken.START_ARRAY`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.slbootcamp.foodbox.entity.Cart` from Array value (token `JsonToken.START_ARRAY`)<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 117] (through reference chain: com.slbootcamp.foodbox.entity.Orden["cart"])]
and when I use #ModelAttribute, I got:
---------------> Orden: Orden(status=null, cart=null, user=null)
I searched all over the Internet twice... but couldn't get what the problem is. I am new to Springboot and Angular... which might be the start of the trouble. I would appreciate if you can please shed some light on this...
It look like you are mixing things:
Controller: Your contrller looks fine with #RequestBody this way you expecting
a post request from the Angular.
Your angular request on the other hand look odd it should be something like this:
const httpOptions = {
headers: new HttpHeaders({ 'Accept': 'application/json', 'Content-Type': 'application/json' })
};
createOrder(): IOrden {
return this.http.post("your_url",JSON.stringify(order), httpOptions);
}
The order is an Angular object defined on its own .ts file.
Before calling the service build the object:
const order = new Order(cart, id, status, ...)
I want to get CAD value from https://api.exchangeratesapi.io/latest
I have already used so many types of code, and it says that "TypeError: Cannot read property 'CAD' of undefined"
Really need your help, thank you very much.
It outputted all the Currencies if I console this code
((this.state.data as any).rates)
but when i want to get CAD currencies, it says the error
I have tried these codes :
((this.state.data as any).rates as any).CAD
(this.state.data as any)["Rates"]["CAD"];
(this.state.data as any)["Rates"].CAD;
The way I get the data is
interface IState {
data?: object | null;
isCurrency?: boolean;
Currency?: string;
Rate?: number;
}
export default class Header extends Component<{}, IState> {
service: UserService = new UserService();
state = {
isCurrency: false,
Currency: "USD",
Rate: 1,
data: [] = []
};
async componentDidMount() {
let result = await this.service.getAllCurrency();
this.setState({
data: (result as Pick<IState, keyof IState>).data
});
console.log(result);
}
}
1.4591 (Based on the latest API)
You should create a type for your data. Because it's coming from an external source, typescript cannot infer that. Then parse your JSON and cast it to that type.
// Create a type for the expernal data.
interface Data {
rates: {
[currency: string]: number
}
base: string
date: string
}
// Result of `JSON.parse()` will be `any`, since typescript can't parse it.
const untypedData = JSON.parse(`{
"rates": {
"CAD": 1.4591,
"HKD": 8.6851,
"ISK": 135.9,
"PHP": 56.797,
"DKK": 7.4648
},
"base": "EUR",
"date": "2019-07-25"
}`)
// Cast the untyped JSON to the type you expect it to be.
const data: Data = untypedData
// Use the data according to it's type.
alert(data.rates.CAD)
Working demo on typescript playground
With the help of the forum I was able to get my httpclient observable mapping issue sorted with this syntax;
this._http.get<DomainMetaData>(serviceURL);
which works great! However, I have a json response coming back from the server which is nested and wonder if I can use the same syntax as I'm currently using or if I need to now manually .map the response into my classes?
Based on posts I've seen here on SO I've created two classes to represent the nested structure of the response JSON (see below).
The function call...
getDomainMetaData(domain): Observable<DomainMetaData> {
let serviceURL = "http://localhost:3000/selectdomains?domain=" + domain;
return this._http.get<DomainMetaData>(serviceURL);
}
The classes...
export class DomainMetaDataAttr {
constructor(public name: string,
public value: string) {
}
}
export class DomainMetaData {
constructor(public name: string,
public attributes: DomainMetaDataAttr[]) {
}
}
An example of the json...
//DomainMetaData
// {
// "ResponseMetadata": {
// "RequestId": "11f000bf-0dff-8a2a-31ff-8631a9f25b5b",
// "BoxUsage": "0.0008183545"
// },
// "Items": [
// {
// "Name": "2",
// "Attributes": [
// {
// "Name": "Document Date",
// "Value": "22/03/13"
// },
// {
// "Name": "Document Ref",
// "Value": "Doc test"
// }
// ]
// },
I love the neatness and simplicity of my current solution but I appreciate I may now have to change my code!
Many Thanks.
If I understand correctly you want to know how to use the JSON response from an HttpClient call.
I currently approach it like this:
// x.service.ts
getData() {
return this.http.get(URL);
}
// x.component.ts
this.service.getData().subscribe(res => {
if (res['data']) {
const data = res['data'];
// do whatever with the data
}
});
With the above approach you can run whatever methods / filters you want on the JSON e.g. map over the array and pull data out / mutate it, etc. Not sure if it's necessary to create additional classes to deal with the nested JSON data.
Oops! The code I posted actually works, I just wasn't referencing the results in the attributes array correctly.
Thanks for taking the time to look at this.
I'm working on an Angular app that contains a list of (financial) Trades that a user can add to. This has been going well, and I'm trying to switch over from a static list provided by a service to trying to fetch the data from a local Node.js server. I'm using an observer to asynchronously fetch the list of trades.
I've been following along with Angular's HTTP tutorial and the associated plunker. However, even though I can see the data coming from the server, I'm having trouble using the .subscribe() method to get a useful set of data out of it.
Here's my service which connects to the node server:
#Injectable()
export class TradeService {
private url = '...'; // URL to web API
tradeArray: Trade[] = [];
constructor(private http: Http) { }
//-----------GETTERS---------------//
getTradeObservable(): Observable<Trade> {
return this.http.get(this.url)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
console.log("body:" + body);
console.log("Entire Body.trades: " + body.trades);
return body.trades;
}
getTrades(): any {
this.getTradeObservable()
.subscribe(
trade => this.tradeArray.push(trade));
return this.tradeArray;
}
And here are the relevant portions the node server itself:
var TRADES = { "trades": [
{"id": 0, "cust": "Ben", "hasSub": true,
"subcust": "Rigby", "type": "s", "security": "001", "ticket": "99"},
...
{"id": 9, "cust": "Uber Bank", "hasSub": true,
"subcust": "Lil Bank", "type": "p", "security": "456", "ticket": "56"}
]};
////////////Get Requests/////////////////
//this route returns all data in JSON format
app.get('/', function(req, res) {
res.send(JSON.stringify(TRADES));
});
And the expected output from getTrades:
[
{id: 0, cust: "Ben", hasSub: true,
subCust: "Rigby", type: "s", security: '001', ticket: '99'},
...
{id: 9, cust: "Uber Bank", hasSub: true,
subCust: "Lil' Bank", type: "p", security: '456', ticket: '56'},
];
And one of the places the service is injected into and called:
export class SubmittedComponent {
constructor(private tradeService: TradeService) { }
//initally show all trades
rows = this.tradeService.getTrades();
...
I can see in the browser console that 'entire body.trades' is a full list of the data I want, but it seems subscribe is not pushing them into tradeArray, which ends up undefined.
Thank you for your time.
So I suppose that you are calling getTrades() from one of your components. If this is the case, this is what will happen:
The request will be sent
The request will be processed in the background asynchronously
The method will not wait for the request to be resolved and will return the current value of tradeArray, which is []
To avoid this, you could refactor you components so that they invoke the getTradeObservable() method an subscribe to the returned Observable.
UPDATE: Another option would be to refactor you service to use a Subject', and expose it to your components through anObservable`.
UPDATE: Assuming that you have the following definition for Trade
export interface Trade{
id: number;
cust: string;
hasSub: boolean;
subCust: string;
type: string;s
security: string;
ticket: string;
}
You could try the following approach
class TestComponent {
data: Trade[];
// inject service in component
getData(){
this.service.getTradesObservable().subscribe(data => this.data = data);
}
}
And change the definition of getTradesObservable to :
getTradeObservable(): Observable<Trade[]> {
return this.http.get(this.url)
.map(this.extractData)
.catch(this.handleError);
}
Speaking just about this portion of the code:
getTrades(): any {
this.getTradeObservable()
.subscribe(
trade => this.tradeArray.push(trade));
return this.tradeArray;
}
since getTradeObservable is asynchronous this line: return this.tradeArray; will (maybe) execute before the observable is resolved, you should remove getTrades method from your service and instead get a hold of the observable returned by getTradeObservable in your component and rather than expecting the whole thing to return the value you want, you should assign that value in the subscription like this:
#Component({
providers:[TradeService ]
})
export class myComponent{
trades:Trade[];
constructor(tradeService:TradeService){
tradeService.getTradeObservable().subscribe(tradeRes=>this.trades=tradeRes as Trade[]);
}
}
This is a question about molding some API data to fit some needs. I've heard it called "munging." I guess the heart of if is really re-formatting some JSON, but It would be ideal to do it the Ember data way...
I'm getting this data in an Emberjs setting - but it shouldn't really matter - ajax, ic-ajax, fetch, etc... I'm getting some data:
...
model: function() {
var libraryData = ajax({
url: endPoint,
type: 'GET',
dataType: 'jsonp'
});
// or most likely the ember-data way
// this.store.findAll(...
console.log(libraryData);
return libraryData;
}
...
The URL is getting me something like this:
var widgetResults = {
"settings": {
"amazonchoice":null,
"show":{
"showCovers":null,
"showAuthors":null
},
"style":null,
"domain":"www.librarything.com",
"textsnippets":{
"by":"by",
"Tagged":"Tagged","readreview":"read review","stars":"stars"
}
},
"books":{
"116429012":{
"book_id":"116429012",
"title":"The Book of Three (The Chronicles of Prydain Book 1)",
"author_lf":"Alexander, Lloyd",
"author_fl":"Lloyd Alexander",
// ...
The promise that is actually returned is slightly different.
My goal is to get to those books and iterate over them - but in my case it wants an array. that #each loops over must be an Array. You passed {settings: [object Object], books: [object Object]} - which makes sense.
In and ideal API the endpoint would be / http:/site.com/api/v2/books
and retrieve the data in this format:
{
"book_id":"116428944",
"title":"The Phantom Tollbooth",
"author_lf":"Juster, Norton",
"author_fl":"Norton Juster",
...
},
{
"book_id":"116428944",
"title":"The Phantom Tollbooth",
"author_lf":"Juster, Norton",
"author_fl":"Norton Juster",
...
},
{
... etc.
I would expect to just drill down with dot notation, or to use some findAll() but I'm just shooting in the dark. Librarything in specific is almost done with their new API - but suggest that I should be able to loop through this data and reformat it in an ember friendly way. I have just looped through and returned an array in this codepen - but haven't had luck porting it... something about the returned promise is mysterious to me.
How should I go about this? am I pointed in the wrong direction?
I've tried using the RESTAdapter - but didn't have much luck dealing with more unconventional endpoints.
Custom Adapters / Serializers ?
this article just appeared: "Fit any backend into ember with custom adapters and serializers
Full url with endpoint in question
model (just title to test)
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string')
});
route ( per #Artych )
export default Ember.Route.extend({
model() {
$.ajax({
url: endPoint,
type: 'GET',
dataType: 'jsonp'
}).then((widgetResults) => {
// modify payload to RESTAdapter
var booksObj = widgetResults.books;
var booksArray = Object.keys(booksObj).map((element) => {
var book = booksObj[element];
book.id = book.book_id;
delete book.book_id;
return book;
});
console.log(booksArray);
this.store.pushPayload({books: booksArray});
});
return this.store.peekAll('book');
}
});
template
{{#each model as |book|}}
<article>
<h1>{{book.title}}</h1>
</article>
{{/each}}
There is straightforward solution to process your payload in model():
Define book model.
Process your payload in model() hook:
model() {
$.ajax({
url: endPoint,
type: 'GET',
dataType: 'jsonp'
}).then((widgetResults) => {
// modify payload to RESTAdapter
var booksObj = widgetResults.books;
var booksArray = Object.keys(booksObj).map((element) => {
var book = booksObj[element];
book.id = book.book_id;
delete book.book_id;
return book;
});
this.store.pushPayload({books: booksArray});
});
return this.store.peekAll('book');
}
Iterate model in controller or template as usual.
Working jsbin:
ember 1.13
ember 2.0
You want a custom serializer to translate the data from that format into JSON-API. JSON-API is an extremely well thought-out structure, so well in fact that ember-data has adopted it as the default format used internally. Some of the benefits are that it defines a structure for objects themselves, separating attributes from relationships; a means for embedding or including associated resources; defines a place for errors and other metadata.
In short, for whatever you're trying to do, JSON-API probably has already done a lot of the decision-making for you. And, by subclassing from DS.JSONSerializer, you'll be mapping right into the format that ember-data needs.
To do this, you create a custom adapter using ember generate serializer books:
// app/serializers/book.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
// payload will contain your example object
// You should return a JSON-API document
const doc = {};
// ...
return doc;
}
});
For your example data, the output of the normalization should look something like this:
{
"data": [
{
"type": "books",
"id": 116429012,
"attributes": {
"title": "The Book of Three (The Chronicles of Prydain Book 1)",
"author_lf": "Alexander, Lloyd",
"author_fl": "Lloyd Alexander"
}
},
{
"type": "books",
"id": 1234,
"attributes": {
}
}
],
"meta": {
"settings": {
"amazonchoice":null,
"show":{
"showCovers":null,
"showAuthors":null
},
"style":null,
"domain":"www.librarything.com",
"textsnippets":{
"by":"by",
"Tagged":"Tagged","readreview":"read review","stars":"stars"
}
}
}
};
Then do
this.get('store').findAll('books').then((books) => {
const meta = books.get('meta');
console.log(meta.settings.domain);
books.forEach((book) => {
console.log(book.get('title'));
});
});
Code is not tested, but hopefully it gets you started.
Define settings and book models. Arrange for the API to respond to the endpoint /books returning data in the format:
{
settings: { ... },
books: [
{
id: xxx,
...
}
]
}
Retrieve the data in the model hook with this.store.findAll('book').
Iterate over the books in your template with {{#each model as |book|}}.