Chaining HTTP call in Angular - javascript

I'm trying to get Google Places with their API and get a response body that contains a "photo_reference" property. With this property, I want to get the Google Place Image and call a second API.
I have an input field that can search for places. By entering a string and clicking on the search button, this method will get called:
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
private lng: number;
private lat: number;
private place_id: string;
private listOfPoi: GoogleLocation[];
constructor(
private gs: GoogleService,
private ds: DataService,
private router: Router
) { }
ngOnInit(): void {
this.ds.getLocation().subscribe(loc => this.listOfPoi = loc);
}
searchPoi(location: string): void {
this.router.navigate(['/poi']);
this.gs.getPoi(location).pipe(
map((response) => response)
).subscribe((data: GoogleResponse) => {
if(data.status === "OK") {
this.ds.setLocation(data.results); //data.results is an array of locations
}
});
}
In my other component, I try to get the locations and map them into a new object with the property imgUrl.
I want to save them in an array.
export class SightseeingViewComponent implements OnInit {
listOfPoi: LocationWithImage[];
constructor(private ds: DataService, private gs: GoogleService) {}
ngOnInit(): void {
this.ds.getLocation().subscribe(
(loc) =>
(this.listOfPoi = loc.map((loc) => {
return {
...loc,
imgUrl:
this.gs.getImage(loc.photos[0].photo_reference).toString(),
};
}))
);
}
}
But my problem is the img src="[object Object]" it should be the string!
My problem is this line this.gs.getImage(loc.photos[0].photo_reference).toString()
I tried to replace this line with a random hardcoded img Url and it works. The image are shown but I cannot retrieve the URL as string with this method.
Of course toString() is not working but I have no clue what else I can do?
This is the google location model:
//The model of a google location object from the API response (getLocationInfo)
export class GoogleResponse {
constructor(
public results: GoogleLocation[],
public status: string
) {}
}
export class GoogleLocation {
constructor(
public formatted_address: string,
public geometry: Locations,
public icon: string,
public name: string,
public photos: PhotoInfo[],
public place_id: string,
public reference: string,
public type: string[]
) {}
}
export interface LocationWithImage extends GoogleLocation {
imgUrl?: string;
}
...
EDIT:
This is my service:
With this, I make a call to my backend which is fetching the photo_reference from Google via its API
#Injectable({
providedIn: 'root',
})
export class GoogleService {
url: string = 'http://localhost:3000/';
constructor(private http: HttpClient) {}
getLocationInfo(location: string): Observable<GoogleResponse> {
console.log(location);
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
return this.http.post<GoogleResponse>(
this.url + 'googleplace/',
{ name: location },
{ headers: headers }
);
}
getPoi(location: string): Observable<GoogleResponse> {
console.log(location);
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
return this.http.post<GoogleResponse>(
this.url + 'googlepoi/',
{ name: location },
{ headers: headers }
);
}
getImage(photoRef: string): Observable<string> {
console.log(photoRef);
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
return this.http.post<string>(
this.url + 'googlephoto/',
{ photoRef: photoRef },
{ headers: headers }
);
}
}
Here is my other service which is needed to send and retrieve data from other components
#Injectable({
providedIn: 'root'
})
export class DataService {
private googleLocationSource = new Subject<GoogleLocation[]>();
constructor() { }
public getLocation(): Observable<GoogleLocation[]> {
return this.googleLocationSource.asObservable();
}
public setLocation(gl: GoogleLocation[]) {
return this.googleLocationSource.next(gl);
}

My problem is this line this.gs.getImage(loc.photos[0].photo_reference).toString()
Yes, calling toString() on an observable isn't going to work ! :-)
You need to subscribe to that observable to receive its result. There are a few "Higher Order Mapping Operators" that can do this for you, so you don't have to deal with nested subscriptions. In this case, we can use switchMap.
However, it's a little more complex because you want to make a call for each element in the returned array. We can map each location to an observable call to get the image and use forkJoin to create a single observable that emits an array containing the results of the individual observables:
this.ds.getLocation().pipe(
switchMap(locations => forkJoin(
locations.map(loc => this.gs.getImage(loc.photos[0].photo_reference))
)
.pipe(
map(imgUrls => imgUrls.map(
(imgUrl, i) => ({ ...locations[i], imgUrl })
))
))
)
.subscribe(
locations => this.listOfPoi = locations
);
The flow here is:
switchMap receives the locations and subscribes to observable created by forkJoin
forkJoin creates an observable that will emit an array of the results (image urls) from all the individual getImage() calls.
map receives the array of image urls and maps it to an array of locations that include the image url
subscribe simply receives the final array

Related

How to display data from client service in ngx-wheel Angular

I want to display the ngx-wheel using api but I'm having trouble displaying the data.
Here my Service :
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class RestServices {
restEndpoint:string = 'https://gorest.co.in/public/v2/users'
constructor(
private httpClient: HttpClient
) { }
async getServiceId() {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
}
return this.httpClient.get<any[]>(this.restEndpoint, httpOptions)
}
Here my Component :
private subscription: Subscription | undefined;
items: any = []
ngOnInit(): void {
this.subscription = this._restService.getServices()
.subscribe((res:any)=>{
let item = res
this.items = item.map((v:any) => ({
text: v.name,
id: v.id,
textFillStyle: "white",
textFontSize: "16"
}));
})
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
Here for html
<ngx-wheel #wheel [width]='350' [height]='350' [spinDuration]='8' [disableSpinOnClick]='true' [items]='items'
[innerRadius]='10' [spinAmount]='10' [textOrientation]='textOrientation' [textAlignment]='textAlignment'
pointerStrokeColor='black' pointerFillColor='white' [idToLandOn]='idToLandOn' (onSpinStart)='before()'
(onSpinComplete)='after()'>
I hope to find the answer here. Thank you
First, you don't need await, async and ,toPromise()... remove them and simply return
return this.httpClient.get<any[]>(this.restEndpoint, httpOptions);
inside your component you should use your constructor only for simple data initialization: if you have to consume a rest api it is a better approach to move that piece of code inside the ngOnInit method:
items: any[] = []
constructor(private restService: RestService){}//dependency injection
ngOnInit(): void {
this.restService.getServiceId().subscribe(response => {
console.log('response success: ', response);
this.items = response; //this may change a little based on your api
console.log('items: ', this.items);
}, errorLog => {
console.log('response error: ', errorLog)
});
}
The above solution is valid, you can enrich it by adding a *ngIf="isLoaded" on your html element and set to true the isLoaded INSIDE subscribe method. but if you prefer you can do the following in the component.ts
items$: Observable<any> = EMPTY;
constructor(private restService: RestService){}//dependency injection
ngOnInit(): void {
this.items$ = this.restService.getServiceId();
}
then, in your html it would change to the following:
<ngx-wheel #wheel *ngIf="items$ | async as items" [width]='350' [height]='350' [spinDuration]='8' [disableSpinOnClick]='true' [items]='items'
[innerRadius]='10' [spinAmount]='10' [textOrientation]='textOrientation' [textAlignment]='textAlignment'
pointerStrokeColor='black' pointerFillColor='white' [idToLandOn]='idToLandOn' (onSpinStart)='before()'
(onSpinComplete)='after()'>

Argument of type 'Observable<Pet>&Observable<HttpResponse<Pet>>&Observable<HttpEvent<Pet>>' is not assignable to 'Observable<HttpResponse<Pet>>'

I am trying to connect the generated openapi-generator angular code with the JHipster CRUD views. I was trying to edit and tailor them together for the Pet entity, however, I get the following error:
"Argument of type 'Observable & Observable<HttpResponse> &
Observable<HttpEvent>' is not assignable to parameter of type
'Observable<HttpResponse>'."
JHipster generates entities having models, services, and CRUD operations. The code generated by the OpenAPI generator has just service and model. I am trying to use the model and service from the OpenAPI gen in JHipster template. I have no idea what to change to remove the error.
The code from the pet-update-component.ts, where I have problems with petService.updatePet(pet) and petService.addPet(pet):
import { Component, OnInit } from '#angular/core';
import { HttpResponse } from '#angular/common/http';
import { FormBuilder, Validators } from '#angular/forms';
import { ActivatedRoute } from '#angular/router';
import { Observable } from 'rxjs';
import { finalize, map } from 'rxjs/operators';
import { Pet } from '../pet.model';
//import { PetService } from '../service/pet.service';
import { PetService } from 'build/openapi/api/pet.service';
import { ICategory } from 'app/entities/category/category.model';
import { CategoryService } from 'app/entities/category/service/category.service';
import { PetStatus } from 'app/entities/enumerations/pet-status.model';
#Component({
selector: 'jhi-pet-update',
templateUrl: './pet-update.component.html',
})
export class PetUpdateComponent implements OnInit {
isSaving = false;
petStatusValues = Object.keys(PetStatus);
categoriesSharedCollection: ICategory[] = [];
editForm = this.fb.group({
id: [],
petId: [],
name: [null, [Validators.required]],
petStatus: [],
category: [],
});
constructor(
protected petService: PetService,
protected categoryService: CategoryService,
protected activatedRoute: ActivatedRoute,
protected fb: FormBuilder
) {}
ngOnInit(): void {
this.activatedRoute.data.subscribe(({ pet }) => {
this.updateForm(pet);
this.loadRelationshipsOptions();
});
}
previousState(): void {
window.history.back();
}
save(): void {
this.isSaving = true;
const pet = this.createFromForm();
if (pet.id !== undefined) {
this.subscribeToSaveResponse(this.petService.updatePet(pet));
} else {
this.subscribeToSaveResponse(this.petService.addPet(pet));
}
}
trackCategoryById(index: number, item: ICategory): number {
return item.id!;
}
protected subscribeToSaveResponse(result: Observable<HttpResponse<Pet>>): void {
result.pipe(finalize(() => this.onSaveFinalize())).subscribe({
next: () => this.onSaveSuccess(),
error: () => this.onSaveError(),
});
}
protected onSaveSuccess(): void {
this.previousState();
}
protected onSaveError(): void {
// Api for inheritance.
}
protected onSaveFinalize(): void {
this.isSaving = false;
}
protected updateForm(pet: Pet): void {
this.editForm.patchValue({
id: pet.id,
petId: pet.petId,
name: pet.name,
petStatus: pet.petStatus,
category: pet.category,
});
this.categoriesSharedCollection = this.categoryService.addCategoryToCollectionIfMissing(this.categoriesSharedCollection, pet.category);
}
protected loadRelationshipsOptions(): void {
this.categoryService
.query()
.pipe(map((res: HttpResponse<ICategory[]>) => res.body ?? []))
.pipe(
map((categories: ICategory[]) =>
this.categoryService.addCategoryToCollectionIfMissing(categories, this.editForm.get('category')!.value)
)
)
.subscribe((categories: ICategory[]) => (this.categoriesSharedCollection = categories));
}
protected createFromForm(): Pet {
return {
...new Pet(),
id: this.editForm.get(['id'])!.value,
petId: this.editForm.get(['petId'])!.value,
name: this.editForm.get(['name'])!.value,
petStatus: this.editForm.get(['petStatus'])!.value,
category: this.editForm.get(['category'])!.value,
};
}
}
Here is the code for pet.service:
/**
* Update an existing pet
* Update an existing pet by Id
* #param body Update an existent pet in the store
* #param observe set whether or not to return the data Observable as the body, response, or events. defaults to returning the body.
* #param reportProgress flag to report request and response progress.
*/
public updatePet(body: Pet, observe?: 'body', reportProgress?: boolean): Observable<Pet>;
public updatePet(body: Pet, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Pet>>;
public updatePet(body: Pet, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Pet>>;
public updatePet(body: Pet, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
if (body === null || body === undefined) {
throw new Error('Required parameter body was null or undefined when calling updatePet.');
}
let headers = this.defaultHeaders;
// authentication (petstore_auth) required
if (this.configuration.accessToken) {
const accessToken = typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
let httpHeaderAccepts: string[] = [
'application/xml',
'application/json'
];
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected != undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [
'application/json',
'application/xml',
'application/x-www-form-urlencoded'
];
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected != undefined) {
headers = headers.set('Content-Type', httpContentTypeSelected);
}
return this.httpClient.request<Pet>('put',`${this.basePath}/pet`,
{
body: body,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
And finally pet.ts:
/**
* Swagger Petstore - OpenAPI 3.0
* This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! You can now help us improve the API whether it's by making changes to the definition itself or the code. That way, with time, we can improve the API in general, and expose some of the new features in OAS3. Some useful links: - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
*
* OpenAPI spec version: 1.0.11
* Contact: apiteam#swagger.io
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { Category } from './category';
import { Tag } from './tag';
export interface Pet {
id?: number;
name: string;
category?: Category;
photoUrls: Array<string>;
tags?: Array<Tag>;
/**
* pet status in the store
*/
status?: Pet.StatusEnum;
}
export namespace Pet {
export type StatusEnum = 'available' | 'pending' | 'sold';
export const StatusEnum = {
Available: 'available' as StatusEnum,
Pending: 'pending' as StatusEnum,
Sold: 'sold' as StatusEnum
};
}
If you have any idea on what I should change, what exactly is the problem, or in what way I should approach this, would be super grateful. Thanks!
Okay, so what I did was commenting out methods declarations with Observable<Pet'> and "Observable<HttpEvent<Pet'>>". I have different problems now but at least those are not highlighted in red anymore.
/**
* Update an existing pet
* Update an existing pet by Id
* #param body Update an existent pet in the store
* #param observe set whether or not to return the data Observable as the body, response, or events. defaults to returning the body.
* #param reportProgress flag to report request and response progress.
*/
//public updatePet(body: Pet, observe?: 'body', reportProgress?: boolean): Observable<Pet>;
public updatePet(body: Pet, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Pet>>;
//public updatePet(body: Pet, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Pet>>;
public updatePet(body: Pet, observe: any = 'body', reportProgress: boolean = false ): Observable<any>

Pass value to extends plugin ng2 smart table

I've checked the document and source code for pagination implementation (advanced-example-server.component.ts).
And found that the ServerDataSource it used had only implemented pagination via HTTP GET (_sort, _limit, _page, etc parameters expose in URL)..... as my current project worked on required to use POST to send front-end parameters to back-end Restful APIs,
using extends to HTTP post call implement, I don't know how to add the extra parameters in pagination request. I Need To pass the request_server to extendsplugin.ts
import { Observable } from 'rxjs/internal/Observable';
import { ServerDataSource } from 'ng2-smart-table';
export class PostServerDataSource extends ServerDataSource {
protected requestElements(): Observable<any> {
let httpParams = this.createRequesParams();
return this.http.post(this.conf.endPoint, request_server, { observe: 'response' });
}
}
anotherComponent.ts
swiftListTable() {
const request_server = { "userType": this.currentUser.role, "authName": this.currentUser.username }
this.source = new PostServerDataSource(this.http,{endPoint: this.service.apiURL + 'swift/pagination', dataKey: 'content', pagerLimitKey:"_limit",
pagerPageKey:"_page",
sortDirKey: "pageable",
sortFieldKey: "pageable",
totalKey:'totalElements'});
}
There are two ways you can handle it,
one way is attaching the params in query string and append to url like,
this.service.apiURL + 'swift/pagination?param1=p&param2=q'
Other way could be handling it in requestElements and swiftListTable functions like below.
swiftListTable() {
const request_server = {
"userType": this.currentUser.role,
"authName": this.currentUser.username
}
this.source = new PostServerDataSource(http,
{ endPoint: url, dataKey: 'content', pagerLimitKey:'_limit'}, request_server);
export class PostServerDataSource extends ServerDataSource {
params: any;
constructor(http: HttpClient, config: any, params?: any) {
super(http, config);
this.params = params;
}
protected requestElements(): Observable<any> {
let httpParams = this.createRequesParams();
if (this.params) {
let keys = Object.keys(this.params);
keys.forEach((key) => {
httpParams = httpParams.set(key, this.params[key]);
});
}
return this.http.post(this.conf.endPoint, httpParams, { observe: 'response' });
}
}

Google Map API call happening before http get request can be made in Angular 5

I'm trying to use URL parameters to do a GET request, and then use JSON output to power a query into Google Maps. So far, I've figured out how to make the embedded map work by being sanitized, and passing in static data. However, doing the call for the query finishes after the URL gets built, thus getting 'undefined' as the result.
#Component({
selector: 'app-contactdetail',
templateUrl: './contactdetail.component.html',
styleUrls: ['./contactdetail.component.css']
})
export class ContactdetailComponent implements OnInit {
contactId: string;
contactDetail: Object = { };
lat: string;
lng: string;
queryBuilder = `&q=${this.lat},${this.lng}`;
apiLink = `https://www.google.com/maps/embed/v1/place?key=`;
// `&q=321+Fake+Street,Mouton+ME`
apiKey = `lol`;
builtLink = `${this.apiLink}${this.apiKey}${this.queryBuilder}`;
url: string = this.builtLink;
urlSafe: SafeResourceUrl =
this.sanitizer.bypassSecurityTrustResourceUrl(this.url);;
constructor(private route: ActivatedRoute,
private http: Http,
private sanitizer: DomSanitizer
) {}
ngOnInit() {
this.contactId = this.route.snapshot.params.id;
this.getContactDetail(this.contactId);
console.log(this.urlSafe);
console.log(this.builtLink);
}
// TODO: make this pass in an option eventually.
/* buildQuery(){
const address = `&q=${this.contactDetail.address.split('
').join('+')},`;
const city = ``
} */
async buildURL(url) {
const sanitized =
this.sanitizer.bypassSecurityTrustResourceUrl(url);
return sanitized;
}
async getContactDetail(contactId) {
return this.http
.request(`http://localhost:3000/contacts/${contactId}`)
.subscribe((res: any) => {
this.contactDetail = res.json();
if (this.contactDetail.hasOwnProperty('geoLocation_lat')) {
this.lat = this.contactDetail['geoLocation_lat'];
console.log(this.lat);
}
if (this.contactDetail.hasOwnProperty('geoLocation_lng')) {
this.lng = this.contactDetail['geoLocation_lng'];
console.log(this.lng);
}
console.log(this.contactDetail);
});
}
}
And the HTML code for what I'm trying:
</div>
<iframe
width="600"
height="450"
frameborder="0"
style="border:0"
[src]="urlSafe">
</iframe>
</div>
I'm very fresh to Web Dev, so any and all help would be appreciated. Thank You.
I don't see how the google maps query is being triggerd, but as long as that is correctly triggered after the .getContactDetails completes, then it looks to me like you need to define the URL in the subscription. In other words,
queryBuilder = `&q=${this.lat},${this.lng}`;
apiLink = `https://www.google.com/maps/embed/v1/place?key=`;
// `&q=321+Fake+Street,Mouton+ME`
apiKey = `lol`;
builtLink = `${this.apiLink}${this.apiKey}${this.queryBuilder}`;
is not dynamic. It simply uses the currently defined values for .lat and .lng, which are undefined.
change .getContactDetail to
async getContactDetail(contactId) {
// ...http request
.subscribe(res => {
// ...parse .lat and .lng; handle parsing errors
this.queryBuilder = `&q=${this.lat},${this.lng}`;
this.apiLink = `https://www.google.com/maps/embed/v1/place?key=`;
this.builtLink = `${this.apiLink}${this.apiKey}${this.queryBuilder}`;
});
}
And you should build the URL correctly.

Angular2 Service call Model Method

I'm new to angular2 so I will try to make this question as clear as possible. I want to have a method in my model that I can call from my service. Right now I have it trying to replace the name.
Here is my model
export class Secret {
public name: string;
constructor (
public id: number,
public type: string,
public visible_data: any,
public secrets?: any,
public group_id?: number,
public group_name?: string
) {
this.name = this.myName();
}
public myName(): string {
return this.name = "whaddup"
}
}
and my service method
/*
* get secrets
*/
public getSecrets(): Promise<Secret[]> {
let tempArray = [];
return this.sdk.list_secrets()
.then((resp) => {
resp.map((item) => {
tempArray.push({
id: item.id,
name: this.secret.myName(),
type: item.type,
visible_data: {
"username": item.data
}
}); // end push
});
return tempArray;
})
.catch((err) => {
console.error(err);
});
}
list.component.ts code:
export class ListComponent implements OnInit {
public constantArray: Secret[];
private secrets: Secret[];
private secret: Secret;
constructor(private secretService: SecretService) { }
public ngOnInit() {
this.getSecrets();
}
public getSecrets() {
this.secretService.getSecrets()
.then((data) => {
this.secrets = data;
this.constantArray = data;
});
}
}
Instead of
tempArray.push({
id: item.id,
name: this.secret.myName(),
type: item.type,
visible_data: {
"username": item.data
}); // end push
You should do
tempArray.push(new Secret(item.id, ...));
Reason: in your original code, the object pushed to array are plain old javascript object and they are not typescript objects. The 'new ...' will create a real typescript class object.
To work with angular2 service, you need to do two things. First of all you need to at the #Injectable annotation on top of your services.
If you want to inject services into each other, you need to provide them on your NgModule like this:
#NgModule({
selector: 'my-app',
providers: [NameService]
}):
If you want to inject the service into another service/component you can just leverage typescript and use constructor injection like this:
constructor(private secretService: SecretService) {}

Categories