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
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'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¶m2=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' });
}
}
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 am new to Angular, JS, and observables. I have a typescript class called DataService. I want it to load a list of URLs from a JSON formatted local file, and then have some way to call those URLs (to a handful of REST APIs) and return observables. The problem I am having is my code is not waiting for the config file to be loaded before the REST API functions get called.
I thought I could have the DataService constructor load the configuration file, and then have unique functions for each REST API call, but that isn't working
my code:
export class DataService {
configFile
constructor(private http: HttpClient) {
this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.configFile = config;
});
}
getUrlFromConfigFile(name: string): string {
...
this returns the URL from the config file
...
}
getUrlAData(): Observable {
return this.http.get( getUrlFromConfigFile('A') )
}
}
My other components have code like this:
export class SomeComponent implements OnInit {
someComponentAData
constructor(private data: DataService) { }
ngOnInit() {
this.data.getUrlAData().subscribe(
data => {
this.someComponentAData = data
}
)
}
I am getting an error that the observable returned from the dataservice is undefined. Which I believe is because the constructor hasn't finished loading the config file, which I think is why the function getUrlAData isn't returning anything.
I feel like I'm not correctly handling these async calls, but I'm at a loss for how to tell my code to :
create the data service object
load the data file before anything else can be done
allow the other functions to be called asyncronously AFTER the config file is loaded
Angular CLI: 6.2.3
Node: 8.12.0
OS: win32 x64
Angular: 6.1.8
Edit 1: attempting to implement suggested solution
My DataService
configFile
configObservable: Observable<any>;
someSubscribeObj
constructor(private http: HttpClient) {
this.someSubscribeObj = this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.someSubscribeObj = undefined;
this.configFile = config;
});
}
getObsFromConfigFile(name: string): Observable<any> {
//...
if (this.configFile != undefined) {
console.log('this.restApiUrlListConfig[name]',this.configFile[name])
return of(this.configFile[name])
}
else
return of(this.someSubscribeObj.pipe(map(c => c[name])))
//this.configObservable
//...
}
getUrlAData(): Observable<any> {
return this.getObsFromConfigFile('A').pipe(mergeMap(url => this.http.get(url)))
}
My other component:
constructor( private data: DataService ) { }
ngOnInit() {
//this.data.loggedIn.pipe((p) => p);
this.data.getUrlAData().subscribe(
data => {
this.urlAData = data
}
)
}
I was unable to store the "subscribe" into the observable, so I created a generic Any type varable, but at runtime I get a problem with the pipe command:
TypeError: this.someSubscribeObj.pipe is not a function
at DataService.push../src/app/services/data.service.ts.DataService.getObsFromConfigFile
(data.service.ts:67)
at DataService.push../src/app/services/data.service.ts.DataService.getUrlAData
(data.service.ts:74)
Edit 2: the unfortunate workaround
I am currently using two nested subscriptions to get the job done basically
http.get(config_file_url).subscribe(
config => {
http.get( config['A'] ).subscribe( adata => { do things };
http.get config['B'].subscribe( bdata => {do things };
}
)
I feel like I should be able to use a mergeMap of some sort, but I couldn't get them to work as I thought they would.
You need to wait on that async call, I would use a flatmap to get the value out of an observable.
export class DataService {
configFile
configObservable: Observable<any>;
constructor(private http: HttpClient) {
this.configObservable = this.http.get('/assets/restApiUrlListConfig.json').pipe(
map(config => {
this.configObservable = undefined;
this.configFile = config;
return configFile;
})
);
}
getUrlFromConfigFile(name: string): Observable<string> {
...
return of(configFile[name]) if configFile is set else return configObservable.pipe(map(c => c[name]));
...
}
getUrlAData(): Observable<string> {
return this.getUrlFromConfigFile('A').pipe(map(url => this.http.get(url)))
}
}
Basically you want to store the observable and keep using it till it completes, after it completes you can just wrap the config in an observable. The reason for wrapping it is to make the interface consistent, otherwise you have to have an if before every get.
I have a DataServive, that fetches content from an API:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError, retry } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
#Injectable()
export class DataService {
this.request = {
count: 10
}
constructor(private http: HttpClient) { }
private handleError(error) {
console.log(error);
}
public getData(count): Observable<any> {
this.request.count = count;
return this.http.post<any>(environment.api + '/path', this.request).pipe(
map(response => {
return response;
}),
catchError(error => {
this.handleError(error);
return [];
})
);
}
}
This DataServie is consumned by a component like this:
ngOnInit() {
const subscriber = this.dataService.getData(this.count).subscribe((data) => { this.data = data; });
}
And it works fine.
However the user is able to change the variable this.count (how many items should be displayed) in the component. So I want to get new data from the server as soon as this value changes.
How can I achieve this?
Of course I could call destroy on this.subscriber and call ngOnInit() again, but that dosn't seem like the right way.
Easiest ways is just to unsubscribe:
subscriber: Subscription;
ngOnInit() {
this.makeSubscription(this.count);
}
makeSubscription(count) {
this.subscriber = this.dataService.getData(this.count).subscribe((data) => { this.data = data; });
}
functionInvokedWhenCountChanges(newCount) {
this.subscriber.unsubscribe();
makeSubscription(newCount);
}
But because count argument is just a one number it means HTTP always asks for data from 0 to x. In this case, you can better create another subject where you can store previous results (so you don't need to make useless HTTP requests) and use that subject as your data source. That needs some planning on your streams, but is definitely the preferred way.
When the user changes count, call getData(count) again with the updated count value. Need to see your html file, but having a button with (click)="getData(count)" may help.