I have an angular app, where I need to check security groups on a machine (array of security_groups). I have a function that receives the serverId and checks array of machine details in the security groups attached on the server, that security group is an array of object. I tried to do this but with a map function, but I’m not getting it.
When I go to request a console.log (serverDetails), only a single object returns to me, and not an array of objects, where I should have all objects from all servers, can you help me?
My code:
export class MachinesComponent implements OnInit, OnDestroy, AfterViewInit { public selectedServer: any; public selectedMachine: any; public flavors: any[] = []; public machines: any[] = []; public serverDetails: any[] = []; private alive: boolean = true; public form: FormGroup; constructor(private serverService: MachineService, private openstackService: OpenStackService, private modalService: NgbModal, private formBuilder: FormBuilder, ) { } ngOnInit(): void { this.form = this.formBuilder.group({ flavorRef: new FormControl('') }) forkJoin(this.serverService.getServer(), this.appService.getFlavors()) .pipe(takeWhile(() => this.alive)) .subscribe((data) => { this.machines = data[0]; this.flavors = data[1]; this.machines.map((item) => { this.serverService.getServerById(item.id) .pipe(takeWhile(() => this.alive)) .subscribe((data: any) => this.serverDetails = data) }) }) this.setExpanded(); } findSelectedMachineDetails(serverId): any { return this.serverDetails.map((item) => { if(item.id === serverId.id) { return item['security_groups'].map(item => item.name) } }) }
My Html:
<tbody> <ng-container *ngFor="let machine of machines"> <tr > <td (click)="machine.expanded = !machine.expanded"> <div class="user-info"> <div class="user-info__img"> <img src="./assets/img/cloud4.svg" alt="Usuário Img"> </div> <div class="user-info__basic"> <h5 class="mb-0">{{machine.name}}</h5> </div> </div> </td> <td (click)="machine.expanded = !machine.expanded"> <span class="active-circle bg-success"></span> Status </td> <td (click)="machine.expanded = !machine.expanded">{{machine?.flavor?.disk}} GB</td> <td (click)="machine.expanded = !machine.expanded">{{machine?.flavor?.ram}} GB</td> <td>[{{findSelectedMachineDetails(machine)}}]</td> <td (click)="machine.expanded = !machine.expanded">{{machine?.flavor?.vcpus}}x 2.8Mbps</td> </ng-container> </tbody>
My response GET serversById
[ { "name": "hello", "id": "1879f47f-1c5e-464b-bb76-e7cc13ef426e", "status": "ACTIVE", "hostId": "30f3f23f5668cf5ae13f0951fb8364063c43dc5be40f9df202a97fe8", "flavor": { "id": "21f46a72-7f4f-40c5-9f1f-0100fadbc226", "disk": "10", "ram": "4000", "swap": "", "vcpus": "4" }, "security_groups": [ { "name": "default" }, { "name": "test" }, { "name": "again" } ] }, { "name": "hell3", "id": "1879f47f-1c5e-464b-bb76-e7c313ef426e", "status": "ACTIVE", "hostId": "30f3f2343668cf5ae13f0951fb8364063c43dc5be40f9df202a97fe8", "flavor": { "id": "21f46a72-7f4f-40c5-9f1f-0100fadbc226", "disk": "10", "ram": "4000", "swap": "", "vcpus": "4" }, "security_groups": [ { "name": "default" }, { "name": "test" }, { "name": "again" } ] },{ "name": "hell2", "id": "1879f47f-1c5e-464b-bb76-e7c213ef426e", "status": "ACTIVE", "hostId": "30f3f23f5668cf5ae13f0951fb8364063c43dc5be40f9df202a97fe8", "flavor": { "id": "21f46a72-7f4f-40c5-9f1f-0100fadbc226", "disk": "10", "ram": "4000", "swap": "", "vcpus": "4" }, "security_groups": [ { "name": "default" }, { "name": "test" }, { "name": "again" } ] } ]
Advertisement
Answer
Check this piece of code. From quick glance it looks like you’re rewriting the array instead of pushing to it:
this.machines.map((item) => { this.serverService.getServerById(item.id) .pipe(takeWhile(() => this.alive)) .subscribe((data: any) => this.serverDetails.push(data)) //serverDetails is initialized with empty array so it should work })
EDIT:
Problem is that you are mixing synchronous code with asynchronous code and this is prone to cause such strange issues.
Let’s consider the code below. When the machines
are rendered in ngFor
cycle serverDetails
are most likely not initialized yet (so the array is either empty or incomplete) and since it’s a synchronous code, it is not notified of the updates that to serverDetails
happen bit later.
<td *ngFor="let secGroup of findSelectedMachineDetails(machine)" > .... </td>
I don’t see the whole code of yours I would suggest some changes and only display data, when everything is indeed finished.
forkJoin( this.serverService.getServer(), this.appService.getFlavors() ) .pipe( takeWhile(() => this.alive), ) .subscribe(([machines, flavors]) => { this.flavors = flavors; this.machines = machines.map((item) => ({ ...item, // spreads the object -> makes copy of it securityGroups: this.serverService.getServerById(item.id) .pipe(map(server => server["security_groups"])) // do not subscribe to this, async pipe will take care of it })) })
This will get you the data you want and then processes the machines array where for every machine it adds another property to the object – securityGroups
. This is an async property, where it expects the result from getServerById()
call.
Then just adjust the HTML and it should work. I haven’t compiled the code and did some assumptions so be sure to check it and maybe adjust some mappings.
<td *ngFor="let secGroup o machine.securityGroups | async"> {{secGroup.name}} </td>