I'm writing the page where I render posts.
in my controller I have:
export class PostsComponent implements OnInit {
posts$: Object;
users$: Object;
constructor(private data: DataService) { }
ngOnInit() {
this.data.getPosts().subscribe(
data => this.posts$ = data
)
this.data.getUsers().subscribe(
data => this.users$ = data
)
}
}
and in the template I iterate through the posts to render them:
<h1>Posts</h1>
<ul>
<li *ngFor='let post of posts$'>
<h3>{{ post.title }}</h3>
<h5>created by:</h5>
<p>{{ post.body }}</p>
</li>
</ul>
after 'created by: ' I would like to add {{user$.username}} where user$ should be a user of post.userId from users$ (already initialized in the controller). Methods getUsers and getPosts work fine and fetch the data from https://jsonplaceholder.typicode.com. I've got also getUser(id) method written and I thought I could use that one. It should be then in the controller:
this.data.getUser(id).subscribe(
data => this.users$ = data
)
but I don't know where to get the id from as I iterate through posts in the template. Could you help me with that?
You can make a function getUser(id) in your component and let it return the user.
In your html
{{ getUser(post.userId) }}.
Or
this.data.getPosts().subscribe(
data => {
for(let i = 0; i < data.length; i++) {
data[i].username = this.getUser(data[i].userId);
}
this.posts$ = data;
})
Related
i'm currently trying to make a Logbox for my webapp. It gets an Array of logs and should display them row by row in a div. Im using ngFor and looping trough my Array of logs and then displaying them.
My problem is now that the logs get displayed infinite times, not just 5 times (5 Array entries in list) like it should.
Does somebody have a hint what im missing?
logs.component.html
<div class="logContent">
<div class="row">
<div class="col-12" *ngFor="let log of this.logService.getLogs()">
<app-singlelog [when]="log.when" [type]="log.type" [data]="log.data"></app-singlelog>
</div>
</div>
</div>
log.service.ts
export class LogService {
private logArray = [];
/* private logObject = {} as Log; */
constructor(private httpservice: HttpserviceService) {
}
public getLogs(): Array<Log> {
this.httpservice.getLogs().subscribe(data => {
data.forEach(index => {
let logObject = {} as Log;
logObject.when = index.when;
logObject.type = index.type;
logObject.data = index.data;
this.logArray.push(logObject);
})
}
)
return this.logArray;
}
}
Thanks :)
Don't use function calls from html template to display data.
Instead, call the getLogs() function from the Angular ngOnInit() function, and store the response in a variable. Then loop on that variable:
export class LogService implements OnInit {
// ...
logs = [];
ngOnInit() {
this.getLogs();
}
getLogs(): Array<Log> {
this.httpservice.getLogs().subscribe(data => {
data.forEach(index => {
let logObject = {} as Log;
logObject.when = index.when;
logObject.type = index.type;
logObject.data = index.data;
this.logArray.push(logObject);
});
// assign the variable here:
this.logs = data;
});
}
In the template:
<div class="col-12" *ngFor="let log of logs">
<app-singlelog [when]="log.when" [type]="log.type" [data]="log.data"></app-singlelog>
</div>
The reason behind this is the fact that Angular calls your getLogs function on every page rendering cycle. But you should call the http request only once, when initalising the component.
Don't forget to unsubscribe from your Observable. ;) - corrected from the comment below.
You can also use the reactive approach and do the following:
Define the observable once in your component:
logs$ = this.httpservice
.getLogs()
.pipe(
shareReplay(),
map((data) => data.map(({ when, type, data }) => ({ when, type, data })))
);
Include in your component HTML the following:
<div class="col-12" *ngFor="let log of logs$ | async">
<app-singlelog [when]="log.when" [type]="log.type" [data]="log.data"></app-singlelog>
</div>
here is my ngOnInit (i am getting the data from a resolver)-
ngOnInit() {
const products = this.route.snapshot.data['users'] as IProductInterface[];
this._products = products.map((product) => new Product(product));
}
my getter -
public getProduct() {
return this._products;
}
here is how im displaying data on html -
<ng-container *ngFor="let product of getProduct()">
<span class="productInfo__title">{{ product.getTitle() }}</span>
</ng-container>
i would like to change that to subscribe because i want that to update whenever im changing the query. how can i do that? thanks
import { ActivatedRoute, Data } from '#angular/router';
....
....
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.data.subscribe((data: Data) => {
const products = data['users'] as IProductInterface[];
this._products = products.map((product) => new Product(product));
})
}
I'm writing app in Angular and Node.js. I have an object (order) that has a list of objects (items) that also contain objects (list of product id). I want to display them all in an html file. Please help me.
html file:
<div *ngIf="order">
<div *ngFor="let item of order.items"> // <- it does not work
<a>{{order.items}}</a> // <--
</div>
</div>
ts file:
export class AdminOrderItemComponent implements OnInit {
order: Order;
orderId;
constructor(private orderService: OrderService, private route: ActivatedRoute) { }
ngOnInit() {
this.route.paramMap
.subscribe(params => {
this.orderId = [params.get('id')];
this.getOrderById();
});
}
getOrderById() {
this.orderService.getOrderById(this.orderId).subscribe(
res => {
this.order = res;
},
err => console.log(err)
);
}
}
order interface:
export interface Order {
_id: any;
shipping: string;
userData: {};
sum: number;
items: {};
}
The ngFor iterates over the items of a collection. If you take a look at your model, you will realize that items is an object ({}), not an array ([]).
Your best bet is transforming the object received from your node.js backend to match your needs or (preferably I think) make your node.js model treat items as a collection as well which seems more appropriate.
Answer from #kg99:
Key: {{item.key}} and Value: {{item.value}}
In my frontend part of application use a method
this.knowledgeMan.getUserAllowedCases(Item.ItemNumber)
which returns Observable. On my backend part, this method returns a List<String>.
My question is: how do I get to loop over the elements of this list of Strings?
If you have an observable you have to subscribe to it to get the actual value. Within subscribtion its up to you, here you can map or loop over your values.
this.knowledgeMan.getUserAllowedCases(Item.ItemNumber).subscribe(allowedCases => {
allowedCases.map(allowedCase => {
// your code here
});
});
If you are using this List<String> observable to show on the HTML part you can use a combination of async and *ngFor to get the desired result.
//in your html for example
<ul>
<li *ngFor="let item of (data$ | async)"> {{ item }} </li>
</ul>
//in your component
//usual angular stuff
export class MyComponent implements OnInit {
data$: Observable<String[]>;
constructor(private knowledgeMan: YourServiceInterface){}
ngOnInit() {
data$ = this.knowledgeMan.getUserAllowedCases(Item.ItemNumber);
}
}
If you are just doing this to compute some value you can do this following.
this.knowledgeMan.getUserAllowedCases(Item.ItemNumber).pipe(
flatMap(),
map(item => //do something with item here)
).subscribe();
If as you say, getUserAllowedCases returns string[] then you can do this:
this.knowledgeMan.getUserAllowedCases(Item.ItemNumber).subscribe(x => {
// assuming x is string[]
for (const item of x) {
// use item
}
});
I have JSON containing Stores[]--->Products[]--->ProductDetails--->ProductTags[] for the begining I want to see all the data
so I coded:
service
export class StoreService {
//private myurl: string ="apidata/products.json"
constructor(private _http: Http) {}
getProducts(): Observable < any > {
let apiUrl = './assets/products.json';
return this._http.get(apiUrl)
.pipe(map((response: Response) => {
const data = response.json();
return data;
}));
}
store component:
export class StoreComponent implements OnInit {
products = [];
constructor(private _storeService: StoreService) {}
ngOnInit() {
this._storeService.getProducts()
.subscribe(data => {
this.products = data;
console.log(data)
});
}
}
html
<h2>store</h2>
<ul>
<li *ngFor="let p of products">{{p}}</li>
</ul>
the error I get:
Error trying to diff '[object Object]'. Only arrays and iterables are
allowed
An error which you are getting suggests that your data is either an object / map instead of an array.
If you want to use *ngFor in your template, your data needs to be an array instead of an object.
Although angular team introduce a pipe called "keyvalue" in version 6.1. So you can take advantage of that and iterate over an object inside your angular template.
For example, assume that your object looks like below
const user = {
name: 'xyz',
age: 30,
gender: 'male'
};
So in your template file, you can iterate over this object like this
<ul>
<li *ngFor="let data of user | keyvalue">
Key : {{data.key}} and Value : {{data.value}}
</li>
</ul>
Please refer this link for more info about this pipe
You generally get this error when you pass an Object to *ngFor as an input. As the error states, *ngFor only works with Iteratable Data Structures, which in our case will be an array of products.
Make sure that in here: this.products = data;, data is actually an array.
UPDATE:
As it's quite obvious from this screenshot of the console log:
data is an object and not an array. PriceFilter, Stores and GenderFilter on that object are arrays. If you want to show stores, you can do:
export class StoreComponent implements OnInit {
stores = [];
constructor(private _storeService: StoreService) {}
ngOnInit() {
this._storeService.getProducts()
.subscribe(data => {
this.stores = data.Stores;
console.log(data)
});
}
}
And in template
<h2>store</h2>
<ul>
<li *ngFor="let store of stores">
<h4>Products:</h4>
<ul>
<li *ngFor="let product of store.Products">
<p>Product Id: {{ product.ProductId }}</p>
<p>Product Title: {{ product.ProductTitle }}</p>
...
</li>
</ul>
</li>
</ul>
The main purpose of this error is that your data (products) is returning an object {} - ngFor requires an array [] to iterate. Either you have to check your HTTP Response or fix your backend API.
And merely for your example you can do this this.products = data.Stores; (if it is stores is what you're looking for to iterate on).
UPDATE
According to your link in comments you have to change your code to :
this.products = data.Stores;
And then :
<div *ngFor="let p of products">
<ul>
<li *ngFor="let item of p">
<-- HERE you take whatever you want from your product object --!>
</li>
</ul>
</div>