Angular Local HTTP request cost for responses
In my service class, I will loop the http get request and use rxjs forkJoin to combine all responses into observables, which I return to my component. For each response returned, I need to add two properties to the json (readySystem, which is an object and serviceType, which is a string). The meaning of each of them is different for each iteration of the loop.
How do I store / store / store the values ββfor both and and match / add them to the correct answer?
With the way I tried to do it below, the values ββfor both are the same in every response returned in the final observable.
getServices() {
for (var x = 0; x < this.service.items.length; x++ ){
var num = Object.keys(this.service.items[x].links).length;
for (var key in this.service.items[x].links) {
var systemName = this.service.items[x].systemName;
var environment = this.service.items[x].environment;
var server = this.service.items[x].server;
var port = this.service.items[x].port;
var linkName = this.service.items[x].links[key];
var serviceType = key;
this.observables.push(
this.http.get('http://localhost:3000/myapi/get/service?server=' + server + '&service=' + linkName)
.map((res:Response) => {
var output = res.json()
for (var obj in output) {
if (output.hasOwnProperty(obj)){
var readySystem = new System(systemName,
environment,
server,
port,
linkName);
output[obj].System = readySystem;
output[obj].serviceType = serviceType;
}
}
return output;
})
);
}
};
return Observable.forkJoin(this.observables);
};
Update . With the suggested code changes given in the answer below, I get an output like:
0: Array(33)
1: System
systemName: "my system"
environment: "my environment"
etc.
2: "myservice"
3: Array(35)
4: System
etc.
5: "myotherservice"
However, the following is required:
0: Array(33)
0: Object
> System
systemName: "my system"
environment: "my environment"
etc.
serviceType: "myservice"
1: Object
> System
systemName: "my system"
environment: "my environment"
etc.
serviceType: "myotherservice"
etc.
1: Array(35)
0: Object
source to share
Just add these values ββto fork using Observable.of
function getObsevrables(observables, readySystem, serviceType) {
return Rx.Observable.forkJoin([
Rx.Observable.of(readySystem),
Rx.Observable.of(serviceType),
...observables
]);
}
getObsevrables([Rx.Observable.of(1), Rx.Observable.of(2)],
{ready: true}, 'type1')
.subscribe(x=>console.log(x))
UPDATE:
I tried to update your code.
getServices() {
for (var x = 0; x < this.service.items.length; x++) {
var num = Object.keys(this.service.items[x].links).length;
for (var key in this.service.items[x].links) {
var systemName = this.service.items[x].systemName;
var environment = this.service.items[x].environment;
var server = this.service.items[x].server;
var port = this.service.items[x].port;
var linkName = this.service.items[x].links[key];
var serviceType = key;
var readySystem = new System(systemName,
environment,
server,
port,
linkName);
// getObservables([Observable.of(1), Observable.of(2)], readySystem, serviceType).subscribe(x => console.log(x));
this.observables.push(
Observable.combineLatest(
this.http.get('http://localhost:3000/myapi/get/service?server=' + server + '&service=' + linkName)
.map((res: Response) => {
var output = res.json();
return output;
});
},
Observable.of(serviceType),
Observable.of(readySystem)
)
);
}
}
source to share
This could be a simple classic JS closure problem. When all the asynchronous calls ( http.get
and Observable.forkJoin
) are allowed, all functions with domain var
(x, num, key, systemName , environment, server, port, ... , etc.) within loops for
fixed to their last value. The simplest solution would be to use ES6 declarations with blocks let
or const
.
So try changing your code to something like:
for (let x = 0; x < this.service.items.length; x++ ){
let num = Object.keys(this.service.items[x].links).length;
for (let key in this.service.items[x].links) {
let systemName = this.service.items[x].systemName;
let environment = this.service.items[x].environment;
let server = this.service.items[x].server;
let port = this.service.items[x].port;
let linkName = this.service.items[x].links[key];
let serviceType = key;
let url = `http://localhost:3000/myapi/get/service?server=${server}&service=${linkName}`;
this.observables.push(this.http.get(url)
.map((res:Response) => {
let output = res.json();
for (let obj in output) {
if (output.hasOwnProperty(obj)) {
let readySystem = new System(systemName,
environment,
server,
port,
linkName
);
output[obj].System = readySystem;
output[obj].serviceType = serviceType;
}
}
return output;
}
));
}};
return Observable.forkJoin(this.observables);
};
and there is no reason at all not to use the new ES6 features when coding with Angular 2/4.
You can also rewrite your first loop for
using a function Array.forEach()
(I assume it this.service.items
is an array because of its length property):
this.service.items.forEach(item => {
let num = Object.keys(item.links).length;
for (let key in item.links) {
let systemName = item.systemName;
let environment = item.environment;
let server = item.server;
let port = item.port;
let linkName = item.links[key];
let serviceType = key;
let url = `http://localhost:3000/myapi/get/service?server=${server}&service=${linkName}`;
this.observables.push(this.http.get(url)
.map((res:Response) => {
let output = res.json();
for (let obj in output) {
if (output.hasOwnProperty(obj)) {
let readySystem = new System(systemName,
environment,
server,
port,
linkName
);
output[obj].System = readySystem;
output[obj].serviceType = serviceType;
}
}
return output;
}
));
}};
return Observable.forkJoin(this.observables);
});
source to share
This is a closure problem. Based on your source code:
for (var key in this.service.items[x].links) {
var systemName = this.service.items[x].systemName;
here you might think that every time the block is executed you are creating a new variable systemName
. Indeed, the variable is created once and assigned a new value every time the block is executed. The difference is important because below you are making an asynchronous call http.get
:
this.observables.push(
this.http.get('http://localhost:3000/myapi/get/service?server=' + server + '&service=' + linkName)
.map(res => {
var output = res.json()
for (var obj in output) {
if (output.hasOwnProperty(obj)){
var readySystem = new System(systemName,
here the callback inside is map
not executed directly in the loop when it systemName
has the correct value. It is simply stored in memory for later execution (when it http.get
returns a result). The stored callback body does not carry a value systemName
, only a variable reference.
Now, every time the loop is executed, it assigns a new value systemName
and saves the callback. At the end of the cycle, the value systemName
is the last value specified by the cycle.
After a while, the requests http.get
start to complete, the stored callbacks are executed, they read systemName
, and they all get the same last value. This is what you are experiencing.
The simplest fix is ββactually creating a new variable systemName
for each execution of the block. This can be achieved by using let
instead var
. So just using it let
for all relevant variables will fix the problem:
let systemName = this.service.items[x].systemName;
let environment = this.service.items[x].environment;
let server = this.service.items[x].server;
let port = this.service.items[x].port;
let linkName = this.service.items[x].links[key];
let serviceType = key;
source to share