I’m working in an angular 6 application and I need a way to tell if my 2D observable array is empty or not.
Here’s my template:
<div *ngFor="let 1DArray of 2DArray$ | async">
<div *ngFor="let item of 1DArray">{{ item.id }}</div>
</div>
This gives me a list of ids.
Here’s my component code:
this.allItems$ = [];
source.forEach(obj => {
const colRef$ = this._firestoreService.colWithIds$(`Collection/${obj.id}/SubCollection`); // a collection of items
this.allItems$.push(colRef$); // an array of collections (2D array)
});
this.2DArray$ = Observable.combineLatest(this.allItems$); // an observable of the 2D array
This gives me an observable of the 2D array.
My problem is that if there are no items to retrieve from the firebase collection, the 2D array will not be empty. Instead, it will consist of a bunch of empty 1D arrays:
[
[],
[],
…
]
I’d like to put a label above the list of items on the page, something like “ITEMS:”. But if there are no items, I’d like to suppress this label.
I could do this by setting a flag, something like itemsExist: boolean. I’d set it like this:
this.itemsExist = false;
this.allItems$ = [];
source.forEach(obj => {
const colRef$ = this._firestoreService.colWithIds$(`Collection/${obj.id}/SubCollection`); // a collection of items
if (colRef$.length > 0) {
this.allItems$.push(colRef$); // an array of collections (2D Array)
this.itemsExist = true;
}
});
this.2DArray$ = Observable.combineLatest(this.allItems$); // an observable of the 2D array
…and then wrap the list in the template with a *ngIf:
<div *ngIf=“itemsExist”>
ITEMS:
<div *ngFor="let 1DArray of 2DArray$ | async">
<div *ngFor="let item of 1DArray">{{ item.id }}</div>
</div>
</div>
<div *ngIf=“!itemsExist”>
There are no items to display.
</div>
But I can’t use .length on an observable. There’s no way that I know of to check how many items exist on an array observable. Except, of course, if you subscribe to it. Then you just get the array and you can check it’s length. I tried something like that:
this.allItems$ = [];
source.forEach(obj => {
const colRef$ = this._firestoreService.colWithIds$(`Collection/${obj.id}/SubCollection`); // a collection of items
this.allItems$.push(colRef$); // an array of collections (2D array)
});
this.2DArray$ = Observable.combineLatest(this.allItems$); // an observable of the 2D array
this.itemCount = 0;
this.2DArray$.subscribe(item2DArray => {
item2DArray.forEach(item1DArray => {
this.itemCount += item1DArray.length;
});
});
Then I check itemCount in an *ngIf.
But then my list doesn’t show up at all even when there are items in it. I also tried subscribing to colRef$ and checking the length of the 1DArray, adding it to allItems$ only if it was greater than 0, but that had the same effect. Can an observable still be used in an *ngFor loop after it’s been subscribed to?
What way is there to check the length of an array observable?
You can achieve this with | async pipe.
Example:
<div *ngIf="(2DArray$ | async)?.length !== 0">...</div>
the mat dataSource is a plain ol typescript object so i created a getter called getCount and in the dataSource have a member variable that exposes the response length. Then on the paginator i simple set the [length] to the dataSource.getCount().
Related
I get array Of data from API and I assign it to two arrays. One of them I bind on it to view checkbox and when I unchecked checkbox I need to splice this object from the second array , but it splice from the two arrays
<div class="row varibles-box" >
<div *ngFor="let variant of variantDetails">
<mat-checkbox (change)="unCheckVariant(variant,$event)" class="example-margin col-4" [checked]="true">
{{variant.englishName}}
</mat-checkbox>
</div>
</div>
this.addProductService.addCustomProducts(this.unitsForm.value).subscribe((res: any) => {
this.variantDetails = res.data ;
this.VariantToSend = res.data;
this.dataSource = new MatTableDataSource(res.data);
console.log(this.VariantToSend)
console.log(this.variantDetails)
});
onCheckVariant(data, event) {
if (!event.checked) {
this.VariantToSend.splice(data, 1)
console.log(this.variantDetails)
console.log(this.VariantToSend)
} else {
this.VariantToSend.push(data)
}
this.dataSource = new MatTableDataSource(this.VariantToSend);
};
Be aware that arrays are "stored" as references in variables.
This means when you write
this.variantDetails = res.data ;
this.VariantToSend = res.data;
Then both your local variables are pointing to the same array. As a result a change to one of them, will effect the other directly.
You could for example use the spread operator.
this.variantDetails = res.data ;
this.VariantToSend = [...res.data];
This solution will create a new array with the same content for the second local variable. But be aware "same content" should be treated as such. If the original array contains objects or arrays (for both they are stored per reference and not per value) you will have the same problem one level deeper. That means if you would change an attribute of a object in the array, then this attribute for this object would ALSO be changed in the other arrays. Because both share the same reference.
A different solution would be not to use "splice" (which changes the array directly) but to use "slice" (if possible) , which will return a new array and leave the original unchanged.
Even there is two arrays but they are pointing to the same reference so whenever u u made change one array it will automatically affect another one.use slice() it returns a new array and leaves the original as it is.
<div class="row varibles-box" >
<div *ngFor="let variant of variantDetails">
<mat-checkbox (change)="unCheckVariant(variant,$event)" class="example-margin col-4" [checked]="true">
{{variant.englishName}}
</mat-checkbox>
</div>
</div>
this.addProductService.addCustomProducts(this.unitsForm.value).subscribe((res: any) => {
this.variantDetails = res.data ;
this.VariantToSend = this.variantDetails.slice();
this.dataSource = new MatTableDataSource(res.data);
console.log(this.VariantToSend)
console.log(this.variantDetails)
});
onCheckVariant(data, event) {
if (!event.checked) {
this.VariantToSend.splice(data, 1)
console.log(this.variantDetails)
console.log(this.VariantToSend)
} else {
this.VariantToSend.push(data)
}
this.dataSource = new MatTableDataSource(this.VariantToSend);
};
I have an array of objects which look something like this:
[
{
id: 1,
settings: "[{object},{object}]", <-- stringifiedJSON
stats: "[{object}, {object}]" <-- stringifiedJSON
},
{
id: 2,
settings: "[{object},{object}]", <-- stringifiedJSON
stats: "[{object}, {object}]" <-- stringifiedJSON
},
]
In the request, I use the map method on the array and parse the strings like so:
settings = [];
stats = [];
getAllArchives(){
this.archiveService.getArchives()
.subscribe(
response => {
if(response) {
this.archives = response.data;
this.stats = this.archives.map(archive => JSON.parse(archive.stats));
this.settings = this.archives.map(archive => JSON.parse(archive.game_settings));
console.log(this.stats);<-this logs the two stats arrays correctly
}
}
)
}
I am trying to then display them in the browser using:
*ngFor="let archive of archives" - there are currently 2 objects in this array
Once the archives array is iterating, if I try to iterate out stat of stats, I find that stats are iterated out to the length of archives and shows all the stats in one section, not the array of stats for that archive id. Settings are the same and so on.
I understand why this is happening, but have not previously encountered a time when I have had to iterate out arrays within arrays which are unique to a particular object.
I have looked about for a precedent, but have not found a similar problem yet.
I am basically looking to iterate out something like this in the end, but can't manage it.
[1]setting, [1]stats
[2]setting, [2]stats
what I get at the minute is:
[1]setting, [2]setting, [1]stats, [2]stats
[1]setting, [2]setting, [1]stats, [2]stats
Is there a better way to set the data before it is displayed in the component? Can I index the ngfor loop effectively to display the data correctly? I am stumped.
Here you can use mapping to iterate over archives array and convert stringified JSONs to proper JSON objects.
this.archives.map(x => {
x.settings = JSON.parse(x.settings);
x.stats = JSON.parse(x.stats);
});
Then you can use *ngFor on settings and stats also. Sample html:
<div *ngFor="let archive of archives">
<p>{{archive.id}}</p>
<div *ngFor="let setting of archive.settings">
<p>{{setting.<property>}}</p>
</div>
<div *ngFor="let stats of archive.stats">
<p>{{stats.<property>}}</p>
</div>
</div>
Here is a working demo
I’m working in an angular 6 application. I have a function that returns an observable of an array of observables, which in turn are observables of arrays.
The results might look something like this:
[
[],
[],
[object, object],
[],
[object],
[],
[object, object, object],
[]
]
As you can see, it’s an array of arrays and some arrays might be empty.
I’d like to list each object in each inner array on the page as follows:
<div *ngFor="let watcher of InvitationService.getInvitations() | async">
<div *ngFor="let invitation of watcher">
{{ invitation | json }}
</div>
</div>
And getInvitations() looks like this:
getInvitations() {
const groupedInvitations$: Observable<any>[] = [];
groups.forEach(group => {
const groupInvites$ = this._firestoreService.collection(`GroupInvitations/${group.groupID}/Invites`);
groupedInvitations$.push(groupInvites$);
});
Return Observable.combineLatest(groupedInvitations$);
}
What I’d like to do is remove the empty arrays from the results returned from combineLatest(…) and flatten the rest into a single one dimensional array.
I understand one can do this by piping through map(…) or flatMap(…) or things like that. But I’ve been trying things like that and nothing’s been working.
For example, I’ve tried this:
Return Observable.combineLatest(groupedInvitations$).pipe(
map(item => {
console.log(item);
});
);
But nothing gets logged to the console.
How can I flatten the array and remove empty arrays? Thanks.
Try using a filter first to remove the empty arrays and the use combineLatest to put them all together.
combineLatest(
groupedInvitations$.pipe(
filter(item => item.length > 0)
)
).pipe(
tap(item => { // Should be an array of arrays
console.log(item);
});
);
as I see it you can put an if condition before this line
groupedInvitations$.push(groupInvites$);
to check if the array length is greater than zero if yes add it to the returned array if not go to the next one
hope that helps
I'm using ng-repeat to (guess) put array content in table.
Content is drawn dynamically, and it works well, when I'm modifying single elements of an array. But when I reload a whole array, there is this moment, when array is reassigned with new value, and ng-repeat draws blank table (which is actually logically correct). Is there a way to delay redrawing of content that way, the ng-repeat ignores the moment when the array is empty? Like the content is switched to new content without the 'clear' time.
I'm assigning new elements to array this way:
items = newItems;
where items is the array ng-repeat uses and newItems is an array of items freshly downloaded from database. The newItems is complete, when the assignment occurres. I'm not doing items = []; before the assignemt.
I'm usign angular 1.3
EDIT:
the ng-repeat:
<tr ng-repeat="order in submittedOrders">
stuff
<\tr>
js:
`$scope.reloadView = function() {
$scope.submittedOrders = OrdersService.getOrdersByStatus(ORDER_STATUS.submitted);
};`
Can it be the that the table is cleared in the first place, before call to database(service takes data from database) and during the wait, the table is cleared?
You may have to make use of Observables and async pipe of Angular.
Here are few steps you can take:
Convert your newItems to a rxjs Subject.
newItems$ = new Subject();
Whenever you get new values for your array, emit them via subject.
this.newItems$.next(newItems);
Make the items an observable of newItems$, and filter out empty arrays.
items = this.newItems$.pipe(
filter((a:any[]) => {
return a.length != 0;
})
);
In your template, use async pipe to iterate over array.
*ngFor="item of items | async"
Below is relevant parts of code that can get you started.
import { Observable, of, from, Subject } from 'rxjs';
import { filter, mapTo } from 'rxjs/operators';
...
newItems$ = new Subject();
items = this.newItems$.pipe(
filter((a:any[]) => {
return a.length != 0;
})
);
...
// A test method - link it to (click) handler of any div/button in your template
// This method will emit a non-empty array first, then, after 1 second emit an empty
// array, and then, after 2 seconds it will emit a non-empty array again with updated
// values.
testMethod() {
this.newItems$.next([3,4,5]);
setTimeout((v) => {
console.log("Emptying the array - should not be displayed browser");
this.newItems$.next([]);
}, 1000);
setTimeout((v) => {
console.log("Updating the array - should be displayed in browser");
this.newItems$.next([3,4,4,5]);
}, 2000);
}
I'm trying to sort each object on it's date value and get each unique date.
Say we have a lot of such objects under a list in Firebase:
{
object123: {
date: "11-12-2016",
title: "These nuts"
}
}
In part of the view I've got something like this:
<div *ngFor="let date of uniqueDates">
<h1>{{date}}</h1>
</div>
To get each unique date I've made an array uniqueDates which are supposed to contain each unique date.
To achieve this I tried to do like so:
/* This was supposed to add each date value from all the objects to uniqueDates,
** but did not work.
** "items" is a FirebaseListObservable
*/
this.items.forEach(
item => {item.subscribe(item => this.uniqueDates.push(item.date))}
);
/*This makes each value unique*/
this.uniqueDates= Array.from(new Set(uniqueDates));
To get each object corresponding with the date I continue as so:
<div *ngFor="let date of uniqueDates">
<h1>{{date}}</h1>
<div *ngFor="let item of items | async"> //Items is a FirebaseListObservable containing all the objects
<div *ngIf="item.date is equal to date">
<p>{{item.someData}}<p>
<div>
</div>
</div>
Is this kind of approach I should go with? If so, how do I push each unique date to an array, the correct way? How would you approach this?
You can try this:
let flags:any = [];
let arrLength = this.items.length;
this.uniqueDates = [];
for(let count=0; i<arrLength; count++) {
if(flags[this.items[count].date]) continue;
flags[this.items[count].date] = true;
this.uniqueDates.push(this.items[count]);
}
Now bind uniqueDates array to view. Hope this will work for you.