I've seen that this problem is fairly common across Angular 2, but I haven't seen anything that helps with my particular situation.
Specifically, I'm creating an app that's pulling data from an API (which requires an API key), where the results are specifically in XML format. What I'm trying to do is call the API when a user enters search parameters, have the API return the results, and then eventually display the results on the same screen.
However, I keep encountering the same "Unexpected token <" error, where the source of the error is on the URL of the API itself. I have my suspicion that the problem is stemming from the fact that I'm attempting to return the body as JSON, but I've never experienced this problem before so I was hoping the community could help!
Here's my Service and Component code below:
search-bar.component.ts
import { Component, Output, Input, EventEmitter } from '#angular/core';
import { NgForm } from '#angular/forms';
import { NgModel } from '#angular/forms';
import { Observable } from 'rxjs/Observable';
import { Search } from './search';
import { ZillowSearchService } from './zillow-search.service';
#Component({
moduleId: module.id,
selector: 'search-bar',
templateUrl: `search-bar.component.html`,
styles: [
`input {
width: 50%;
margin-left: 25%;
margin-top: 15%;
color: black;
height: 40px;
}
button {
border-radius: 5px;
margin-top: 5%;
margin-left: 45%;
}
`
]
})
export class SearchBarComponent {
getSearchResults: string;
model = new Search("");
constructor( private zillowSearchService: ZillowSearchService) {}
private sub: any;
public state: string = "" ;
data: string;
onSubmit(){
this.zillowSearchService.searchZillow("New Mexico")
.subscribe(
data => console.log(data),
error => console.log("Error: " + error)
);
}
}
zillow-search.service.ts:
import { Inject, Injectable } from '#angular/core';
import { Http, Headers, Response, Jsonp } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import '../rxjs-operators';
import { AppConfig } from './app-config';
import { Search } from './search';
let api = AppConfig.baseUrls.api;
let url = AppConfig.baseUrls.base;
#Injectable()
export class ZillowSearchService {
public state: string;
constructor ( #Inject(Jsonp) private jsonp: Jsonp) {
//this.http = http;
}
protected results: any;
searchZillow(statevar: any) {
console.log("Before" + statevar);
var queryString = url + `?zws-id=${api}&state=${statevar}&callback=JSONP_CALLBACK`;
return this.jsonp
.get(queryString, new Headers({ "Content-type":
"application/xml", "Accept": "application/xml",
"Access-Control-Allow-Origin": "*" }))
.map((res: Response) => res.json())
//.map(res => console.log("TEST"));
//.map((res) => {
//return
//});;
}
}
For some additional details, looking into the Console and Network, I can see that the "type" is listed as script and the initiator is http.umd.js, where it points me to BrowserJsonp.send (if this helps provider any context at all).
Let me know if you'd like for me to include any additional code snippets, and I really appreciate the help and guidance!
You mentioned the results are in XML format, but I see this code at the bottom of your service:
return this.jsonp
.get(queryString, new Headers({ "Content-type":
"application/xml", "Accept": "application/xml",
"Access-Control-Allow-Origin": "*" }))
.map((res: Response) => res.json())
//.map(res => console.log("TEST"));
//.map((res) => {
//return
//});;
}
I'm not certain that you are able to map the response to json that way -- I think it's actually expecting JSON there from a literal that it would map into a typed object you could use.
So the problem is that you are trying to parse xml as json -- hence the error saying that it can't parse the first "<", as that's not valid json.
You will probably need some sort of xml parser to resolve this -- I would look for a 3rd party library in this case :)
EDIT: To be clear, I think the line:
.map((res: Response) => res.json())
is causing the issue, as it's saying take this input json response and convert it to a usable javascript object -- but it can't parse it, as the response is in XML format.
Related
I am working on adding a promotions feature to an Angular 11 e-commerce app.
I have a service that makes a get request and reads a JSON containing the campaign's data.
The service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Campaign } from '../models/campaign';
#Injectable({
providedIn: 'root'
})
export class PromoProductsService {
public apiURL: string;
public promo$: Observable<Campaign>;
constructor(private http: HttpClient) {
this.apiURL = `${environment.bffApi}/offer-service`;
this.promo$ = this.http.get<Campaign>(`${this.apiURL}/campaign`).pipe(shareReplay());
}
}
NOTE: the campaign JSON contains other (arrays of) "things" the just banners and I would prefer to make a single GET request for all of them. This is why I use promo$: Observable<Campaign>.
In the category component I have:
public getCampaignBanners() {
this.promoProductsService.promo$.pipe(takeUntil(this.destroyed$)).subscribe((data: any) => {
this.campaignData = data;
this.campaignBanners = this.campaignData.campaign.banners;
if (this.campaignBanners && this.campaignBanners.length > 0) {
this.displayCampaignBanners();
}
});
}
public displayCampaignBanners(){
this.productList$.pipe(takeUntil(this.destroyed$)).subscribe((productList) => {
this.campaignBanner = this.campaignBanners.find((banner: any) => {
return banner.category.toLowerCase() == productLists.category_name.toLowerCase();
});
});
}
The problem
Unless I refresh the page after accssing a product list, productList$ is undefined and the the line this.productList$.pipe(takeUntil(this.destroyed$)).subscribe((productList) => { throws a Cannot read properties of undefined (reading 'pipe') error.
Questions:
What am I doing wrong?
What is the easiest way to fix this issue?
When you call Backend with Angular, you need to subscribe to the observable for get data.
I dont really understand why you create an Observable<Campaign>
this.http.get<Campaign>(this.apiURL + "/campaign").subscribe(response => {
console.log(response);
})
Or something like this if you want to have a method
public getCampaignBanners() :Observable<Campaign> {
return this.http.get<Campaign>(this.apiURL + "/campaign");
}
// call it after
this.promoProductService.getCampaignBanners().subscribe( response => {
// ...
})
If you want to handle some error you can do this.
this.http.get<Campaign>(this.apiURL + "/campaign").subscribe(response => {
console.log(response);
},
(error) => {
console.error(error);
switch(error.status){
case 404:
// 404 code
break;
case 500:
// 500 code
break;
default :
break
}
})
Hope it will help you, maybe it's not the main problem here, did you try to call your API with POSTMAN or CURL ?
(Sorry for bad english , not native :) )
EDIT
promo: Observable:<any>;
getPromo(){
if(this.promo == undefined){
/* Do API CALL and Set promo with Data*/
}else{
return this.promo;
}
}
I have an component where i am adding a new object called customer by calling the api like this:
public onAdd(): void {
this.myCustomer = this.customerForm.value;
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
this.callSuccessMethod();
},
(error) => { // If POST is failed
this.callFailureMethod();
},
);
}
Service file:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import {ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer);
}
}
As shown in component code, i have already subscribed the api call like this:
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
.....
},
(error) => { // If POST is failed
...
},
);
But,I want to subscribe the results in another component, I have tried like this:
public getAddedCustomer() {
this.myService.addCustomer().subscribe(
(data:ICustomer) => {
this.addedCustomer.id = data.id; <======
}
);
}
I am getting this lint error: Expected 1 arguments, but got 0 since i am not passing any parameter.
What is the right approach to subscribe the api call in other components? after POST operation.
Because i want to get added object id for other functionality.
Well it totally depends on the design of your application and the relation between components. You can use Subjects for multicasting the data to multiple subscribers.
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import { ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
private latestAddedCustomer = new Subject();
public latestAddedCustomer$ = this.latestAddedCustomer.asObservable()
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer).pipe(map((data) => this.latestAddedCustomer.next(data)));
}
}
and subscribing to the subject as follows
this.latestAddedCustomer$.subscribe()
should get you the latest added customer details. Even though i would not do this the way its written. I would basically write a seperate service to share the data between the components or would write a cache service if its used across the application. But the idea here is to use the concept of Subjects. You can read more about it Here
I have a contentful service like so..
import { Injectable } from '#angular/core';
import { createClient, Entry } from 'contentful';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
const CONFIG = {
space: '<spaceid>',
accessToken: '<accesstoken>',
contentTypeIds: {
programItems: 'programItem'
}
};
#Injectable()
export class ContentfulService {
private cdaClient = createClient({
space: CONFIG.space,
accessToken: CONFIG.accessToken
});
public weekNumber = new BehaviorSubject<any>(1);
constructor() { }
// Get all the program items
getProgramItems(query?: object): Promise<Entry<any>[]> {
return this.cdaClient.getEntries(Object.assign({
content_type: CONFIG.contentTypeIds.programItems
}, query))
.then(res => res.items);
}
}
but I only want to bring in the programItems sys.ids in the contentful documentation.. you can modify api calls and return only certain values like this modify api calls
https://cdn.contentful.com/spaces/<space_id>/entries/
?select=fields.productName,fields.price
&content_type=<content_type_id>
but Im not sure how I would implement the same thing, the way they do angular calls.. I could just do a http request but I would prefer to keep it the same way as I have done above
any help would be appreciated
You add a select property to your getEntries call.
// Get all the program items
getProgramItems(query?: object): Promise<Entry<any>[]> {
return this.cdaClient.getEntries(Object.assign({
content_type: CONFIG.contentTypeIds.programItems,
select: 'sys.id'
}, query))
.then(res => res.items);
}
You can read the full documentation, including javascript snippets, here: https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/select-operator/query-entries/console/js
I have ts file and I would like to create POST method inside component. I try in the way shown below unfortunately without positive results.
this.http.post("http://localhost:8000/", JSON.stringify({ body: 'String' }), {headers:{'Content-Type': 'application/json'}})
UPDATE
I have a little modified my backend logic and I realized that I don't need to send body in POST method. I can send my data in URL parameter. I would like to send GET request and assign received data to object object.sth which needs object of Isth[] type. At this moment my code looks in the way shown below. However console.log("data: "+object.sth); after assignment returns data: undefined.
this.http.get("http://localhost:8000/path?sth=exampleurl", headers).map(res => res.json()).subscribe(data => this.data = data);
object.sth = this.data;
this.http.post(url, JSON.stringify(YOURSTRING), {headers:{'Content-Type': 'application/json'}})
It should work for request. (Ur first question)
in top of ur component
import { Headers, Http } from "#angular/http";
ur component:
constructor(private http: Http) {}
yourRequest() {
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded' })
this.http.post(url, JSON.stringify(YOURSTRING), headers).subscribe(res=>console.log(res));
}
I'm going to update the answer with more complete syntax. Hopefully that can get you something running without an error.
import { Http } from '#angular/http';
import { Component } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Component({
selector: 'my-app',
template: `<h1>Hello World</h1>`,
styleUrls: ['./app.component.css']
})
export class AppComponent {
private headers: Headers;
private options: RequestOptions;
constructor(private http: Http) {
this.headers = new Headers({ 'Content-Type': 'application/x-www-form- urlencoded' });
this.options = new RequestOptions({ headers: this.headers });
}
ngOnInit() {
this.doPost().subscribe(response => {
console.log(response);
});
}
doPost() {
return this.http.post("http://localhost:8000/sth", this.options).map(res => res.json());
}
}
Original:
I think what you're missing is the subscribe. Observables won't execute unless you subscribe to them.
this.http.post("http://localhost:8000/", JSON.stringify({ body: 'String' }), {headers:{'Content-Type': 'application/json'}}).subscribe(res=>console.log(res));
Just for the record, it is usually better to put your http calls in a service, not inside the component.
First of all, I am very new to Angular2 (or any other version actually) and I have followed several tutorials to get me started but I'm now in a dead end and I don't know how to proceed.
Here is the background: I am accessing a third party web API through a POST request (I did that in a service) and it returns HTML markup of the control I need to render on my page, so I made a component of it. It works fine (I had to create a custom pipe to work around the DOM sanitization though).
And here's my issue: in the markup I'm receiving from the web API there's JavaScript stuff to initialize the control that is supposed to execute as soon as it is on the page (it does in every other language I used this control in, PHP, Java, JavaScript, ASP.NET, etc) but for some reason, using Angular2 I can see the script in the DOM, properly inserted at the end of the markup but it does not execute so my control does not work.
Here is my component code:
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { MyService } from './my.service'
#Component({
selector: 'mycontrol',
template: `<div style="width:1200px; height:1000px;" [innerHtml]="htmlContent | keepHtml"></div>`,
styleUrls: ['app/control-min.css'],
encapsulation: ViewEncapsulation.None
})
export class MyComponent implements OnInit {
htmlContent: any;
constructor(private myService: MyService) {
}
ngOnInit(): void {
this.myService.getControlMarkup().subscribe(
response => this.htmlContent = response["HtmlContent"],
error => this.htmlContent = <any>error
);
}
}
And here is my service code:
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
#Injectable()
export class MyService {
headers: Headers;
options: RequestOptions;
constructor(private http: Http) {
this.headers = new Headers({ 'Content-Type': 'application/json' });
this.options = new RequestOptions({ headers: this.headers });
}
getControlMarkup() {
let controlConfig = {
SessionId: "mySessionId",
ControlId: "MyControl1"
};
let body = JSON.stringify(controlConfig);
return this.http
.post('http://localhost:62968/api/GetControl', body, this.options)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || {};
}
private handleError(error: any) {
let errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg);
return Observable.throw(errMsg);
}
}
Any idea how I can make this work?