Javascript/Typescript 'this' scope - javascript

I am working with Ionic2 and Meteor. I do however have a Javascript/Typescript issue relating to the scope of the this object.
I have read that I should use bind when I don't have handle on this at the appropriate level.
I probably don't understand the concept, because I try the following, but get an error trying to call a function.
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
setLocalMessage.bind(message);
});
});
});
and
private setLocalMessage(message: Message): void {
this.localMessageCollection.insert(message);
}
I get the following error when I try build the app:
ERROR in ./app/pages/messages/messages.ts
(72,19): error TS2304: Cannot find name 'setLocalMessage'.
UPDATE
Thank you for the advise below.
I am now using the following, and it works.
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});
});

I have read that I should use bind when I don't have handle on this at the appropriate level.
That's a bit outdated now, better have a look at How to access the correct `this` context inside a callback? these days which also shows you how to use arrow functions.
You're getting the error message because setLocalMessage is not a variable but still a property of this so you have to access it as such. There are basically three solutions in your case:
bind
messageData.find().forEach(this.setLocalMessage.bind(this));
the context argument of forEach (assuming it's the Array method):
messageData.find().forEach(this.setLocalMessage, this);
another arrow function:
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});

There are a few things wrong here.
In ES6 (and thus TypeScript), you need to refer to instance members using explicit this, such as this.setLocalMessage. Just writing setLocalMessage is invalid no matter where the code is.
Inside a function, the this object will probably not be what you expect anyway. You need to capture the this object from outside the function and put it in a variable, like so:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let self = this;
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
self.setLocalMessage(message);
});
});
});
Alternatively, you can use an arrow expression, in which this is the same as what it is in the code around it:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(message => this.setLocalMessage(message));
});
});
});
It's not an issue of TypeScript itself. Without it, the code will just fail at runtime.

Related

how to use the google-translate-api translation outside the method?

I started using Cloud Google and implemented the translation API in my code but I can't use the response outside the callback.
methods:{
clicked(){
const text = "Olá";
const target = navigator.language;
googleTranslate.translate(text, target, function(err, translation){
console.log(translation.translatedText)
//this.newText = translation.translatedText;
});
//console.log(this.newText);
},
}
Show the error with or without the console.log. In the this.newText = translation.translatedText;
Error in render: "TypeError: Cannot read property 'newText' of undefined"
I would like to show the user the answer in the template. How can I do it?
Using the function keyword changes the 'this' context. You can either save 'this' outside of the function or use arrow functions.
Here's how you would use an arrow function
methods:{
clicked(){
const text = "Olá";
const target = navigator.language;
googleTranslate.translate(text, target, (err, translation) => {
console.log(translation.translatedText)
//this.newText = translation.translatedText;
});
//console.log(this.newText);
},
}

Turn a for loop on an object into a .map

I have been told my function:
for (const key of Object.keys(temp)) {
this.sessionData.push(temp[key]);
}
Must now use a .map instead,
I have tried this below:
Object.keys(temp).map(function(key) {
this.sessionData[key];
})
But 1 I don't know if it's actually accurate, and also, it cant access the data outside of the scope of the function it is in, here is the whole function below:
public sessionData;
sessionDates(sessionsData) {
const temp = {};
this.sessionData = [];
sessionsData.forEach(session => {
const date = moment(session.startDatetime).format('DDMMYYYY');
if (temp[date]) {
temp[date].push(session);
} else {
temp[date] = [session];
}
});
Object.keys(temp).map(function(key) {
this.sessionData[key];
})
TRYING TO USE THIS BELOW... session data is undefined, it can't access out of the scope?
Object.keys(temp).map(function(key) {
this.sessionData[key];
})
But this works..
for (const key of Object.keys(temp)) {
this.sessionData.push(temp[key]);
}
So this new .map method can't access anything out of its scope.. sigh!
If anybody can help that would be amazing! Thanks!
In Javascript all functions can access variables from outside (called "higher scope") - it's one of the strengths of the language and is called "capture".
The reason your code is failing is because it's using this.sessionData inside a function declaration, which cases problems because this in javascript is... somewhat complex. But you don't need it!
You also need to make sure you return the value you want to output. Here's how I would write it:
this.sessionData = Object.keys(temp).map(key => temp[key]);

How to use observable and pipes in a function rxjs?

Im trying to get this following code to move into a function that's called rather than doing it from a native element click event:
const saveRit$ = Observable.fromEvent(this.saveRitbutton.nativeElement, 'click');
console.log('here', saveRit$);
saveRit$.pipe(
withLatestFrom(this.account$)
).subscribe(([click, { accountId }]) => {
console.log('working');
this.saveRit(accountId).then(() => {
const years = this.ritForm.controls['lineItems'].value.map(row => row.year);
this.emitSuccess(years);
});
});
I have limited knowledge on pipes and observable so any input is amazing. I know I no longer need the saveRit$ variable however I am at a loss as to what to do .pipe() to once it's removed? I just want to be able to call this from some function.
Since the Observable.fromEvent just gives you an Observable we could replace it with any other Observable. As far as I can see from your snippet you want to subscribe on this.account$.
this.account$.subscribe(account => {
console.log('working');
this.saveRit(account.accountId).then(() => {
const years = this.ritForm.controls['lineItems'].value.map(row => row.year);
this.emitSuccess(years);
});
});
Good luck!

How to access 'this' keyword inside of static method in react native?

I am not able to access 'this' keyword in my static method in react- native, when I try to access it, it's thrown me error like 'this.setState not a function'.
Here is my code.
static getShiftStatus = () =>{
//for check shift start or not
Usermodal.getShiftStatus((isStatus) =>{
this.setState({isShiftStart: isStatus}) //error occure here.
console.log(a.state.isShiftStart)
})
}
this in the inner function points to something else.
You need to capture the this from the outside function.
static getShiftStatus = () =>{
var that = this; // capture here
Usermodal.getShiftStatus((isStatus) =>{
that.setState({isShiftStart: isStatus}) // use it here
console.log(a.state.isShiftStart)
})
}

Best way to export Express route methods for promise chains?

I have an API route that is being refactored to use ES6 promises to avoid callback hell.
After successfully converting to a promise chain, I wanted to export my .then() functions to a separate file for cleanliness and clarity.
The route file:
The functions file:
This works fine. However, what I'd like to do is move the functions declared in the Class constructor() function into independent methods, which can reference the values instantiated by the constructor. That way it all reads nicer.
But, when I do, I run into scoping problems - this is not defined, etc. What is the correct way to do this? Is an ES6 appropriate to use here, or should I use some other structure?
RAW CODE:
route...
.post((req, res) => {
let SubmitRouteFunctions = require('./functions/submitFunctions.js');
let fn = new SubmitRouteFunctions(req, res);
// *******************************************
// ***** THIS IS WHERE THE MAGIC HAPPENS *****
// *******************************************
Promise.all([fn.redundancyCheck, fn.getLocationInfo])
.then(fn.resetRedundantID)
.then(fn.constructSurveyResult)
.then(fn.storeResultInDB)
.then(fn.redirectToUniqueURL)
.catch((err) => {
console.log(err);
res.send("ERROR SUBMITTING YOUR RESULT: ", err);
});
})
exported functions...
module.exports = class SubmitRouteFunctions {
constructor (req, res) {
this.res = res;
this.initialData = {
answers : req.body.responses,
coreFit : req.body.coreFit,
secondFit : req.body.secondFit,
modules : req.body.modules,
};
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
this.clientIp = requestIp.getClientIp(req);
this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);
this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {
console.log(mongooseResult);
if (mongooseResult != null) {
console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
console.log('NEW ID: ', this.newId);
};
return clientLocationPromise.data;
}
this.constructSurveyResult = (clientLocation) => {
let additionalData = {quizId: this.newId, location: clientLocation};
return Object.assign({}, this.initialData, additionalData);
}
this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);
this.redirectToUniqueURL = (mongooseResult) => {
let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
this.res.send('/results' + parsedId);
}
}
}
ALTERNATIVE #1:
Rather than using ES6 classes, an alternate way to perform the same behavior that cleans up the code just a little bit is to export an anonymous function as described by Nick Panov here: In Node.js, how do I "include" functions from my other files?
FUNCTIONS FILE:
module.exports = function (req, res) {
this.initialData = {
answers : req.body.responses,
coreFit : req.body.coreFit,
secondFit : req.body.secondFit,
modules : req.body.modules,
};
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
this.clientIp = requestIp.getClientIp(req);
this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);
this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {
if (mongooseResult != null) {
console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
console.log('NEW ID: ', this.newId);
};
return clientLocationPromise.data;
}
this.constructSurveyResult = (clientLocation) => {
let additionalData = {quizId: this.newId, location: clientLocation};
return Object.assign({}, this.initialData, additionalData);
}
this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);
this.redirectToUniqueURL = (mongooseResult) => {
let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
res.send('/results' + parsedId);
}
}
Although this does not avoid having to tag each method with this.someFn()..., as I originally wanted, it does take an extra step in the routing file - doing things this way prevents me from having to assign a specific namespace to the methods.
ROUTES FILE
.post((req, res) => {
require('./functions/submitFunctions_2.js')(req, res);
Promise.all([redundancyCheck, getLocationInfo])
.then(resetRedundantID)
.then(constructSurveyResult)
.then(storeResultInDB)
.then(redirectToUniqueURL)
.catch((err) => {
console.log(err);
res.send("ERROR SUBMITTING YOUR RESULT: ", err);
});
})
The functions are reset to reflect each new req and res objects as POST requests hit the route, and the this keyword is apparently bound to the POST route callback in each of the imported methods.
IMPORTANT NOTE: You cannot export an arrow function using this method. The exported function must be a traditional, anonymous function. Here's why, per Udo G's comment on the same thread:
It should be worth to note that this works because this in a function is the global scope when the function is called directly (not bound in any way).
ALTERNATIVE #2:
Another option, courtesy of Bergi from: How to use arrow functions (public class fields) as class methods?
What I am looking for, really, is an experimental feature....
There is an proposal which might allow you to omit the constructor() and directly put the assignment in the class scope with the same functionality, but I wouldn't recommend to use that as it's highly experimental.
However, there is still a way to separate the methods:
Alternatively, you can always use .bind, which allows you to declare the method on the prototype and then bind it to the instance in the constructor. This approach has greater flexibility as it allows modifying the method from the outside of your class.
Based on Bergi's example:
module.exports = class SomeClass {
constructor() {
this.someMethod= this.someMethod.bind(this);
this.someOtherMethod= this.someOtherMethod.bind(this);
…
}
someMethod(val) {
// Do something with val
}
someOtherMethod(val2) {
// Do something with val2
}
}
Obviously, this is more in-line with what I was originally looking for, as it enhances the overall readability of the exported code. BUT doing so will require that you assign a namespace to the new class in your routes file like I did originally:
let SubmitRouteFunctions = require('./functions/submitFunctions.js');
let fn = new SubmitRouteFunctions(req, res);
Promise.all([fn.redundancyCheck, fn.getLocationInfo])
.then(...)
PROPOSED / EXPERIMENTAL FEATURE:
This is not really my wheelhouse, but per Bergi, there is currently a Stage-2 proposal (https://github.com/tc39/proposal-class-public-fields) that is attempting to get "class instance fields" added to the next ES spec.
"Class instance fields" describe properties intended to exist on
instances of a class (and may optionally include initializer
expressions for said properties)
As I understand it, this would solve the issue described here entirely, by allowing methods attached to class objects to reference each instantiation of itself. Therefore, this issues would disappear and methods could optionally be bound automatically.
My (limited) understanding is that the arrow function would be used to accomplish this, like so:
class SomeClass {
constructor() {...}
someMethod (val) => {
// Do something with val
// Where 'this' is bound to the current instance of SomeClass
}
}
Apparently this can be done now using a Babel compiler, but is obviously experimental and risky. Plus, in this case we're trying to do this in Node / Express which makes that almost a moot point :)

Categories

Resources