Splice object from array - arrays

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);
};

Related

How do I partition an observable of an array of values in one step still utilizing async pipe?

I receive an observable containing an array of objects. I want to partition the array into two based on a property of each object. I'd like to do this in such a way that the resulting arrays are in observables that can use the async pipe in the template, rather than having to manually manage subscriptions. I'm using RxJS 6 with Angular 9.
The trouble I'm having is in trying to partition based on individual elements while returning arrays to the destructuring assignment. I have tried variations of
public groupA: Observable<IItem[]>;
public groupB: Observable<IItem[]>;
...
// assigned values are not arrays
[this.groupA, this.groupB] = partition(
this.service.item$.pipe(
// split array into stream of its elements
concatAll(),
),
(item: IItem) => belongsInGroupA(item.myGroup)
);
and
[this.groupA, this.groupB] = partition(
this.service.item$,
(items: IItem[]) => {
return ???;
// How do I partition each item without first splitting into individual elements?
}
);
StackBlitz demo
I know I could build the two arrays using something like map (or, more appropriately, tap), but I don't know how to auto-(un)subscribe using async with that, if it's even possible.
partition seems intended to divide streams of one item at a time, but my need feels like something well within the domain of Angular+RxJS and I just lack the understanding. Can you help?
private version1() {
[this.groupCabbages, this.groupKings] = partition(
new Observable((observer) => {
source.subscribe((res: IItem[]) => {
for(let i = 0; i< res.length; i++) {
observer.next(res[i]);
}
observer.complete();
})
}),
(item: IItem) => {
return item.myGroup === 'cabbages';
}
);
this.groupCabbages = this.groupCabbages.pipe(toArray());
this.groupKings = this.groupKings.pipe(toArray());
// elements are correctly sorted, but streamed one at a time
this.groupCabbages.subscribe(res => {
console.log('v1 cabbages', res);
});
this.groupKings.subscribe(res => {
console.log('v1 kings', res);
});
}
Working Stackblitz :- https://stackblitz.com/edit/rxjs-cazbuc?devtoolsheight=33&file=index.ts
You can transform your array elements to observable emits by mergeMap, then groupBy the property, and emit an arrays for each group.
Something like this perhaps:
private versionZ() {
source
.pipe(
mergeMap((items: IItem[]) => from(items)),
groupBy((item: IItem) => item.myGroup),
mergeMap((group: Observable<any>) => group.pipe(toArray()))
)
.subscribe(t => console.log(t));
}
Result:
You just have to apply toArray() operator
this.groupCabbagesForView = this.groupCabbages.pipe(toArray())
// in view
groupCabbagesForView | async
then it is consumable by the view async pipe. View will get the list of items instead of item by item.
RxJS partition is used to split observable streams. You on the other hand need to create two different streams based on a condition applied to elements of each notification of the stream. If the number of elements in each emission is fairly limited and if you need to create only a limited number of observables, I'd say it'd be quicker to create multiple observables individually using the map operator.
Controller
ngOnInit() {
this.cabbages$ = this.source$.pipe(
map((items: IItem[]) =>
items.filter((item: IItem) => item.myGroup === 'cabbages')
)
);
this.kings$ = this.source$.pipe(
map((items: IItem[]) =>
items.filter((item: IItem) => item.myGroup === 'kings')
)
);
}
Template
<ng-container *ngIf="(cabbages$ | async) as cabbages">
Cabbages:
<div *ngFor="let cabbage of cabbages">
{{ cabbage | json }}
</div>
</ng-container>
<br>
<ng-container *ngIf="(kings$ | async) as kings">
Kings:
<div *ngFor="let king of kings">
{{ king | json }}
</div>
</ng-container>
Working example: Stackblitz
Note: Each async pipe would trigger a separate subscription to the source$ observable stream. If too many async pipes are needed it might lead to performance issues.
If I understand your issue correctly, then the essence is that you want to obtain 2 observables with values derived from a single observable. The general way to do this would be to multicast the original, then use that to create the derived observables.
temp$ = obs1$.pipe(share());
obs2$ = temp$.pipe(map(value => ...));
obs3$ = temp$.pipe(map(value => ...));
So to apply this to your example:
temp$ = this.service.item$.pipe(
map(arr => splitArr(arr, belongsInGroupA));
// splitArr returns [arrA, arrB]
);
this.groupA = temp$.pipe(map(value => value[0]));
this.groupB = temp$.pipe(map(value => value[1]));
The remaining question is how to efficiently split the array, but this is normal array manipulation. If a single-pass is important then use reduce.

How can I check the size of an array observable?

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().

Is it possible to make ng-repeat delay a bit redrawing of array content

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);
}

How to force Object type returned from an Observable to an array of objects in Angular?

I'm obtaining a response using GET like this.
constructor(private http: HttpClient) { }
items: Item[];
stuff: any[];
ngOnInit() {
const url = ...;
this.http.get(url)
.subscribe(next => {
console.log(next);
console.log(next.length);
this.items = next;
this.stuff = next;
});
}
I can see that there's 6 elements and what they are (checking the console). However, neither of the fields seem to the the array. I'm trying to iterate out the elements using the ngFor directive but seeing nothing or just a single line.
<div ngFor="let item of items">
->{{item?.id}}
</div>
<div ngFor="let item of stuff">
->{{item?.id}}
</div>
I know that I can resolve it by exposing the data through a service like this but partly, I want to do a quicky here and learn how to do it; partly, I'm going to have the very same problem in the service code instead.
I've tried using map and forEach on the next value but I got the error saying that Object isn't an array. The IDE suggested adding the work array to the syntax so it becomes next.array.forEach but that didn't even got executed, producing a lot of red ink.
What should I do? (Not sure what to google for at this stage.)
donkeyObservArray: Observable<Donkey[]>;
donkeyArray: Array<Donkey>;
this.donkeyObservArray.subscribe(donk=> {
this.donkeyArray = donk;
console.log(this.donkeyArray);
and to be happy...
Or get typed:
donkey: Donkey= null;
getDonkey(): Donkey{
this.donkey = new Donkey();
this._http.get<Donkey>(...your ws link)
.subscribe(data => {
this.donkey = data;
You can either convert the object to an array before passing to template
object-2-array-angular-4
or use a custom pipe to transform object to array

How to set knockout variable from array element

I´m having some finger trouble with this:
<p data-bind="text: $root.myArray()[0]"></p>
<hr>
myVal = <span data-bind="text: $root.myVal()></span>
In viewmodel:
self.myArray = ko.computed(function() {
var categories = ko.utils.arrayMap(self.selectedItems(), function(item) {
return item.id();
});
return categories.sort();
});
self.myVal = ko.observable(self.myArray()[0]);
Printing myArray shows the correct value, but myVal is blank. Why?
(and yes, I only want the first value in the array).
Also, I´d like it as a number when I save to database. Do I need to do some kind of typecast then?
The difference is probably that you are setting the value of myVal to the first object in the array before the array is actually populated. To see this if you console.log(self.myArray()[0]) right before self.myVal is set you should see what is being set. Since you are setting it only once it will not subscribe to change in the array. If you wanted to do this you would use a computed.
self.myVal = ko.computed(function () { return self.myArray()[0]; });
The computed will now fire whenever anything is added to or removed from myArray

Resources