Service module:
baseurl: string = 'https://fireman-7cc06-default-rtdb.firebaseio.com/'
getFunction(): Observable<object> {
return this.http.get(this.baseurl + 'items.json')
}
Component module:
ngOnInit(): void {
this.itemservices.getFunction().subscribe(data => {
this.listItems= data
console.log(this.listItems)
}, err => {
console.log('error' + err)
})
}
View:
<div *ngIf="listItems">
<ul *ngFor="let info of listItems">
<li>{{info.Item_name}}</li>
</ul>
</div>
As the error indicates *ngFor could only loop through iterables like an array. However your API returns an object. Ideally any data conversions must be done in the backend. However, if you so desire, you have the following options in the frontend.
Option 1: convert object to array
You could use JS Object.values() with RxJS map operator to fetch the values of the object as an array.
const input = {"-MhJ49AIHh-53nnF07a1":{"Item_color":"white","Item_name":"amma","Item_price":"369","Item_type":"human"},"-MhJXlVgPBdZ6Q7L0Tnn":{"Item_color":"red","Item_name":"manu","Item_price":"1000","Item_type":"sunny"},"-MhJa9Xzmdq9OvkZyVN7":{"Item_color":"yellow","Item_name":"chinnu","Item_price":"500000","Item_type":"sunny"}}
console.log(Object.values(input))
.as-console-wrapper { max-height: 100% !important; top: 0; }
import { map } from 'rxjs/operators';
baseurl: string = 'https://fireman-7cc06-default-rtdb.firebaseio.com/'
getFunction(): Observable<object> {
return this.http.get(this.baseurl + 'items.json').pipe(
map((res: any) => Object.values(res))
);
}
Option 2: use Angular keyvalue pipe
You could skip the conversion in using Object.values() and use the keyvalue pipe in component template to iterate through an object.
Service
baseurl: string = 'https://fireman-7cc06-default-rtdb.firebaseio.com/'
getFunction(): Observable<object> {
return this.http.get(this.baseurl + 'items.json')
}
Template (*.html)
<div *ngIf="listItems">
<ul *ngFor="let info of listItems | keyvalue">
<li>{{info?.value?.Item_name}}</li>
</ul>
</div>
The problem here is you are trying to loop through an object instead of array. Please check how you define this.listItems.
Declare it as array and assign the values accordingly. Then you should be able to loop through in html code.
listItems = [];
Related
I'm working on a vuejs/(vuex) for state management/firebase project of posts.
So I have a firestore collection of posts (array of objects that have name id owner content and timestamp for creation ...)
I'm retrieving that data by using the onSnapshot methode and it's stored on blogPosts variable... and we show theme all, when a user want to visit a single post it will redirect him to the route of the single post (..../view-post/postid) and i filter that array using the id of the post to have an array of one element (which is the post he visited)
when the filter complete i got all the data and i fill theme on the template like these
<template>
<NavBarView />
<section class="preview" v-if="currentBlog">
<h2>{{ currentBlog[0].blogTitle }}</h2>
<p>Posted on: <span>{{ dateFormat() }}</span> <span v-if="currentBlog[0].editTime">(Last edit:
{{ editFormat() }})</span></p>
<img class="blogCover" :src="currentBlog[0].blogCoverFileURL" :alt="currentBlog[0].blogCoverPhoto">
<div class="html-blog" v-html="currentBlog[0].blogHTML"></div>
<hr />
</section>
</template>
<script>
export default {
data() {
return {
currentBlog: null
}
},
mounted: {
this.currentBlog = this.blogPosts.filter((post) => {
return post.blogID == this.$route.params.id
})
},
computed: {
...mapState(['blogPosts'])
}
//note that i have exported all the requirements such as mapState and firebase functions ... didn't write theme here
}
</script>
now the problem is that the filter is occurring before the data are fetched from firebase and i can't handle that so it's always returning can't read property of null (currentBlog[0])
i found a solution which is sending a getDoc request to firebase but it's a bad idea, (why i send request and i have all the posts here so i can filter it directly and get the specific post) which didn't work!!!!!
any solution??
Looking at your template, I'm under the impression currentBlog should not be an array, as you don't ever have more than 1 element in that array (you're filtering by blogID, which I'm guessing is a unique identifier). You need to .find() the blog entry, not .filter() it:
computed: {
blogID() {
return this.$route.params.id;
},
currentBlog() {
return this.$store.state.blogPosts.find((b) => b.blogID === this.blogID);
}
}
Note: if state.blogPosts does not start as an empty array (as it should), you might want to use:
currentBlog() {
return (this.$store.state.blogPosts || []).find(
(b) => b.blogID === this.blogID
);
}
And now replace all currentBlog[0]s with currentBlog:
<template>
<NavBarView />
<section class="preview" v-if="currentBlog">
<h2>{{ currentBlog.blogTitle }}</h2>
<p>
Posted on: <span>{{ dateFormat() }}</span>
<span v-if="currentBlog.editTime">(Last edit: {{ editFormat() }})</span>
</p>
<img
class="blogCover"
:src="currentBlog.blogCoverFileURL"
:alt="currentBlog.blogCoverPhoto"
/>
<div class="html-blog" v-html="currentBlog.blogHTML"></div>
<hr />
</section>
</template>
Important: make sure in every single method/computed/watch in the script part of the component, if using currentBlog, you're first checking if it's not falsy. Generic example:
computed: {
blogHTML() {
return this.currentBlog?.blogHTML
/* shorthand for:
* return this.currentBlog && currentBlog.blogHTML
*/
}
}
I would suggest that rather than trying to do this operation once on mounted that you instead re-run the filter every time that state.blogPosts is updated. You can easily do this through a computed property. See below:
export default {
data() {
return {
}
},
computed: {
...mapState(['blogPosts']),
currentBlog(){
const posts = this.$store.state.blogPosts;
if(Array.isArray(posts)) {
return posts.filter((post) => {
return post.blogID == this.$route.params.id
});
} else {
return null
}
}
}
}
I am using a query to receive a JSON response. I would like to loop each object (VGF, SSR, BCV, etc..) and output them to premade divs, then the arrays within those objects will loop and create divs within that matching object container.
This is a shortened down version of what I have, and it works mostly. (hopefully, I haven't screwed it up here).
The problem is I have to repeat the searchresult function by copying and pasting the entire function for each object (VGF, SSR, BCV, etc). I would really like to learn how to loop this and not have the same code pasted more than a dozen times.
If I have messed up or left something out of this question, please let me know and I will take care of it.
Here is my ajax request and javascript. I know my problem lies within this loop. I have tried to do a loop inside of a loop, etc. But, when I do that I get no results at all. I am baffled and ready to learn.
$(function getData() {
$("#searchbtn").click(function () {
$.ajax({
url: "action.php",
type: "POST",
data: {},
dataType: "json",
success: function (response) {
console.log(response);
searchresult(response);
}
});
});
});
let searchresult = function(response) {
let container = document.getElementById('VGFresults');
let output = "";
for (let j = 0; j < response.length; j++) {
if (response[j].rcode == "VGF") {
output +=
`<div id="person${response[j].code}">
<p>${response[j].firstname} ${response[j].lastname}</p>
</div>`
}
$(container).html(output);
}
};
Here is my response (Same layout as I am currently receiving but shortened the objects in the arrays).
response =
{"VGF":
[{"code":"TU","rcode":"VGF","firstname":"Tom","lastname":"Riddle"},
{"code":"AZ","rcode":"VGF","firstname":"Harry","lastname":"Potter"},
{"code":"FR","rcode":"VGF","firstname":"Hermoine","lastname":"Granger"}],
"SSR":
[{"code":"HG","rcode":"SSR","firstname":"Walt","lastname":"Disney"},
{"code":"TR","rcode":"SSR","firstname":"H.R.","lastname":"Pickins"},
{"code":"ED","rcode":"SSR","firstname":"Tom","lastname":"Ford"}],
"BCV":
[{"code":"YH","rcode":"BCV","firstname":"Tom","lastname":"Clancy"},
{"code":"RS","rcode":"BCV","firstname":"Robin","lastname":"Williams"},
{"code":"AB","rcode":"BCV","firstname":"Brett","lastname":"Favre"}]}
Here is the HTML that the searchresult function is working with. Currently, it works fine.
To clarify, I would like each object to insert its arrays within the corresponding div. Example:
SSR arrays will go into <div id="SSRresults">
BCV arrays will go into <div id="BCVresults">
From there, each array will create a div within that *results div for each array.
<div id="VGFresults">
<div id="VGFheader">This is the VGF Header</div>
<div id="VGFresults">The Javascript Creates Divs for each array here.</div>
</div>
<div id="SSRresults">
<div id="SSRheader">This is the SSR Header</div>
<div id="SSRresults">The Javascript Creates Divs for each array here.</div>
</div>
<div id="BCVresults">
<div id="BCVheader">This is the BCV Header</div>
<div id="BCVresults">The Javascript Creates Divs for each array here.</div>
</div>
Thanks, any help is much appreciated.
I would do like this:
I declare the response as variable (but sure it will work with your ajax response.
var response =
{"VGF":
[{"code":"TU","rcode":"VGF","firstname":"Tom","lastname":"Riddle"},
{"code":"AZ","rcode":"VGF","firstname":"Harry","lastname":"Potter"},
{"code":"FR","rcode":"VGF","firstname":"Hermoine","lastname":"Granger"}],
"SSR":
[{"code":"HG","rcode":"SSR","firstname":"Walt","lastname":"Disney"},
{"code":"TR","rcode":"SSR","firstname":"H.R.","lastname":"Pickins"},
{"code":"ED","rcode":"SSR","firstname":"Tom","lastname":"Ford"}],
"BCV":
[{"code":"YH","rcode":"BCV","firstname":"Tom","lastname":"Clancy"},
{"code":"RS","rcode":"BCV","firstname":"Robin","lastname":"Williams"},
{"code":"AB","rcode":"BCV","firstname":"Brett","lastname":"Favre"}]}
let searchresult = function(response) {
// let container = document.getElementById('VGFresults');
let output = "";
for (var key in response) {
// skip loop if the property is from prototype
if (!response.hasOwnProperty(key)) continue;
var obj = response[key];
let container = document.getElementById(key+'results');
for (var prop in obj) {
// skip loop if the property is from prototype
if (!obj.hasOwnProperty(prop)) continue;
// your code
//alert(prop + " = " + obj[prop]);
console.log(obj[prop])
output += "<div id="+prop+"><p>"+obj[prop].firstname+" "+ obj[prop].lastname+"</p></div>"
}
}
container.innerText = output;
console.log(output);
};
<div id="VGFresults"></div>
each property VGF, SSR, BCV and so on can be handled now.
EDIT: based on users request, I guess you can edit the selector like this:
let container = document.getElementById(key+'results');
Hey I am following another guide and really struggling to get it working for me. Somewhat new to Angular so I am sure this is a simple issue. Can anyone help me?
The front end shows all the JSON objects at the page load but when I type anything they all disappear.
_ninjaFilter:string
get ninjaFilter():string{
return this._ninjaFilter;
}
set ninjaFilter(value:string){
this._ninjaFilter = value
console.log(this.filteredNinjas, this.ninjaFilter)
this.filteredNinjas = this.ninjaFilter ? this.performFilter(this.ninjaFilter) : this.ninjas
}
performFilter(filterBy: string): any{
filterBy = filterBy.toLocaleLowerCase();
console.log(filterBy)
return this.ninjas.filter(ninja=>{
ninja.name.toLocaleLowerCase().includes(filterBy)
//tried a if statement here to console log match and it does log out match
//also have tried .indexOf(filterby) !== -1
})
}
filteredNinjas: any
ninjas=[{
'name':'yoshi',
'belt':'red'
},
{
'name':'luigi',
'belt':'black'
},
{
'name':'Ryu',
'belt':'green'
}]
constructor(private route: ActivatedRoute) {
this.filteredNinjas = this.ninjas //create new array to filter on
this.ninjaFilter='' //set initial filter string to null
}
and the view:
<h2>Ninja Listing</h2>
<input type='text' id="filter"
[(ngModel)]='ninjaFilter' />
<ul id="ninja-listing">
<li *ngFor="let ninja of filteredNinjas">
<div class='single-ninja'>
<span [ngStyle]="{background: ninja.belt}">{{ninja.belt}} belt</span>
<h3>{{ninja.name}}</h3>
</div>
</li>
</ul>
Here is console log (first page load and then me typing)
(3) [{…}, {…}, {…}] "r"
directory.component.ts:23 r
directory.component.ts:17 [] "ry"
directory.component.ts:23 ry
directory.component.ts:17 [] "ryu"
directory.component.ts:23 ryu
You don't return anything inside your filter function. You should return a condition there:
return this.ninjas.filter(ninja => {
return ninja.name.toLocaleLowerCase().includes(filterBy);
});
I'm working on my web-app and I am facing a problem.
I have an array with several values, which I'd like to display in the frontend as list or something similar.
app.component.ts
in this function I split the tags from the string into an array
splitTags() {
if (this.data.tags != null) {
var tag = this.data.tags.split(";")
console.log(tag)
}
}
ngOnInit() {
this.splitTags()
}
app.component.html
here I d'like to display the tags in a list
<li *ngFor="let tag in tags">
{{ tag }}
</li>
but nothing appears, also if I see the values in the console.
you need to create a property to hold the split result
tags:any[]; // 1️⃣
splitTags() {
if (this.data.tags != null) {
this.tags = this.data.tags.split(";"); // 2️⃣
console.log(this.tags)
}
}
ngOnInit() {
this.splitTags()
}
template
<li *ngFor="let tag of tags">
{{ tag }}
</li>
when I update a value in an array of the store, the interface doesn't reflect the change.
Goal: I'm trying to display a simple minesweeper grid. Once I click on a cell, the object attach to that cell should update isRevealed to true to say is was revealed and Vuejs should add the class dug to that cell.
What's working: isRevealed is switched to true.
What was working: using only props everything was working, but trying to learn VueJS and including Vuex store the UI doesn't update anymore with the array.
Structure:
App shows a grid and grid shows multiple cell.
store.js for Vuex
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
GameGrid: [],
},
mutations: {
revealCell(state, cell) {
state.GameGrid[cell.y][cell.x].isRevealed = true;
console.log("revealCell ", cell);
},
});
GameGrid contain the following:
10 arrays of 10 objects:
{
isRevealed: false,
x: 0,
y: 0
...
}
grid.vue, nothing special, display every cell
<template>
<div class="grid">
<div v-for="row in $store.state.GameGrid" class="row">
<app-cell v-for="col in row" :cell="col"></app-cell>
</div>
</div>
</template>
cell.vue:
<template>
<div
#click="dig"
:class="{dug: cell.isRevealed, dd: cell.debug}">
<span class="text" v-html="cell.display"></span>
<span class="small">{{ cell.code }}</span>
</div>
</template>
<script>
export default {
props: {
cell: {
type: Object
},
},
data: function() {
return {
display: null,
}
},
methods: {
dig() {
this.$store.commit('revealCell', this.cell);
},
}
</script>
Edit accoridng to pirs answer:
Grid.vue file
<template>
<div class="grid">
<div v-for="row in gameGridAlias" class="row">
<app-cell v-for="col in row" :cell="col"></app-cell>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Cell from './Cell.vue';
export default {
components: {
'app-cell': Cell,
},
data: {
gameGridAlias: []
},
methods: {
},
computed: mapState({
gameGridAlias: state => state.GameGrid
})
}
</script>
Note that I get The "data" option should be a function that returns a per-instance value in component definitions. error since data doesn't return a function.
My functions to set isRevealed are on the cell.vue though. I'm still seeing no UI updates (except when I'm saving?). Cells with this.cell.isRevealed = true confirmed with console.log but no change in classes.
Here is how I'm populating GameGrid
This function is calling when the user press a button on the hmm, home page? App.vue file.
generateGrid() {
console.log("generateGrid");
//Generate empty play grid
for(let y = 0; y < this.$store.state.GameGridHeight; y++) {
this.$store.state.GameGrid[y] = new Array();
for(let x = 0; x < this.$store.state.GameGridWidth; x++) {
this.$store.state.GameGrid[y][x] = {
content: 'Empty',
code: 0,
isRevealed: false,
x: x,
y: y,
debug: false,
};
}
}
}, //End generateGrid
In your grid.vue, you should use computed and mapState to update the props
It would be like:
import { mapState } from 'vuex'
// ...
<div v-for="row in gameGridAlias" class="row">
// ...
computed: mapState({
gameGridAlias: state => state.GameGrid
})
More : https://vuex.vuejs.org/en/state.html#the-mapstate-helper
*Note: mapState is optional, but you can handle multiple stores with it and it's robust, i use it for all cases personally.
Edit: Don't use this.$store.state.GameGrid[y] or another this.$store.state vars but only mapState aliases to make it work correctly
state.GameGrid[cell.y][cell.x].isRevealed = true;
add this
state.GameGrid = Array.from(state.GameGrid); //force reflection by cloning array
It is not the most efficient solution but it only one that works for me