I have an array of devices.
For each device I want to set the default name ("DeviceX" - X its a sequence number) but some of them might be already used/occupied. So I need make an request to check if name already exists (ifDeviceExists() returns true or false in response). If the name is already occupied it should increase the number and once again send request to check if that name (e.g. "Device2") already exist. If name is not occupied then it should set name of that device (device.name = "Device2").
For example - occupied devices names: "Device2" and "Device4". Then the names of new created devices should be: "Device1", "Device3", "Device5", "Device6" ...
So the devices array should be: [{name: "Device1"}, {name: "Device3"}, {name: "Device5"}, {name: "Device6"}, ... ]
The main problem for me here is that checking if name exists is async action and I do not know how to handle that (where to send request and when to subscribe) Could anyone help me with that?
ifDeviceExists(name: string): Observable<boolean> {
return [ 'Device2', 'Device4' ].includes(name) ? of(true) : of(false);
}
findNextDeviceName(indexWrapper: { index: number }, device: Device):
Observable<{ device: Device, name:string; index: number }> {
let currentName: string;
let currentIndex = indexWrapper.index;
return interval(0).pipe(
// send requests to check name sequentially until it returns false and filter passes event
// to first() operator which will stop interval loop, NOTE: if ifDeviceExists() allways returns true,
// there will be infinite loop
concatMap(interval => {
currentIndex = currentIndex + interval;
currentName = `Device${currentIndex}`;
return this.ifDeviceExists(currentName);
}),
filter(exists => !exists),
first(),
map(() => ({device, name: currentName, index: currentIndex})),
)
}
renameDevices(){
const devices = [{id: 101}, {id: 102}, {id: 103}, {id: 104}];
// this object keeps last index and allows to change it between concatMap iterations
const indexWrapper = {index: 1};
from(devices).pipe(
concatMap(device => this.findNextDeviceName(indexWrapper, device)),
map(result => {
indexWrapper.index = result.index + 1;
result.device.name = result.name;
return result.device;
}),
toArray()
).subscribe({
next: (devices) => {
console.log('Devices with names', devices);
}
})
}
As you didn't provide extra info,so I provided solution as per my understanding and assumption
ifDeviceExists returning Observable<boolean> and returning true/false as per availability statusAs ifDeviceExists returning observable,so in order to use it's result inside pipe we need to use higher order observable,in this case I used concatmap
About concatMap
For indexing used concatMap index value
devices = [
{ id: 1, status: 'available' },
{ id: 2, status: 'used' },
{ id: 3, status: 'available' },
{ id: 4, status: 'occupied' },
{ id: 5, status: 'available' },
{ id: 6, status: 'available' },
{ id: 7, status: 'available' },
];
ngOnInit(): void {
this.setDevicesName().subscribe(console.log);
}
ifDeviceExists(deviceId: number): Observable<boolean> {
let device = this.devices.find((d) => d.id === deviceId);
return of(device.status === 'used' || device.status === 'occupied');
}
setDevicesName(): Observable<any> {
return from(this.devices).pipe(
concatMap((device, index) =>
this.ifDeviceExists(device.id).pipe(
tap(() => index++),
filter((res) => !res),
map((res) => {
device['name'] = 'Device' + index;
return device;
})
)
),
toArray()
);
}
const reserved = ["device2", "device5"]; //any list that contains device<X> elements
const devices = [{},{},{},{},{},{},{},{},{},{}];
let num = 0;
for (let device of devices) {
if (!reserved.includes("device" + num)) {
device.name = 'device' + num
};
num++
}
console.log(devices)