I want to make a table like this:
I got the values from the apex in js and it looks like this:
#track data2 = [];
#track data = [];
testClick() {
getExchangeRates({baseCurrency : this.defaultCurrency, dateFrom : this.dateFrom, dateTo : this.dateTo, value : this.defaultValue})
.then(resultJSON => {
let result = JSON.parse(resultJSON);
console.log(result);
console.log('////////');
console.log(result.rates);
let recordsByDates = result.rates;
for (var key in recordsByDates) {
console.log(key);
let record = {
date : key,
USD : recordsByDates[key].USD,
CAD : recordsByDates[key].CAD,
EUR : recordsByDates[key].EUR,
GBP : recordsByDates[key].GBP
}
this.data.push(record);
this.data2 = JSON.stringify(this.data);
}
console.log(this.data);
console.log(this.data2);
})
.catch(error => {
this.error = error;
console.log(error);
});
}
I tried to make a table, but something did not work out for me:
<template for:each={data2} for:item="dat">
<tr key={dat} onclick={testClick}>
<td data-label="data2">
{dat.key}
</td>
<td data-label="data2">
{dat.value}
</td>
</tr>
</template>
please tell me how to fix this
there are a couple of issues in the code:
not referring date value properly
should add a unique key, need in for:each
Here is the updated code
for (var key in recordsByDates) {
let record = {
key: key, // index of object can be used
date : recordsByDates[key].date,
USD : recordsByDates[key].USD,
CAD : recordsByDates[key].CAD,
EUR : recordsByDates[key].EUR,
GBP : recordsByDates[key].GBP
}
}
in HTML, you are referring wrong variable, it should data, in place of data2 (which is stringified).
no need to have table based tags for this type of structure, you can use div for better control.
in HTML file, it should be something like:
<template for:each={data} for:item="dat">
<div key={dat.key} onclick={testClick}>
<div class="date-wrap">
Date: {dat.date}
</div>
<div class="value-wrap">
USD: {dat.USD}
</div>
<div class="value-wrap">
CAD: {dat.CAD}
</div>
<div class="value-wrap">
EUR: {dat.EUR}
</div>
<div class="value-wrap">
GBP: {dat.GBP}
</div>
</div>
</template>
Moreover, you can create a nested array of objects inside the data variable which can help to make the structure more generic.
Related
I have an array that is populated after a .subscribe to my API. Console shows it populated as expected. Accessing an element of the array results to an error thrown because of it being undefined
<div *ngIf="!invoices || invoices.length === 0">
No invoices
</div>
<div *ngIf="invoices || async ">
{{ invoices[0]?.invoice_id || async}}
</div>
If I remove the elvis operator my content will load fine however the console will throw errors InvoicesComponent.html:10 ERROR TypeError: Cannot read property 'invoice_id' of undefined until the array gets populated from the subscribe function.
The invoices array is initialised in my service
invoices: Array<Invoice> = [];
And I populate the array
getInvoices(){
var _invoices = this.invoices;
if(this.afAuth.user){
// users/uid/invoices/invoice_id/
var userRef = this.afs.doc(`users/${this.afAuth.auth.currentUser.uid}`)
userRef.collection('invoices').get().subscribe(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
_invoices.push({
'invoice_id': doc.id,
'customer_company': doc.data().customer_company,
'year_id':doc.data().year_id,
'date_created': doc.data().date_created,
'date_modified': doc.data().date_modified})
});
console.log(_invoices)
});
return _invoices
}
Based on the suggestion of trichetriche, an `Invoice class was created
import { QueryDocumentSnapshot } from "#angular/fire/firestore";
import { of } from 'rxjs'
export class Invoice {
invoice_id: string;
customer_company: string;
date_created: string;
date_modified: string;
year_id: string;
constructor(invoiceDoc: QueryDocumentSnapshot<any>){
this.invoice_id = invoiceDoc.id
this.customer_company = invoiceDoc.data().customer_company
this.date_created = invoiceDoc.data().date_created
this.date_modified = invoiceDoc.data().date_modified
this.year_id = invoiceDoc.data().year_id
}
toObservable(){
return of(this)
}
}
Original
<div *ngIf="!invoices || invoices.length === 0">
No invoices
</div>
<div *ngIf="invoices || async ">
{{ invoices[0]?.invoice_id || async}}
</div>
Edited
<ng-container *ngIf="invoices | async as invoicesSync; else noInvoices">
<p>{{ invoicesSync[0]?.invoice_id || 'No ID for invoice' }}</p>
</ng-container>
<ng-template #noInvoices>
<p>No invoices</p>
</ng-template>
1 - It's | async, not || async : | is a pipe, || is a fallback to a falsy statement.
2 - There should be a single async in your code, which create a template variable through as XXX.
3 - You don't need several conditions. Use a single one with a then statement.
i think you are using the Async pipe in wrong way .
you can passe Observable directly to template and the code will like this :
<div *ngIf="invoices|async as invoicesList; else noInvoices">
{{ invoicesList[0]?.invoice_id}}
</div>
<ng-template #noInvoices>
<div >
No invoices
</div>
</ng-template>
Right so after some research it seems that I was better off subscribing to an observable and dealing with the data as it arrives from my API with the async pipe.
So my final functions look kind of like this:
ngOnInit() {
this.observableInvoices = this.auth.getObservableInvoices().pipe(map(
(data) => data));
console.log(this.observableInvoices)
}
<li *ngFor="let invoice of observableInvoices | async; index as i">
getObservableInvoices(): Observable<any> {
this.observable_invoices = this.afs
.collection(`users/${this.afAuth.auth.currentUser.uid}/invoices`)
.valueChanges() as Observable<any[]>;
return this.observable_invoices;
}
My object looks like:
var models = {
"Test1":{
"name":"Test1",
"members":
{
"T1":{//some more properties},
"T2":{//some more properties}
}
}
"Test2":{
"name":"Test2",
"members":
{
"T1":{//some more properties},
"T2":{//somemore properties}
}
}
}
Provided, user selects a property of object models, I want to display the members property of that selected property of models object in a drop down.
Eg. if user selects Test1 property, drop down should be populated with T1 and T2.
Any suggestions regarding question clarity are appreciated.
As you can't change your object, in your HTML you can have:
<div ng-repeat="model in models">
<div>
<p ng-click="toggleVisibility(model.name)">Name: {{model.name}}</p>
<div ng-repeat="member in model.members" ng-if="model.visibility">
<p>Member Name: {{getMemberName(model.name, $index)}} </p>
<p>Member properties: {{getMemberProperties(model.name, $index)}} </p>
</div>
</div>
</div>
And on your AngularJS Controller:
$scope.toggleVisibility = function (entry) {
$scope.models[entry].visibility = !$scope.models[entry].visibility;
};
$scope.getMemberName = function (entry, $index) {
return Object.keys($scope.models[entry].members)[$index];
};
$scope.getMemberProperties = function (entry, $index) {
var key = Object.keys($scope.models[entry].members)[$index];
return $scope.models[entry].members[key];
};
EDIT: Change the answer to fit the requirements.
I'm trying to show the categories of a service using AngularJS and firebase with the ng-if directive by testing if the entry exist in a table.
The tables are written in a denormalize way as recommended on the Firebase guideline.
- servicecat
- catid
- name : "Category Name"
- services
- srvid1 : true
- srvid2 : true
- srvid3 : true
- services
- srvid
- name: "Service Name"
- Details: "blabla"
- servicecat:
- catid1 : true
- catid2 : true
I am trying to show on a page of a service the categories of the table where the key of the service exists.
<li
ng-repeat="cat in servicecat"
ng-if="checkIfTrue(cat.$id, postSrv.$id)">
{{ cat.name }}<br>
</li>
So I'm trying to get the value a srvid ("true") and return it with the function checkIfTrue(catid,srvid).
$scope.checkIfTrue = function(catid,srvid) {
var ref = fb.child('servicecat/'+catid+'/services/'+srvid);
ref.once('value').then(function(snap){
return snap.exists();
});
}
I don't understand why the ng-if directive does not take the result of the function (which is true when the element exists in the table).
I can see that if I had a "return true;" at the end of the function (out of the ref.once('value') function), it is taken by the ng-if directive.
I was then trying to drop the result of the "once" function in a global variable that I could return at the end of the checkIfTrue function. It seems that the return on the "once" function is a "promise" that the ng-if directive can't read.
So I manage to fetch all categories and all services with the following factory. But when I try to get the selected categories of a specific sercices, It seems I miss something. I was trying to do it by joining two tables but It seems that I shouldn't do this method. Anyway I can't manage to solve this issue.
app.factory("getServices", ["$firebaseArray",
function($firebaseArray) {
var ref = firebase.database().ref().child("services");
return $firebaseArray(ref);
}
]);
app.factory("getServiceCategories", ["$firebaseArray",
function($firebaseArray) {
var ref = firebase.database().ref().child("servicecat");
return $firebaseArray(ref);
}
]);
app.controller("maincontroller", ["$scope","$firebaseArray","$firebaseObject",
"getServices","getServiceCategories",
function($scope,$firebaseArray,$firebaseObject, getServices, getServiceCategories)
{
// GET ALL SERVICES AND ALL CATEGORIES
$scope.services = getServices;
$scope.servicecat = getServiceCategories;
// SHOW & EDIT SERVICE DETAILS AND CATEGORIES
$scope.showDetailsService = function(srvid) {
var ref = fb.child('services/'+srvid);
$scope.selectSrvtoPost = $firebaseObject(ref);
var ref2 = fb.child('services/'+srvid+'/servicecat/');
$scope.selectSrvCatID = $firebaseArray(ref2);
var result = ref2.once('value', function(snap) {
snap.forEach(function(snap2) {
var catid = snap2.key;
var ref3 = fb.child('servicecat/'+catid);
ref3.once('value', function(snap3) {
var catname = snap3.val().name;
console.log("Srvid: "+srvid+" /// Id : "+catid+" /// Name : "+catname);
return snap3;
})
})
})
// ANOTHER TRY WITH $loaded()
var ref2 = fb.child('services/'+srvid+'/servicecat/');
$scope.listcat = $firebaseArray(ref2);
$scope.listcat.$loaded()
.then(function(snap) {
snap.forEach(function(snap2) {
var catid = snap2.key;
var ref3 = fb.child('servicecat/'+catid);
ref3.once('value', function(snap3) {
var catname = snap3.val().name;
console.log("Srvid: "+srvid+" /// Id : "+catid+" /// Name : "+catname);
return snap3;
})
})
})
}
)];
<div ng-controller="mainController">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--3-col">
<h3>List Services</h3>
<ul>
<li ng-repeat="service in services" ng-click="showDetailsService(service.$id)" class="clickable"><strong>{{ service.title }}</strong><br>{{ service.details }} <br><span class="idgrey">{{ service.$id }}</span></li>
</ul>
<h3>List All Categories</h3>
<li ng-repeat="cat in servicecat">{{ cat.name }}</li>
</div>
<div class="mdl-cell mdl-cell--3-col">
<h3>Post Full Service Selected </h3>
<p>
<strong>{{ selectedService.title }}</strong><br>
{{ selectedService.details }}
<h5>Id of Selected Categories</h5>
<ul>
<li ng-repeat="cat in selectedCategoriesId">{{ cat.$id }}</li>
</ul>
<hr>
<h5>Name of Selected Categories</h5>
<ul>
<li ng-repat="cat in selectedSrvCat">ID : {{ findcatscope.name }}</li>
</ul>
</div>
</div>
</div>
I have object like this
{
{
"assetId" : "560",
"assetName" : "Testname",
"message" : "hai",
"timestamp" : 1452585984181
},
{
"message" : "haii",
"timestamp" : 1452585999592
},
{
"assetId" : "560",
"assetName" : "Testname",
"message" : "heloo",
"timestamp" : 1452586221604
}
}
show to this object i am using ng-repeat. My question is
I need to show all message using ng-repat comes under single assetName. but in this object two objects have same assetName and assetId. i need to show the messages both same objects but no need to repeatably show the assetName in top.
How can i only avoid the duplicate assetName and assetId. I used
<div class="container" data-ng-repeat="data in dataList | unique:'assetId'">
But it's completely removing the object. I need the message from all objects.
is it possible.Please help
This is the out put i am expecting.:
I think you should create your own custom filter
yourApp.filter('customuniqueFilter', function() {
return function( array, propertyThatMustBeUnique) {
var newArray = [];
for (i = 0; i++; i < array.length){
if ( notYetInYourArray ){ // I was too lazy to think about a way to do it ;-) feel free to update my answer
newArray.push(array[i]);
}
}
return newArray;
}
});
and then use it like this :
<div class="container" data-ng-repeat="data in dataList | customunique:'assetId'">
How about this:
<div class="container" data-ng-repeat="data in dataList | unique:'assetId'">
<span>{{data.assetId}}</span>
<div data-ng-repeat="data2 in dataList | filter:(data.assetId === undefined ? {assetId:'!'} : data.assetId)">
<span>{{data2.message}}</span>
</div>
</div>
I want to make some kind of project list from a JSON file. The data structure (year, month, project) looks like this:
[{
"name": "2013",
"months": [{
"name": "May 2013",
"projects": [{
"name": "2013-05-09 Project A"
}, {
"name": "2013-05-14 Project B"
}, { ... }]
}, { ... }]
}, { ... }]
I'm displaying all data using a nested ng-repeat and make it searchable by a filter bound to the query from an input box.
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.projects | filter:query | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
If I type "Project B" now, all the empty parent elements are still visible. How can I hide them? I tried some ng-show tricks, but the main problem seems so be, that I don't have access to any information about the parents filtered state.
Here is a fiddle to demonstrate my problem: http://jsfiddle.net/stekhn/y3ft0cwn/7/
You basically have to filter the months to only keep the ones having at least one filtered project, and you also have to filter the years to only keep those having at least one filtered month.
This can be easily achieved using the following code:
function MainCtrl($scope, $filter) {
$scope.query = '';
$scope.monthHasVisibleProject = function(month) {
return $filter('filter')(month.children, $scope.query).length > 0;
};
$scope.yearHasVisibleMonth = function(year) {
return $filter('filter')(year.children, $scope.monthHasVisibleProject).length > 0;
};
and in the view:
<div class="year" ng-repeat="year in data | filter:yearHasVisibleMonth | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:monthHasVisibleProject | orderBy:sortMonth:true">
This is quite inefficient though, since to know if a year is accepted, you filter all its months, and for each month, you filter all its projects. So, unless the performance is good enough for your amount of data, you should probably apply the same principle but by persisting the accepted/rejected state of each object (project, then month, then year) every time the query is modified.
I think that the best way to go is to implement a custom function in order to update a custom Array with the filtered data whenever the query changes. Like this:
$scope.query = '';
$scope.filteredData= angular.copy($scope.data);
$scope.updateFilteredData = function(newVal){
var filtered = angular.copy($scope.data);
filtered = filtered.map(function(year){
year.children=year.children.map(function(month){
month.children = $filter('filter')(month.children,newVal);
return month;
});
return year;
});
$scope.filteredData = filtered.filter(function(year){
year.children= year.children.filter(function(month){
return month.children.length>0;
});
return year.children.length>0;
});
}
And then your view will look like this:
<input type="search" ng-model="query" ng-change="updateFilteredData(query)"
placeholder="Search..." />
<div class="year" ng-repeat="year in filteredData | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Example
Why not a custom $filter for this?
Efficiency: the nature of the $diggest cycle would make it much less efficient. The only problem is that this solution won't be as easy to re-use as a custom $filter would. However, that custom $filter wouldn't be very reusable either, since its logic would be very dependent on this concrete data structure.
IE8 Support
If you need this to work on IE8 you will have to either use jQuery to replace the filter and map functions or to ensure that those functions are defined, like this:
(BTW: if you need IE8 support there is absolutely nothing wrong with using jQuery for these kind of things.)
filter:
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun/*, thisArg*/) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function') {
throw new TypeError();
}
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
map
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
if (thisArg) {
T = thisArg;
}
A = new Array(len);
k = 0;
while(k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[ k ];
mappedValue = callback.call(T, kValue, k, O);
A[ k ] = mappedValue;
}
k++;
}
return A;
};
}
Acknowledgement
I want to thank JB Nizet for his feedback.
For those who are interested: Yesterday I found another approach for solving this problem, which strikes me as rather inefficient. The functions gets called for every child again while typing the query. Not nearly as nice as Josep's solution.
function MainCtrl($scope) {
$scope.query = '';
$scope.searchString = function () {
return function (item) {
var string = JSON.stringify(item).toLowerCase();
var words = $scope.query.toLowerCase();
if (words) {
var filterBy = words.split(/\s+/);
if (!filterBy.length) {
return true;
}
} else {
return true;
}
return filterBy.every(function (word) {
var exists = string.indexOf(word);
if(exists !== -1){
return true;
}
});
};
};
};
And in the view:
<div class="year" ng-repeat="year in data | filter:searchString() | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:searchString() | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | filter:searchString() | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Here is the fiddle: http://jsfiddle.net/stekhn/stv55sxg/1/
Doesn't this work? Using a filtered variable and checking the length of it..
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true" ng-show="filtered.length != 0">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in filtered = (month.projects | filter:query) | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>