Can't bind from API data Angular 6 - javascript

I'm working with API to get data and bind it to the HTML, i get to show the response on the console but once i try to bind data to the User screen i cannot, nothing is showing
My HTML:
<ul *ngFor="let item of items; let i of index">
<li class="fa fa-plus">{{item.name}} - {{i}}</li>
</ul>
My TS:
id: number;
name:string;
imageUrl: string;
items:any = {};
ngOnInit() {
this.getItems();
}
getItems() {
this.getItemsService.getItems().subscribe(
res => {
console.log(res)
if (res.Success) {
this.items = res;
}
}
)
}
I'm trying to get data from an array of objects this is why i defined items as an object, if i tried to define it as an array, same result, cannot bind data to HTML
My API Link:
http://5a12745f748faa001280a746.mockapi.io/v1/stores/item
Any questions or missing info tell me but kindly i need help binding this data

Two things, HTML first : let i = index is the syntax.
The second one, you have a condition in your binding :
if (res.Success) {
this.items = res;
}
This implies that your response has a Success property.
I checked your API link, and it doesn't.
So in your service, either you use Http from #angular/http or HttpClient from #angular/common/http. Here are the two syntaxes :
// Http
return this.http.get('url').map(res => res.json());
// HttpClient
return this.http.get<any[]>('url');
For the condition, change to the following, to check if you have data. You don't need to check for success because you have a special callback for that. This means you will always have succeed where the condition is.
if (res) {
this.items = res;
}

Related

Extracting data from subscribe() method

I don't really know how to extract the values from the subscribe() method.
getMessages(): any {
this.gatewayMessagesState$.subscribe(data => data.gatewayMessages
.get(this.gatewayId)
?.list
.map(Message => Message.message));
}
gatewayMessagesState is an initial state that contains some data. gatewayMessages is a map with gatewayIds as keys and arrays of Message objects as values. Message has message field that's just a string. I would like to extract an array of messages for a given id. How can I do that?
What you probably want to do is to populate another Observable with the data so that you can access it elsewhere in your project without the need for calling the API more than once.
To do this, you create what is known as a Subject (in this case a BehaviorSubject) and you can populate that with data when your API call returns a response.
Then, in order to access this data elsewhere, you can create a "get" function to return the Subject (which is itself an Observable) whenever you need the data.
Here is an example:
my - data.service.ts
myData: BehaviorSubject < number > = new BehaviorSubject < number > (0);
callApi() {
this.dbService.get('apiUrl').subscribe(
(data) = > this.myData.next(data) // Assuming data is a 'number'
);
}
getMyData() {
return this.myData.asObservable();
}
Now to use this in a component:
this.myService.getMyData().subscribe(
(data) = > {
/* Use the value from myData observable freely */
}
);
Or you could rely on the Angular async pipe (which is a very convenient method for dealing with observables in your code).
You are not specifying if getMessages is in a service, component... in any case, I suggest returning the Observable without subscribing to it in the getMessages function
// this function could be anywhere
getMessages(): Observable<string[]> {
return this.gatewayMessagesState$.pipe(
map((data) => data.gatewayMessages.get(this.gatewayId)),
map((yourMap) => yourMap?.list.map((theMessage) => theMessage.message))
);
}
Now, if you need to extract this value, either from a component, a service, etc... then, just call this function and then subscribe to get the result
Let's say getMessages is in a service file for example
Your component
constructor(private theService: YourService) {}
anotherFunction() {
this.theService.getMessages().subscribe((myMessages) => console.log(myMessages));
}
Or let the async pipe subscribe to this observable
Your component
messages$!: Observable<string[]>
constructor(private theService: YourService) {}
anotherFunction() {
this.messages$ = this.theService.getMessages()
}
Your component html
<ng-container *ngIf="messages$ | async as messages">
<div *ngFor="let message of messages">
<p>{{ message }}</p>
</div>
</ng-container>
I this you want to retrieve the data as an observable of messages as string, you can define the function return as this and using pipe and map operatoes from rxjs,this is code below is my proposition
getMessages(): observable<string[]>{
return this.gatewayMessagesState$.pipe(map((data) =>
data.filter((f) => f.gatewayMessages.id ===this.gatewayId)),
map(item => item.message));
}

How to make asynchronous function in angular?

In componentOne.ts i am sending the data through sharedService as,
this.sharedService.sendData(this.getCheckedProduct);
In componentTwo.ts i am subscribing the data like,
productList: any = [];
getAllProducts: any = [];
ngOnInit() {
this.sharedService.getData().subscribe(data => {
this.productList = data;
this.getProduct();
});
}
I am getting the data here in productlist then i need to call the function this.getProduct() which has the following,
getProduct() {
let tempArray = [];
this.productList.forEach(element => {
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).subscribe(res => {
tempArray.push(res.data);
});
});
this.getAllProducts = tempArray;
}
I need to pass the id element.product_obj_id to get the necessary data of that id..
I have tried changing the above like this,
ngOnInit() {
this.sharedService.getData().subscribe(data => {
data.forEach(element => {
this.getProduct(element);
})
});
}
async getProduct(element) {
let asyncResult = await this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).toPromise();
this.getAllProducts.push(asyncResult['data']);
}
Inside async getProduct(element) function i am, getting the data of this.getAllProducts but in html i am not getting the data.
HTML:
<div *ngFor="let product of getAllProducts">
Getting data
</div>
If i changed the above with async
<div *ngFor="let product of getAllProducts | async">
Getting data
</div>
I am getting error as,
ERROR Error: InvalidPipeArgument: '' for pipe 'AsyncPipe'
at invalidPipeArgumentError
To explain in detail, i need to send a data through sharedservice in componentone and recieve it in componenttwo then need to get the product_obj_id from that shared service data.
The on passing each id, i will get the data of that particular product and i need to store the final data recieved from this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).toPromise(); to getAllProducts..
AppConfig.settings.product.getProducts
is the url ..
How to achieve it in a async way.. UPto this i am getting data
this.getAllProducts.push(asyncResult['data']);
but outside the function i am not getting the value, Also the getAllProducts no more working on any scenario in html..
In scenario 1 explained at top of this question i have given the following
this.getAllProducts = tempArray;
This one gives empty array as value and so only i am trying with async function.
In simple words i need to get the final data from this.getAllProducts which will be recieved from the service with get method for which i need to pass an id in the url..
Simply assign your data inside the async call like this -
getProduct() {
let tempArray = [];
this.productList.forEach(element => {
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).subscribe(res => {
tempArray.push(res.data);
this.getAllProducts = tempArray; // Here
});
});
}
PS: In your use case no need to use pipe async.
AsyncPipe is to be applied on an Observable. In Your case, you are applying it on getAllProducts which is an Array, I believe. Hence you are getting that error.
Possible Solution:
remove async from your for loop of getAllProducts
Make getAllProducts return an Observable of your Products
Note: since you are looping through productList and calling the webapi to get the products, you might have to use mergeMap.
Solution 2 Sample:
I believe Solution 2 is best for you, since you productList contains all the products for which you need to hit the webapi to get the details of the products.
Learn MergeMap here
Sample code(untested & you need to add code for error handling with catchError):
import { from } from 'rxjs';
import { mergeMap, map, combineAll } from 'rxjs/operators';
this.getAllProducts = from(this.productList)
.pipe(
mergeMap(element =>
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).pipe(
map(res => res['data'])
)
),
combineAll()
)
<div *ngFor="let product of getAllProducts | async">
Getting data
</div
Explanation:
since you productList is an Array and you have to loop through it to and fire a request for each product object in that Array
from(this.productList) makes each object in productList into an observable. We add a pipe and using mergeMap we can fire request for each of the product, hence you see this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id) this inside mergeMap. We then map the response we get from the webapi and only return data using res['data']. Finally we combineAll the response which returns an Observable<[]>, which we set into getAllProducts, hence getAllProducts is an Observable and we can use AsyncPipe on it.
Edit:
Add the following in your constructor instead of in ngOnit
const getAllProducts = this.sharedService.getData().pipe(
mergeMap(data=> from(data))
).pipe(
mergeMap(element =>
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).pipe(
map(res => res['data'])
)
),
combineAll()
)

Angular2 observable with JSON root element

This is my first time ever working with angular observables and I'm a bit confused on how this works. I was given a mostly functioning angular CLI app that I just need to wire up to my already existing API.
I have a service with this function
public getApps(): Observable<ApplicationInterface[]> {
return this.http.get(url);
}
Then in my component, I have
public data: ApplicationInterface[];
ngOnInit() {
this.route.params
.subscribe(params => {
this.fetchData();
});
}
fetchData() {
this.service.getApps()
.subscribe(data => {
this.data = data;
});
}
My api endpoint returns a JSON structure of {"applications": []}
I can't seem to figure out how to access the array in that JSON hash.
If I console.log(data) in the subscribe block, it is the API response with the applications key that I expect, but if I change the data assignment to this.data = data.applications, ng build fails with Property 'applications' does not exist on type 'ApplicationInterface[]'
You should design the interface to be aligned with the response. If the response is object, than you need to have it like this also in the interface.
Try something like this (using the new HttpClient):
interface ApplicationInterfaceResponse {
applications: ApplicationInterface[];
}
public getApps(): Observable<ApplicationInterface[]> {
return this.httpClient
.get<ApplicationInterfaceResponse>(url)
.map(response => {
console.log(response.applications);
return data.applications;
});
}
If your return is of type ApplicationInterface[], then it's an array of ApplicationInterfaces, thus does not have a property called applications on it. This has nothing to do with your observable; it's fine. Rather, you've mistyped your variable.
If you don't need any other properties of data, you can map the value:
public getApps(): Observable<ApplicationInterface[]> {
return this.http.get(url).map(data => data.applications);
}
However, I recommend against this in most situations. If your object changes in the future, then you have to change this function and all attached subscriptions. Instead, you should create an interface for your response (your response right now does not match the type you're giving it), and use values of it as necessary.
The simplest fix is to indicate the correct form of the data that is returned by your service method since it doesn't actually return an array:
public getApps(): Observable<{applications:ApplicationInterface[]}> {
return this.http.get(url);
}
Now in your subscribe, you can get at the array as you would expect
.subscribe(e => this.data = e.applications)

Angular 4 http get json file. Error cannot find a differ [duplicate]

Im following this tutorial. On the way to get list of users from api.github Im getting error:
Cannot find a differ supporting object '[object Object]'
I think its related to
<ul>
<li *ngFor = "#user of users">
{{user | json}}
</li>
</ul>
In my code because before it there was no any error, and im unsure if data come from get request, just clicking didnt give any error, here is my code so far
#Component({
selector: 'router',
pipes : [],
template: `
<div>
<form [ngFormModel] = "searchform">
<input type = 'text' [ngFormControl]= 'input1'/>
</form>
<button (click) = "getusers()">Submit</button>
</div>
<div>
<ul>
<li *ngFor = "#user of users">
{{user | json}}
</li>
</ul>
</div>
<router-outlet></router-outlet>
`,
directives: [FORM_DIRECTIVES]
})
export class router {
searchform: ControlGroup;
users: Array<Object>[];
input1: AbstractControl;
constructor(public http: Http, fb: FormBuilder) {
this.searchform = fb.group({
'input1': ['']
})
this.input1 = this.searchform.controls['input1']
}
getusers() {
this.http.get(`https://api.github.com/
search/users?q=${this.input1.value}`)
.map(response => response.json())
.subscribe(
data => this.users = data,
error => console.log(error)
)
}
}
bootstrap(router, [HTTP_PROVIDERS])
I think that the object you received in your response payload isn't an array. Perhaps the array you want to iterate is contained into an attribute. You should check the structure of the received data...
You could try something like that:
getusers() {
this.http.get(`https://api.github.com/search/users?q=${this.input1.value}`)
.map(response => response.json().items) // <------
.subscribe(
data => this.users = data,
error => console.log(error)
);
}
Edit
Following the Github doc (developer.github.com/v3/search/#search-users), the format of the response is:
{
"total_count": 12,
"incomplete_results": false,
"items": [
{
"login": "mojombo",
"id": 1,
(...)
"type": "User",
"score": 105.47857
}
]
}
So the list of users is contained into the items field and you should use this:
getusers() {
this.http.get(`https://api.github.com/search/users?q=${this.input1.value}`)
.map(response => response.json().items) // <------
.subscribe(
data => this.users = data,
error => console.log(error)
);
}
I received this error in my code because I'd not run JSON.parse(result).
So my result was a string instead of an array of objects.
i.e. I got:
"[{},{}]"
instead of:
[{},{}]
import { Storage } from '#ionic/storage';
...
private static readonly SERVER = 'server';
...
getStorage(): Promise {
return this.storage.get(LoginService.SERVER);
}
...
this.getStorage()
.then((value) => {
let servers: Server[] = JSON.parse(value) as Server[];
}
);
Missing square brackets around input property may cause this error.
For example:
Component Foo {
#Input()
bars: BarType[];
}
Correct:
<app-foo [bars]="smth"></app-foo>
Incorrect (triggering error):
<app-foo bars="smth"></app-foo>
Something that has caught me out more than once is having another variable on the page with the same name.
E.g. in the example below the data for the NgFor is in the variable requests.
But there is also a variable called #requests used for the if-else
<ng-template #requests>
<div class="pending-requests">
<div class="request-list" *ngFor="let request of requests">
<span>{{ request.clientName }}</span>
</div>
</div>
</ng-template>
This ridiculous error message merely means there's a binding to an array that doesn't exist.
<option
*ngFor="let option of setting.options"
[value]="option"
>{{ option }}
</option>
In the example above the value of setting.options is undefined. To fix, press F12 and open developer window. When the the get request returns the data look for the values to contain data.
If data exists, then make sure the binding name is correct
//was the property name correct?
setting.properNamedOptions
If the data exists, is it an Array?
If the data doesn't exist then fix it on the backend.
If this is an Observable being return in the HTML simply add the async pipe
observable | async
Explanation:
You can *ngFor on the arrays. You have your users declared as the array. But, the response from the Get returns you an object. You cannot ngFor on the object. You should have an array for that. You can explicitly cast the object to array and that will solve the issue.
data to [data]
Solution
getusers() {
this.http.get(`https://api.github.com/
search/users?q=${this.input1.value}`)
.map(response => response.json())
.subscribe(
data => this.users = [data], //Cast your object to array. that will do it.
error => console.log(error)
)
If you don't have an array but you are trying to use your observable like an array even though it's a stream of objects, this won't work natively. I show how to fix this below.
If you are trying to use an observable whose source is of type BehaviorSubject, change it to ReplaySubject then in your component subscribe to it like this:
Component
this.messages$ = this.chatService.messages$.pipe(scan((acc, val) => [...acc, val], []));
Html
<div class="message-list" *ngFor="let item of messages$ | async">

Data from resolved Promise not loading properly in component view (Angular2, JavaScript)

I'm fairly new to Angular2 so forgive me if this is a super basic fix.
I'm working through a tutorial using PokeAPI and Angular2 but trying to deviate from the tutorial to add Pokemon types to the rendered view. However, the data from my PokedexService is not properly loading into my view template like it should.
Full codebase that is producing the error on Github.
As it deviates from the original tutorial, I've updated the Pokemon class to add a types property to it:
export class Pokemon {
id: number;
sprite: string;
name: string;
types: [];
}
In my pokedex.service.ts service file, I've added a new method getPokemonTypes() that queries the API for each individual Pokemon's types and returns an array containing either one or two types.
getPokemonTypes(id: number): Promise<any> {
return this.http.get(`${this.baseUrl}${id}/`)
.toPromise()
.then(response => response.json())
.then(details => {
const types = details.types
.map(t => {
return t.type.name;
});
return types;
});
}
In my app.component.ts, I've called a method loadMore() in my ngOnInit that should load all of the data necessary to render the view when the app initializes. Inside the loadMore() method, I've called my pokedexService's getPokemon() method and then in that Promise's resolution chain, I've called getPokemonTypes() to map Pokemon type values onto my Pokemon object.
export class AppComponent implements OnInit {
pokemon: Pokemon[] = [];
isLoading: boolean = false;
error: boolean = false;
constructor(private pokedexService: PokedexService) { }
ngOnInit() {
this.loadMore();
}
loadMore() {
this.isLoading = true;
this.pokedexService.getPokemon(this.pokemon.length, 9)
.then(pokemon => {
pokemon = pokemon.map(p => {
p.imageLoaded = false;
// Map Pokemon types onto each Pokemon object
p.types = this.pokedexService.getPokemonTypes(p.id);
return p;
});
this.pokemon = this.pokemon.concat(pokemon);
this.isLoading = false;
this.error = false;
})
.catch(() => {
this.error = true;
this.isLoading = false;
})
}
}
In my view template (app.component.html), I've added a <div> to hold the Pokemon types. Currrently the div only renders via {{p.types}} just so that I can gain feedback on what is causing the error. For reference, here is the view template:
<div class="pokedex">
<div class="pokedex-pokemon" *ngFor="let p of pokemon">
<div class="pokedex=pokemon-id">
#{{p.id}}
</div>
<img [ngClass]="{'hidden': !p.imageLoaded}" class="pokedex-pokemon-sprite" (load)="p.imageLoaded = true" [attr.src]="p.sprite" />
<div class="pokedex-pokemon-name">
{{p.name | capitalize}}
</div>
<div class="pokedex-pokemon-types" *ngIf="!isLoading">
{{p.types}}
</div>
</div>
</div>
When I reload the app, the view renders like this:
where, instead of rendering the types, it displays [object Promise]. I've also tried rendering the view with {{p.types[0]}}{{p.types[1]}} since Pokemon.types is an array of types, but when I do that, it doesn't render anything in the view:
I've hypothesized that I somehow need to resolve the Promise that is stored in the types property, but I thought calling .then() on the returned HTTP promise in the getPokemonTypes() method of PokedexService resolved the promise to a value for me.
As a side note, I would ultimately like to render the types in my view like this:
<div class="pokedex-pokemon-types">
<div *ngFor="let t of pokemon.types">
<div [ngClass]="${t}">{{t}}</div>
</div>
</div>
but thus far I haven't been able to get that to render either. I'm not entirely sure what the proper syntax is for the [ngClass] directive to get it to use the Pokemon type name as the class for each div.
Thanks for any help offered! Greatly appreciated.
Isn't a result this.pokedexService.getPokemonTypes(p.id) a promise? If is, I think you should write something like this:
pokemon = pokemon.map(p => {
p.imageLoaded = false;
this.pokedexService.getPokemonTypes(p.id)
.then(types => {
p.types = types;
this.pokemon = this.pokemon.concat(p);
this.isLoading = false;
this.error = false;
});
});

Categories

Resources