Trying to iterate an array of objects in angular - arrays

Problem: Trying to iterate an array of objects in angular but only appears the first one. The service works, I'm getting the full response.
The angular service where I execute a get for all the assets:
getAsset(asset_type):Observable<Asset[]>{
return this.http.get<Asset[]>(`${this.find}/${this.domain_id}/assets?asset_type=${asset_type}`, httpOptions);
}
The model of the Asset
export class Asset{
asset_name: string;
mac_address: string;
floor: number;
location: string;
temperature: number;
battery: number;
timestamp_tlm: string;
timestamp_geo: string;
}
The component.ts where i call the service and send the corresponding parameter.
ngOnInit() {
this.whereisService.getAsset(this.asset_type).subscribe(assets => {
if(Array.isArray(assets)){
this.assets = assets;
}
else if(typeof assets === 'string' ){
this.assets = [];
}
else{
this.assets = [assets];
}
});
}
}
The component.html
<div class="text-center">
<ul id="ticket" class="list-unstyled my-2">
<li class="btn w-100 bg-primary text-center text-white mx-0 my-2 display-block" *ngFor="let asset of rows_transformed let i = index">
<p class="h5"> Name: {{ asset?.asset_name }}</p>
<p class="h5"> Tipo: {{ asset?.asset_type }}</p>
<p class="h5"> Mac adress: {{ asset?.mac_address }} </p>
<p class="h5"> Piso: {{ asset?.floor }} </p>
<p class="h5"> Localização: {{ asset?.location }} </p>
<p class="h5"> Hora: {{ asset?.timestamp_tlm }} </p>
</li>
</ul>
</div>
Response JSON from API
{
"code": 200,
"data": [
{
"mac_address": "AC233F52BD17",
"floor": -3,
"asset_name": "Tripés microfone 1",
"asset_type": "Carro trasnporte",
"location": "Armazem 2",
"temperature": 22.0,
"battery": 74.0,
"timestamp_tlm": "2019-11-22 10:17:49.563121+00:00",
"timestamp_geo": "2019-11-22 10:17:49.563266+00:00"
},{...}
]
}

The JSON response doesn't match with what you expect. Start by simplifying your component. The service is supposed to return an Observable<Asset[]>. So the component shouldn't test to see if the emitted event is an array or a string. It's supposed to be an array of assets. If it's not, then the service should be fixed:
this.whereisService.getAsset(this.asset_type).subscribe(assets => this.assets = assets);
Then, you need to fix the service. What the server returns is not an array. It's an object with a data property, which is an array of assets. So the service should be
getAsset(asset_type): Observable<Asset[]>{
return this.http.get<{ data; Asset[]; }>(`${this.find}/${this.domain_id}/assets?asset_type=${asset_type}`, httpOptions).pipe(
map(object => object.data)
);
}
Since the methods allows getting an arra of assets, it should also be named getAssets(), not getAsset(). When one sees a method getAsset(), one expects to get back one asset, not an array of assets.
And finally, since the array is stred in the property assets and not rows_transformed, the template should iterate through that array:
*ngFor="let asset of assets; index as i"

Use
*ngFor="let asset of assets"
in your Html instead of rows_transformed. Also you can scrap i as you are not using it.

Related

Proper way to show array inside an array with v-for

I am trying to display the values of the text array/object but I am getting an output trying to show me paragraphs for every name inside the array/object.
Link to current result: current result
I am fairly new to vue.js so any tips are welcome!
<template>
<div class="education center">
<div v-if="object.timelines != null">
<template v-for="(time,index) in object.timelines">
<p :key="index">{{ time.schoolyear }}</p>
<div :key="index" v-bind="time" v-for="(text,index) in time.text">
<p :key="text">Degree: {{ text.degree }}</p>
<p>Institution: {{ text.institution }}</p>
<p>Where: {{text.where}}</p>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
el: ".education",
data: function() {
return {
object: {
timelines: [
{
schoolyear: "2016 - 2017",
text: [
{ degree: "Applied Computer Science" },
{ institution: "Thomas More University of Applied Science" },
{ where: "Belgium, Geel" }
]
},
{
schoolyear: "2018 - 2019",
text: [
{ degree: "Business IT" },
{ institution: "HAMK University of Applied Science" },
{ where: "Finland, Hämeenlinna" }
]
}
]
}
};
}
};
</script>
I only want to show text.degree once for schoolyear="2016 - 2017"
It's not completely clear to me what you want, but maybe is it that you want to iterate through the .text array and, for each entry in the array, display both its key and its content. If so, you might try something like:
<p v-for="(entry, index) in time.text" :key="index">
{{Object.keys(entry)[0]}}: {{Object.values(entry)[0]}}
</p>
If you need to worry about title case for the keys, you could either use a computed property to convert the array, or define a method to convert each string.
First of all thanks to Boussadjra Brahim for providing the codepen wich resolved my issue.
I will first rephrase the question and then copy the solution.
The question: I want to print out values from an array inside a javascript object. In my <div>tag is is currently printing trying to print out text.institution for each element in the text array.
resulting in vue.js trying to show <p>Institution: {{ text.institution }}</p>
for degree, institution and where. Giving a browser output of:
<p>Degree:</p>
<p>Institution: Thomas More University of Applied Science"</p>
<p>Where:</p>
for text.where this would change to
<p>Degree:</p>
<p>Institution:</p>
<p>Where: Belgium, Geel</p>
The answer: Yet again a huge thanks to Boussadjra Brahim for showing the solution.
/* eslint-disable vue/require-v-for-key */
<template>
<div class="education center">
<div v-if="object.timelines != null">
<template v-for="(time,index) in object.timelines">
<p :key="index">{{ time.schoolyear }}</p>
<div :key="index" :set="text = time.text">
<p>Degree: {{ text.degree }}</p>
<p>Institution: {{ text.institution }}</p>
<p>Where: {{text.where}}</p>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
el: ".education",
data(){
return {
object: {
timelines: [
{
schoolyear: "2016 - 2017",
text: {
degree: "Applied Computer Science",
institution: "Thomas More University of Applied Science",
where: "Belgium, Geel"
}
},
{
schoolyear: "2018 - 2019",
text: {
degree: "Business IT",
institution: "HAMK University of Applied Science",
where: "Finland, Hämeenlinna"
}
}
]
}
};
}
};
</script>
I changed the text array from square brackets to curly brackets and instead of using a v-for= I changed to a :set=.

Get into Firebase complex object containing another object

I've got Angular2 project connected directly with Firebase.
The structure of my database is presented in the picture below. So we've got couple of objects.
Main is orders, then the key od order, some extra information and another object "items" which contains several objects like 0, 1 etc. Each of them has a object called "product" which has some other parameters...
orders
-> datePlaced,
items
-> 0
-> quantity, totalPrice, product
->
imageUrl,
price,
title
My point is that when I'm creating "My Orders" tab, I'd like to get information of every item in specific order e.g. I see list of orders, and small button "View order", I click and I see details about this order with special key.
I prepared view with orders, buttons but I cannot take any objects form object orders, I've got a blank page.
order-details.component.ts
export class OrderDetailsComponent implements OnInit, OnDestroy {
order$;
userId: string;
userSubscription: Subscription;
constructor(private orderService: OrderService, private authService: AuthService) {}
async ngOnInit() {
this.userSubscription = this.authService.user$.subscribe(user => this.userId = user.uid);
this.order$ = this.authService.user$.switchMap(u => this.orderService.getOrdersByUser(u.uid));
}
ngOnDestroy() {
this.userSubscription.unsubscribe();
}
}
order-details.component.html
<div class="card" *ngIf="order$ | async as order">
<div class="card-body">
<h5 class="card-title">Your Order</h5>
<p class="card-text">You ordered {{ order.datePlaced | date }} items</p>
<ul class="list-group list-group-flush">
<li *ngFor="let order of order.shipping" class="list-group-item">
{{ order.city }} x {{ order.city }}
<div class="float-right">
{{ order.totalPrice | currency:'USD':'symbol' }}
</div>
</li>
<li class="list-group-item font-weight-bold">
Total
<div class="float-right">
{{ order.totalPrice | currency:'USD':'symbol' }}
</div>
</li>
</ul>
</div>
</div>
order.service.ts
export class OrderService {
constructor(private db: AngularFireDatabase, private shoppingCartService: ShoppingCartService) { }
async placeOrder(order) {
let result = await this.db.list('/orders').push(order);
this.shoppingCartService.clearCart();
return result;
}
getOrders() {
return this.db.list('/orders');
}
getOrdersByUser(userId: string) {
return this.db.list('/orders', {
query: {
orderByChild: 'userId',
equalTo: userId
}
});
}
}
How can I get every parameter from complex object "orders"?
I solved this by taking param routes in my order-details.component.ts and then a create function getOrder(id) in order service. When you have id of your order it's quite simple to take object from database.

Compare two lists in angularJs

I have two list that creat from two different json object:
<ul ng-repeat="a in user.data">
<li>
<md-checkbox>
{{ a.name }}
</md-checkbox>
</li>
</ul>
and :
<ul ng-repeat="x in job.data">
<li>
<md-checkbox>
{{ x.user.name }}
</md-checkbox>
</li>
</ul>
I have to compare two lists and remove {{ a.name }} that the same as {{ job.user.name }} . Comparing json objects that have different structure is hard. How can I compare this two list and remove repeated items?
You can use the filter filter ;) using a function instead of a string in the expression argument. Remember this is the syntax of filter
{{ filter_expression | filter : expression : comparator}}
The expression can be a string, an object or a function. Something like this will do
$scope.filterUnemployed = function(value) {
var jobs = $scope.job.data;
for (var i = 0; i < jobs.length; i++) {
// Search every object in the job.data array for a match.
// If found return false to remove this object from the results
if (jobs[i].user.name === value.name) {
return false;
}
}
// Otherwise return true to include it
return true;
}
And then apply the filter to your ng-repeat directive like this
<ul ng-repeat="a in user.data | filter:filterUnemployed">
<li>
<md-checkbox>
{{ a.name }}
</md-checkbox>
</li>
</ul>
Note that this will not modify your original collection but will result in a new copy beign displayed in your html since this is usually the desired effect.
Check the sample for a working demo
angular.module('app', [])
.controller('SampleCtrl', function($scope) {
$scope.user = {
data: [{
name: 'John'
}, {
name: 'Mary'
}, {
name: 'Peter'
}, {
name: 'Jack'
}, {
name: 'Richard'
}, {
name: 'Elizabeth'
}]
};
$scope.job = {
data: [{
jobTitle: 'CEO',
user: {
name: 'John'
}
}, {
jobTitle: 'CFO',
user: {
name: 'Mary'
}
}, {
jobTitle: 'Analist',
user: {
name: 'Jack'
}
}]
};
$scope.filterUnemployed = function(value) {
var jobs = $scope.job.data;
for (var i = 0; i < jobs.length; i++) {
if (jobs[i].user.name === value.name) {
return false;
}
}
return true;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="SampleCtrl">
<h1>All Users</h1>
<ul ng-repeat="a in user.data">
<li>
<md-checkbox>
{{ a.name }}
</md-checkbox>
</li>
</ul>
<h1>Unemployed users</h1>
<ul ng-repeat="a in user.data | filter:filterUnemployed">
<li>
<md-checkbox>
{{ a.name }}
</md-checkbox>
</li>
</ul>
<h1>Employed</h1>
<ul ng-repeat="x in job.data">
<li>
<md-checkbox>
{{ x.user.name }}
</md-checkbox>
</li>
</ul>
</div>
You can create a new array made from filtering one of your arrays :
var newArray = job.data.filter(function(jobData) {
return user.data.some(function(userData) {
return userData.name === jobData.user.name;
});
});
Something like this should help :
for(i=0; i<user.data.length;i++){
for(j=0; j<job.data.length;j++){
if(user.data[i].name === job.data[j].name){
user.date.splice(i,1);
}
}
}
I would appreciate some feedback, at least to know that my code helped you
You can use vanilla javascript, jQuery or library undescore.
Please check related links:
How can I merge two complex JSON objects with only unique or different values only showing in resultant array
How to merge two object values by keys
How to merge two arrays of JSON objects - removing duplicates and preserving order in Javascript/jQuery?
Merging two json objects in Java script?

Angular one-time binding on object keys? One-time add like button

Goal: Like button only adds once using Angular.
Theory: Angular's one-time binding will do the trick.
It works here on Angular Expression Doc and on the Plunker example
Problem: Doesn't work in my trials.
Here's the Controller info (from Codecademy's Book App exercise)
$scope.products = [
{
name: 'The Book of Trees',
price: 19,
pubdate: new Date('2014', '03', '08'),
cover: 'img/the-book-of-trees.jpg',
likes: 0,
dislikes: 0
},
{
name: 'Program or be Programmed',
price: 8,
pubdate: new Date('2013', '08', '01'),
cover: 'img/program-or-be-programmed.jpg',
likes: 0,
dislikes: 0
}
View Trial 1
<div ng-repeat="product in products" class="col-md-6">
<div class="thumbnail">
<img ng-src="{{ product.cover }}">
<p class="title">{{ product.name }}</p>
<p class="price">{{ product.price | currency }}</p>
<p class="date">{{ product.pubdate | date }}</p>
<div class="rating">
<p class="likes" ng-click="plusOne($index)">+ {{ ::product.likes}} </p> <!-- here's the damn pickle of a problem -->
<p class="dislikes" ng-click="minusOne($index)">+ {{ product.dislikes }} </p>
</div>
</div>
</div>
RESULT - The button doesn't add
View Trial 2
<div ng-repeat="product in products" class="col-md-6">
<div class="thumbnail">
<img ng-src="{{ product.cover }}">
<p class="title">{{ product.name }}</p>
<p class="price">{{ product.price | currency }}</p>
<p class="date">{{ product.pubdate | date }}</p>
<div class="rating">
<p class="likes" ng-click="plusOne($index)">+ {{ ::product.::likes}} </p> <!-- here's the damn pickle of a problem -->
<p class="dislikes" ng-click="minusOne($index)">+ {{ product.dislikes }} </p>
</div>
</div>
</div>
RESULT - No content shows, only Handlebars / Mustaches / Handlebar-Mustaches {{ }}
I tried implementing your code. The following works for me:
<div ng-repeat = 'product in products'>
<p ng-click="plusOne($index)"> + {{ product.likes}}</p>
<p ng-click="minusOne($index)"> + {{ product.dislikes }}</p>
</div>
Since products is an array, we need to iterate through each of its elements.
$scope.products = [
{
name: 'The Book of Trees',
price: 19,
pubdate: new Date('2014', '03', '08'),
cover: 'img/the-book-of-trees.jpg',
likes: 0,
dislikes: 0
},
{
name: 'Program or be Programmed',
price: 8,
pubdate: new Date('2013', '08', '01'),
cover: 'img/program-or-be-programmed.jpg',
likes: 0,
dislikes: 0
}];
$scope.plusOne = function(index){
if($scope.products[index].likes == 0){
$scope.products[index].likes++;
}
};
I am incrementing the value of likes only if it is 0. Hence it will get incremented only once.
Here's the quick error I see on your code. 1. remove the double colon (::) and change your + sign to -. See screenshot below:
https://i.gyazo.com/ae9d8bb8be57a7b9e9cf840d3f1d705b.png
Also, don't forget to attach your plus and minus properties on your scope(in your controller).
$scope.plusOne = function(index) {
$scope.products[index].likes += 1;
};
$scope.minusOne = function(index) {
$scope.products[index].dislikes += 1;
};
If I understand you correctly, you want the user to only be able to "like" or "dislike" a product once.
One-time binding is used to have the view only watch a value until it is defined, and then stop watching it. The main purpose it to not add extra watches when you are binding data that won't be changing to your view.
In the case of ng-click, the expression is not watched using a $watch, but rather, re-evaluated every time the button is clicked. Even if it worked with one-time and the expression was only evaluated once, that expression would still be used every time the button clicked.
You need to be tracking whether the user has already "liked" a product or not. You can then check that in your plusOne and minusOne functions, and simply short-circuit them if they've already been fired.
Alternately, you could simply have plusOne and minusOne simply replace themselves with empty functions after they fire, like so:
$scope.plusOne = function(index) {
// Do stuff...
// This function disables itself after it fires once
$scope.plusOne = function() {};
};

How to iterate over inner object's properties in an AngularJS template?

I'm in the process of learning AngularJS. I would like to print out a list of objects and iterate over one of the object's inner object's properties. This looked like a standard procedure of using nested loops, however, it doesn't appear to be so simple.
My Controller is setup below. Essentially, it is a list of random vehicles.
var vehicleApp = angular.module("vehicleApp", []);
vehicleApp.controller('VehicleController', function ($scope) {
$scope.vehicles = [{
id: 0,
name: "car",
parts: {
wheels: 4,
doors: 4
}
}, {
id: 1,
name: "plane",
parts: {
wings: 2,
doors: 2
}
}, {
id: 2,
name: "boat",
parts: {
doors: 1
}
}];
});
I'd like to output the vehicles as such:
car
- wheels (4)
- doors (2)
plane
- wings (2)
- doors (2)
boat
- doors (1)
My template that I used was setup as such:
<div ng-app="vehicleApp" ng-controller="VehicleController">
<p ng-repeat="vehicle in vehicles">
{{ vehicle.name }}
</p>
<ul>
<li ng-repeat="(attribute, value) in vehicle.parts">
{{attribute}} ({{value}})
</li>
</ul>
</div>
This produces a list of the vehicles, but not the sub lists of the parts inner object.
Interestingly, enough, when I use {{ vehicle.parts }} it returns a JSON string of the parts inner object. Does AngularJS treat it as a string and hence, it is unable to print out the properties of the parts object?
You didn't enclose the second ngRepeat in the first one:
<div ng-app="vehicleApp" ng-controller="VehicleController">
<p ng-repeat="vehicle in vehicles">
{{ vehicle.name }}
<ul>
<li ng-repeat="(attribute, value) in vehicle.parts">
{{attribute}} ({{value}})
</li>
</ul>
</p>
</div>

Resources