Passing a Promise from one Service Call To Another in Angular - javascript

So I've got a service call going out to the back end and saving an object, the call has a promise set up to return a number.
That call looks like this
saveTcTemplate(item: ITermsConditionsTemplate): ng.IPromise<number> {
item.modifiedDate = new Date();
return this.$http.post(this.api + '/SaveTcTemplate', item)
.then(this.returnData);
}
returnData = (response: any) => {
return response.data;
};
This is when creating a new object, all the fields are set to their needed values, passed to save in, then called to be displayed.
This is the get function used to pull the object after it's been saved.
getTermsConditions(id: number): ng.IPromise<ITermsConditionsTemplate> {
return this.$http.get(this.api + '/GetTermsConditions',
{
params: {
id: id
}
}).then(this.returnData);
}
This is the initial construction, saving, and getting, of the object
this.newTemplate.copyId = 0;
this.newTemplate.id = 0;
this.newTemplate.isLibrary = true;
this.newTemplate.studyFacilityScheduleId = this.studyFacilityScheduleId;
this.studyTermsConditionsService.saveTcTemplate(this.newTemplate)
.then(this.studyTermsConditionsService.getTermsConditions)
.then(this.setTemplateData);
When set up like this I can successfully save a new item, and have its id (the ng.IPromise part) returned to me, and passed into my Get service call.
The problem is, when set up this way, this.$http.get comes back undefined. From what I think I understand from other stack overflow issues that are similar, it is happening because I called the function without explicitly passing anything into it's parameter when I say
.then(this.studyTermsConditionsService.getTermsConditions)
To test this I also set up the save and get like this
var data: any;
data = this.studyTermsConditionsService.saveTcTemplate(this.newTemplate);
this.studyTermsConditionsService.getTermsConditions(data)
.then(this.setTemplateData);
And that worked, sort of. $http.Get was recognized, and could be used, but the problem with this setup was; due to the asynchronous nature of ng.IPromise the data isn't being sent to my Get function as the promised number value. It's being sent as type $$promise which results in the parameter in my get function being NaN.
So one way I'm passing a usable value, i.e. 32, but I'm not explicitly passing this value, so $http.get is undefined.
The other way, I am explicitly passing a parameter, so $http.get is recognized, and usable, but the explicitly called parameter is of type $$promise, not type number, It seems, unfortunately, the ng.IPromise< number > is not resolved by time I call my Get function.
How do I proceed from here??

I guess you are messed up with this. As you directly pass the function reference as parameter it lost with the context. You should call them explicitly like below.
Code
this.newTemplate.copyId = 0;
this.newTemplate.id = 0;
this.newTemplate.isLibrary = true;
this.newTemplate.studyFacilityScheduleId = this.studyFacilityScheduleId;
this.studyTermsConditionsService.saveTcTemplate((response) => this.newTemplate(response))
.then((response) => this.studyTermsConditionsService.getTermsConditions(response))
.then((response) => this.setTemplateData(response));
Otherwise you could make your function to use of arrow function which would help to available this context inside function.
saveTcTemplate(item: ITermsConditionsTemplate): ng.IPromise<number> { .. }
should be
saveTcTemplate = (item: ITermsConditionsTemplate): ng.IPromise<number> => { .. }
And
getTermsConditions(id: number): ng.IPromise<ITermsConditionsTemplate> { .. }
should be
getTermsConditions = (id: number): ng.IPromise<ITermsConditionsTemplate> => { .. }

Related

Angular push service object data to array

I have created one service to load all the files data:
readonly file= new BehaviorSubject(null);
readonly file$ = this.pnlNumbers.asObservable();
getFile(filename: string) {
this.file.next(null);
this.subscriptions.push(this.http.get(`/file/${filename}).subscribe(data => {
this.file.next(data);
}, error => {
this.file.next(error);
}));
}
This will return an single object with file information ,eg:
{
id:0001,
name: 'test_file.txt',
...
}
I have created ab function to store all the result data that comes from the getFile service:
getAllFiles(): any {
let filesList= [];
this.activeFilesList.forEach(fileName => {
this.fileService.getFile(fileName);
});
this.fileService.file$.subscribe((data) => {
if (data) {
fileList?.push(data);
}
});
return filesList;
}
I don't know why , but "typeOf this.getAllFiles()" will return an Object instead of Array, for that reason I cant access the indices of filesList[], eg on chrome console:
[]
1:{id:0001,name:'test.file.text'}
2:{id:0002,name:'test.file2.text'}
3:{id:0003,name:'test.file3.text'}
the filesList.lenght = 0
I need that this filesList[] be a type of Array instead of an Object.
Few things here:
Firstly, a common JavaScript gotcha is the return type of an array is in fact 'object'
typeof [] // 'object'
typeof ['any array contents'] // 'object'
i.e. typeof is an ineffective heuristic for determining whether the return type of this function is an array. Use Array.isArray instead:
Array.isArray(['any array contents']) // true
Array.isArray({ someKey: 'someValue' }) // false
Secondly, am I safe to assume that this line
readonly file$ = this.pnlNumbers.asObservable();
Should instead be
readonly file$ = this.file.asObservable();
otherwise the rest of the code does not really have much relevance, as this.pnlNumbers is not referenced anywhere else
Thirdly, it appears that you are trying to combine the results of multiple asynchronous streams (in this case http calls) into a single array to be returned from getAllFiles(). As these are asynchronous, they by nature take some time to return their data. While this data is being returned, the rest of your synchronous function code will run, and that means your return statement will be hit before the http calls have returned their data.
In its current state, getAllFiles() is simply returning the value of filesList before any of the http calls have returned their values, i.e. the default value it was assigned, [].
What you will need to do instead is to
Use an RxJs operator to combine those independent http streams into one stream
Subscribe to this combined stream and handle the combination of values as is appropriate for the operator being used
An example implementation using forkJoin is here, but depending on your use case, other joining operators like concat, mergeMap, combineLatest etc may be preferable:
type FileType = { id: number; name: string }
getAllFiles$(): Observable<FileType[]> {
const filesListObservables: Observable<FileType>[] =
this.activeFilesList
.map(
(fileName: string) => this.fileService.getFile(fileName)
);
const filesList$: Observable<FileType[]> = forkJoin(filesListObservables);
return filesList$;
}
getAllFiles(): void {
this.getAllFiles$()
.subscribe(
(allFiles: FileType[]) => {
console.log(allFiles) // [{id:0001,name:'test.file.text'},...]
}
)
}

Binding a Vue property to an asynchronous value

I have a Vue block that I need to bind to a boolean property:
<div class="row" v-if.sync="isThisAllowed">
To calculate that property I need to make an API call using Axios, which has to be asynchronous. I've written the necessary code to get the value:
public async checkAllowed(): Promise<boolean> {
var allowed = false;
await Axios.get(`/api/isItAllowed`).then((result) => {
var myObjects = <MyObject[]>result.data.results;
myObjects.forEach(function (object) {
if (object.isAllowed == true) {
hasValuedGia = true;
}
})
});
return allowed;
}
What I did then - I'm not very experienced with Vue - is to add a property to the Vue model and assign a value to it in created:
public isThisAllowed: boolean = false;
async created() {
this.checkAllowed().then(result => {
this.isThisAllowed = result;
});
}
This works in the sense that the value I'm expecting is assigned to the property. But Vue doesn't like it and complains
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
Most of the values on the model are exposed via getters:
get isSomethingElseAllowed(): boolean {
return this.productCode === ProductTypes.AcmeWidgets;
}
But I need to "await" the value of the async function, which would mean making the getter async which then, of course, makes it a Promise and I can't bind that to my model?
What's the right way to go about this?
You can't define a property that way, instead define isThisAllowed in the data object
as
data: function(){
return {
isThisAllowed: false
}
}
And make checkAllowed into a normal function and set this.isThisAllowed = allowed inside it

Is there a better way to avoid if/then/else?

Background
We have a request object that contains information. That specific object has a field called partnerId which determines what we are going to do with the request.
A typical approach would be a gigantic if/then/else:
function processRequest( request ){
if( request.partnerId === 1 ){
//code here
}else if( request.partnerId === 23 ){
//code here
}
//and so on. This would be a **huge** if then else.
}
This approach has two main problems:
This function would be huge. Huge functions are a code smell (explaining why next) but mainly they become very hard to read and maintain very quickly.
This function would do more than one thing. This is a problem. Good coding practices recommend that 1 function should do only 1 thing.
Our solution
To bypass the previous problems, I challenged my co-worker to come up with a different solution, and he came up with a function that dynamically builds the name of the function we want to use and calls it. Sounds complicated but this code will clarify it:
const functionHolder = {
const p1 = request => {
//deals with request
};
const p23 = request => {
//deals with request
};
return { p1, p23 };
};
const processRequest = request => {
const partnerId = request.partnerId;
const result = functionHolder[`p${partnerId}`](request);
return result;
};
Problems
This solution has advantages over the previous one:
There is no main function with an huge gigantic if then else.
Each execution path is not a single function that does one thing only
However it also has a few problems:
We are using an object functionHolder which is in reality useless. p1 and p23 don't share anything in common, we just use this object because we don't know how else we can build the function's name dynamically and call it.
There is no else case. If we get an incorrect parameter the code blows.
Out eslint with rule non-used-vars complains that p1 and p23 are not being used and we don't know how to fix it ( https://eslint.org/docs/rules/no-unused-vars ).
The last problem, gives us the impression that perhaps this solution is not so great. Perhaps this pattern to avoid an if then else has some evil to it that we are yet to find.
Questions
Is there any other pattern we can use to avoid huge if then else statements ( or switch cases )?
Is there a way to get rid of the functionHolder object?
Should we change the pattern or fix the rule?
Looking forward to any feedback!
You can get rid of the unused variables by never declaring them in the first place:
const functionHolder = {
p1: request => {
//deals with request
},
p23: request => {
//deals with request
};
};
const processRequest = request => {
const partnerId = request.partnerId;
const method = functionHolder[`p${partnerId}`]
if(method) // Is there a method for `partnerId`?
return method(request);
return null; // No method found. Return `null` or call your default handler here.
};
To answer your points:
Yeap, as shown above.
Not without some kind of object.
That's up to you. Whatever you prefer.
Perhaps I'm not understanding the question properly, but why not an object to hold the methods?
const functionHolder = {
1: function(request) {
// do something
},
23: function(request) {
// do something
},
_default: function(request) {
// do something
}
}
function processRequest(request) {
(functionHolder[request.partnerId] || functionHolder._default)(request)
}
Explanation:
The object functionHolder contains each of the methods used to deal with a given request.
The keys of functionHolder (e.g. 1) correspond directly to the values of request.partnerId, and the values of these members are the appropriate methods.
The function processRequest "selects" the appropriate method in functionHolder (i.e. object[key]), and calls this method with the request as the parameter (i.e. method(parameter)).
We also have a default method, under the key _default, if request.partnerId does not match any existing key. Given a || b; if a is "falsy", in this case undefined (because there is no corresponding member of the object), evaluate to b.
If you are concerned about making functionHolder "bloated", then you can separate each of the methods:
const p1 = request => {
// do something
}
const p23 = request => {
// do something
}
const _default = request => {
// do something
}
And then combine them into a "summary" object of sorts.
const functionHolder = {
1: p1,
23: p23,
_default: _default
}
processRequest remains the same as above.
This adds a lot of global variables though.
Another advantage is you can import / change / declare methods on the fly. e.g.
functionHolder[1] = p1b // where p1b is another request handler (function) for `request.partnerId` = 1
functionHolder[5] = p5 // where p5 is a request handler (function) that has not yet been declared for `request.partnerId` = 5
Combining the above, without having to declare many global variables while also being able to separate the declaration of each method:
const functionHolder = {}
functionHolder._default = request => {
// do something
}
functionHolder[1] = request => {
// do something
}
functionHolder[23] = request => {
// do something
}
processRequest remains the same as above.
You just have to be sure that the methods are "loaded in" to functionHolder before you call processRequest.

Accessing source observable from outside Rx.Observable.prototype

I am having trouble figuring out how to access the source observable, in this scheme (just trying to figure out how to this without modifying Rx.Observable.prototype):
q.drain()
.flatMap(function(val){
return q.backpressure(val, function(cb){
setTimeout(cb,1000);
});
})
We call backpressure as a method on the Queue prototype:
Queue.prototype.backpressure = function(val, fn){
const source = ? // I don't know how to access the source observable...
return Rx.Observable.create(sub => {
return source.subscribe(val => {
fn.call(source, val, function(err, val){
if(err){
sub.error(err);
}
else{
sub.next(val);
}
});
},
// be sure to handle errors and completions as appropriate and
// send them along
err => sub.error(err),
() => sub.complete());
});
};
but the problem is I don't know if I can access the source observable in this scheme - the correct value for source is certainly not the this value inside the prototype because that belongs to the queue instance. My only hope I think is somehow to pass the source observable directly into the backpressure method. Anyone know how I can this? I don't mind putting this function elsewhere, it doesn't have to be a method on queue, but I think the same problem will exist either way.
If it helps, the value for this inside the flatMap function (if you use a regular function instead of an arrow function) is a MergeMapSubcriber object, see:
However, after experimenting, I don't believe that the MergeMapSubcriber value is the one I want to use as my source; my source should be an Observable TMK, not a Subscriber.
Have you thought about putting it on Observable prototype?
Observable.prototype.backpressure = function(queue, fn){
const source = this;
return this.flatMap(function(val){
return Rx.Observable.create(sub => {
return source.subscribe...
});
})
};
Then for queue:
q.drain()
.backpressure(q, function(cb) {
setTimeout(cb,1000);
});

Backbone.js requestPager conditionally exclude paramater from URL

RequestPager sends all the attributes in server_api to the request as query string. However, sometime I want to exclude a parameter on some condition. This is how, i'm setting the param:
server_api: {
query: function () {
return this.searchQuery
},
type: function(){ return this.searchType }
}
If this.searchQuery is empty, it makes the URL like ?query=&type=1. But I don't want to send query or type when it's empty or when my some other condition fails.
I know the dirty way like:
if(!myCollection.searchQuery){
delete(myCollection.server_api.licensed);
}
But this is not maintainable. Because text time I've to create this function. So, I'm looking for a better way of doing this. Any Help?
If you look at how server_api is used:
_.each(_.result(self, "server_api"), function(value, key){
if( _.isFunction(value) ) {
value = _.bind(value, self);
value = value();
}
queryAttributes[key] = value;
});
you'll see that it uses _.result:
result _.result(object, property)
If the value of the named property is a function then invoke it;
otherwise, return it.
var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
_.result(object, 'cheese');
=> "crumpets"
_.result(object, 'stuff');
=> "nonsense"
That means that you can make server_api a function which returns the appropriate object.

Categories