I just can't work out how to list/loop through items from an array into my component. All the online tutorials and SO answers make sense, but my code won't respond in a like fashion.
My scenario is this: A user selects an option from a menu and...
switch(which){
:
case 'who': {
this.getStaffList('stafflist');
break;
}
:
the database is called and returns an array...
getStaffList(value:string){
this.targetID = value;
this.service.getStuff(this.targetID).subscribe(
items => {
console.log(items[0].fname); <---this yields 'Sue'
this.title = "Your staff list";
}, error => {
}, () => {
}
);
}
The PHP view of the array (before JSON.encode) is:
Array
(
[0] => Array
(
[userID] => 6551
[certID] => SB287
[fname] => Sue
[lname] => Bennett
)
[1] => Array
(
[userID] => 6568
[certID] => MF6568
[fname] => Marion
[lname] => Ferguson
)
:
Back in Angular, the very simple template is:
<div id="stafflist" class="mainbox" *ngIf="bListStaff">
<div class="panel panel-info">
<div class="panel-heading">
<div class="panel-title">{{title}}</div>
</div>
<div style="padding-top:25px" class="panel-body">
{{items[0].fname}} <!--This generates a '..._co.items is undefined' error
<ul>
<li *ngFor="let item of items; let i = index">{{i}} {{item}}</li> <--- this yields nothing/zilch
</ul>
</div>
</div>
</div>
The '..._co.items is undefined' error that I'm receiving suggests that the items array isn't known outside of getStaffList, but I don't understand why that is (if it is) and don't understand what's missing in my approach.
You are not assigning items inside the subscribe, create a variable named items of type any and assign the value inside the subscription,
getStaffList(value:string){
this.targetID = value;
this.service.getStuff(this.targetID).subscribe(
items => {
this.items = items;
this.title = "Your staff list";
}, error => {
}, () => {
}
);
}
also since the request is asynchronous use safe navigation operator to check if the value is present before the values are being assigned,
<div style="padding-top:25px" class="panel-body">
{{items[0]?.fname}} <!--This generates a '..._co.items is undefined' error
<ul>
<li *ngFor="let item of items; let i = index">{{i}} {{item}}</li> <--- this yields nothing/zilch
</ul>
</div>
Just define a property items in your component class and set it like this:
getStaffList(value:string){
this.targetID = value;
this.service.getStuff(this.targetID).subscribe(
items => {
console.log(items[0].fname); <---this yields 'Sue'
this.title = "Your staff list";
this.items = items;
}, error => {
}, () => {
}
);
}
The scope of the items in your subscribe is only inside that function so it's not accessible from the outside.
Related
I have an array that contains as follows. I have successfully mapped the data and put it into the elements. but there are some values I couldn't get correctly. I have added a console.log to figure out the data. data is correct in the array, I want to get the Seats ("A1,B1") in here <h5><small>{seatNos.seats}</small></h5> but nothing is displaying. appreciate any help.
Data array
"data": {
"userBookings": [
{
"transactionId": 6357604,
"totalAmount": 350,
"createdAt": "2021-08-05 02:16:48",
"movieName": "Mortal Kombat",
"theaterName": "XxX Cinema",
"showTime": "17:30",
"refcode": "0016048GIN210805I",
"bookedSeats": [
{
"seatType": "Comfert",
"seats": "A1,B1",
"childTickets": 1,
"totalTickets": 2,
"bookedDate": "2021-08-05"
}
]
}
]
},
code
<div className="col-xl-5 col-lg-5 col-md-7 col-sm-7 col-xs-9 col-6" style={{paddingLeft:25}}>
<h5><b>{bookingsData.movieName}</b></h5>
<h5>{bookingsData.theaterName}</h5>
<h5><small>{bookingsData.showTime}</small></h5>
{bookingsData.bookedSeats.map((seatNos) => {
console.log(seatNos.seats);
<h5><small>{seatNos.seats}</small></h5>
})}
{/* <h5><small>{bookingsData.bookedSeats.seats}</small></h5> */}
</div>
You need to return element in map, and set key for this element:
{bookingsData.bookedSeats.map((seatNos, index) => {
console.log(seatNos.seats);
return <h5 key={index}><small>{seatNos.seats}</small></h5>
})}
Your arrow function inside the .map() doesn't return a value. You need a return before the JSX:
{bookingsData.bookedSeats.map((seatNos) => {
console.log(seatNos.seats);
return <h5><small>{seatNos.seats}</small></h5>
})}
Or to use the implicit return arrow function syntax (Either round brackets, or no brackets: () => () or () => returnedValue)
{bookingsData.bookedSeats.map((seatNos) => <h5>
<small>{seatNos.seats}</small>
</h5>)}
This is because you forgot the return
array.map((item) => {
return (
<p>{item.value}</p>
)
})
In React, how can I turn , characters into new lines?
Suppose we have an array like this:
const items = [
{
label: "Animals",
value: "Puppies, Kittens, Bunnies"
},
// ...
];
And we display it like this:
<div>
{items.map(item => (
<div style="left">
{item.label}
</div>
<div style="right">
{item.value}
</div>
))};
</div>
How can I turn all , characters in the value keys of the array items into new lines?
Current Output:
Animals Puppies, Kittens, Bunnies
Desired Output:
Animals Puppies
Kittens
Bunnies
{item.value.split(", ").map((line, i) => <div key={i}>{line}</div>)}
is the simplest, if putting each item in a div is okay for you.
The other, more complex option is to add <br>s between each line, and wrap those in a React.Fragment:
function addBrs(items) {
const children = [];
items.forEach((item) => {
children.push(item);
children.push(<br />);
});
children.pop(); // Remove last extraneous BR
return React.createElement(React.Fragment, {}, ...children);
}
// ...
{addBrs(item.value.split(", "))}}
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 have a list Entity called Awards which has a Name (string) and YearGiven (Entity) as its fields.
I want to show all awards grouped by Year.
ie
2017
---Bob
---Sue
2016
---Fred
2015
etc
Here is my template:
#using ToSic.SexyContent
#functions
{
// variable which will contain the sorted categories
IEnumerable<dynamic> sortedCategories;
// Prepare the data - get all categories through the pipeline
public override void CustomizeData()
{
// get all categories of these questions, then get the distinct entities
// this could all be done on 1 line, but it would be harder for people who don't know LINQ yet
var awardsInThisModule = AsDynamic(App.Data["Awards"].List);
var categoriesUsed = awardsInThisModule.SelectMany(q => ((List<DynamicEntity>)q.YearGiven));
var distinctCategories = categoriesUsed.Select(AsEntity).Distinct(); // Distinct only works reliably when cast as entity
sortedCategories = AsDynamic(distinctCategories).OrderBy(q => q.Year);
}
}
<link rel="stylesheet" href="#App.Path/assets/awards.css" data-enableoptimizations="true" />
#foreach (var cat in sortedCategories)
{
<h3> #cat.Year</h3>
foreach (var q in AsDynamic(App.Data["Awards"].List).Where(t => t.Name == "Bob").OrderBy(q => q.Name))
{
//this works fine and puts Bob against each year
<h2>#q.Name</h2>
}
foreach (var q in AsDynamic(App.Data["Awards"].List).Where(t => t.Year.Select(a => AsDynamic(a).Year) == "2017"))
{
//this is what I actually want to do and fails
<h2>#q.Name</h2>
}
<br />
}
I started by changing the Where clause to t.YearGiven == 2016 but that gives an error "Operator '==' cannot be applied to operands of type 'System.Collections.Generic.List' and 'int' a" - I assume because YearGiven is an Entity and so is actually a List<>.
So then I changed to the next foreach in the code and got this error:-
"Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type."
I can't find any template example that does what I'm trying to do and nothing I do works.
N.B. I've hardcoded '2017' in there for now to keep things simple but it will obviously be doing each Year found in the outer loop.
Here is a simple example with a similar schema if you want to adapt it. I am basically using a variable (currCat) to keep track and handle the 'on-change of category'. Hopefully you can ignore all the expando/collapse stuff. Here is what the final looks like:
http://www.blackandco.com/Vendor-Linecard
<div id="vendor-list" role="tablist" class="small">
#{
int currCat = 0;
int firstCo = 851; // Abrasives
foreach (var aCat in AsDynamic(App.Data["CompanyCategories"])
.Where(c => c.CategoryActiveYN == true)
.OrderBy(c => c.CategoryName)
)
{
currCat = aCat.EntityId;
<div class="card">
<div class="card-header" role="tab" id="#string.Format("{0}{1}", "heading", #currCat)">
<h5 class="mb-0#((currCat == firstCo) ? "" : " collapsed")" data-toggle="collapse" href="#string.Format("{0}{1}", "#collapse", #currCat)"
aria-expanded="#((currCat == firstCo) ? "true" : "false")" aria-controls="#string.Format("{0}{1}", "collapse", #currCat)">
#aCat.CategoryName
</h5>
</div>
<div id="#string.Format("{0}{1}", "collapse", #currCat)" class="collapse#((currCat==firstCo) ? " show" : "")" role="tabpanel" aria-labelledby="#string.Format("{0}{1}", "heading", #currCat)" data-parent="#accordion" aria-expanded="#((currCat==firstCo) ? "true" : "false")">
<div class="card-body">
<ul>
#foreach (var vComp in AsDynamic(App.Data["Company"])
.Where(v => v.CompanyActiveYN && v.IncludeOnVendorCards)
.OrderBy(v => v.CompanyName)
)
{
foreach (var vCat in vComp.CompanyCategory)
{
if (vCat.EntityId == currCat)
{
<li>#vComp.CompanyName<span></li>
}
}
}
</ul>
</div>
</div>
</div>
}
}
</div>