Angular 5 callback and that=this - javascript

in my Angular 5 project I have some problems with the callback from an external library. I load it using
<script src="https://js.chargebee.com/v2/chargebee.js" data-cb-site="my-site"> </script>
and then in Angular I import it as follows:
declare var Chargebee: any;
Chargebee.init({site: "my-site"});
I also have a public variable in my component, lets say publicVar, which I am displaying in the template.
publicVar: string = 'before checkout';
I have the following method:
subscribeToPlan() {
var _self = this;
var chargebeeInstance = Chargebee.getInstance();
chargebeeInstance.openCheckout({
hostedPage: function() {
_self.publicVar = 'hosted-page';
console.log('hosted-page');
},
success: function(hostedPageId) {
_self.publicVar = 'success';
console.log('success');
},
close: function() {
_self.publicVar = 'closed';
console.log('closed');
}
});
}
What is happening when I run the code?
All the console.log functions output the correct data, so I know the chargebee callbacks are called. However, only hostedPage: function() {} correctly changes my publicVar, and it says "hosted-page" in my template.
success: function(){} nor close: function(){} won't update the publicVar in my template. I suspect because these are, unlike the hostedPage, a callback methods and the self. in them has wrong context?

So I worked it out (or found a workaround)
As the _self did hold the correct data, I thought that the change detection is not triggered for those callbacks. After I've manually triggered it, it all works as expected.
Final code and changes:
Import
import { ChangeDetectorRef } from '#angular/core';
Add it to the constructor:
constructor(private cd: ChangeDetectorRef)
And then call it at the end of the callback method, this will manually trigger Angular change detection, and updates template rendering of publicVar
subscribeToPlan() {
var _self = this;
var chargebeeInstance = Chargebee.getInstance();
chargebeeInstance.openCheckout({
hostedPage: function() {
_self.publicVar = 'hosted-page'; // This is not a callback, so it just works
console.log('hosted-page');
},
success: function(hostedPageId) {
console.log('success');
_self.publicVar = 'success';
_self.cd.detectChanges(); // Manual change detection
},
close: function() {
console.log('closed');
_self.publicVar = 'closed';
_self.cd.detectChanges(); // Manual change detection
}
});
}
Alternative code using the arrow functions, as per Joe's suggestion (https://stackoverflow.com/a/50335020/5644425)
subscribeToPlan() {
var chargebeeInstance = Chargebee.getInstance();
chargebeeInstance.openCheckout({
hostedPage: () => {
this.publicVar = 'hosted-page';
console.log('hosted-page');
},
success: hostedPageId => {
console.log('success');
this.publicVar = 'success';
this.cd.detectChanges();
},
close: () => {
console.log('closed');
this.publicVar = 'closed';
this.cd.detectChanges();
}
});
}

Rather than assigning this to some variable _self you could just use arrow functions, which don't affect the scope of this:
subscribeToPlan() {
var chargebeeInstance = Chargebee.getInstance();
chargebeeInstance.openCheckout({
hostedPage: () => {
this.publicVar = 'hosted-page';
console.log('hosted-page');
},
success: hostedPageId => {
this.publicVar = 'success';
console.log('success');
},
close: () => {
this.publicVar = 'closed';
console.log('closed');
}
});
}
And without the logging this becomes the even neater:
subscribeToPlan() {
var chargebeeInstance = Chargebee.getInstance();
chargebeeInstance.openCheckout({
hostedPage: () => this.publicVar = 'hosted-page',
success: hostedPageId => this.publicVar = 'success',
close: () => this.publicVar = 'closed'
});
}
Though I think your code should work fine. You're assigning this to _self and using that, so the this of the functions shouldn't matter. I'd definitely recommend putting in a breakpoint and checking what _self is in those two functions.

Related

How to get the parent function name of the function being called

I am trying to get the name of the parent function of the function being called.
For example if I have these functions:
var functions = {
coolfunction1: {
add: function () {
},
delete: function () {
},
save: function () {
}
},
coolfunction2: {
add: function () {
// i want to console.log() the name of the parent of this function,
// output: coolfunction2
},
delete: function () {
},
save: function () {
}
}
}
When I call functions.coolfunction2.add(), is there a way to log the name of the parent function that was run?
I know I can use the variable this but that only outputs the names of the children functions, add(), delete(), save().
How can I know that the coolfuntion2 was run?
I know this can be done manually, by rewriting the function name in the add() function, but is there a way to get the name dynamically?
You can add a getter to those methods as
Object.keys(functions).forEach(t =>
Object.keys(functions[t]).forEach(t2 => {
var func = functions[t][t2]; //save a reference to function since it won't be a function anymore once a getter is assigned
Object.defineProperty(functions[t], t2, {
get: function() {
console.log(t); //print the name of parent property or grand-parent property, etc
//func();
return func; //return the reference to this function
}
});
})
);
Demo
var functions = {
coolfunction1: {
add: function() {
},
delete: function() {
},
save: function() {
}
},
coolfunction2: {
add: function() {
console.log("a is invoked");
},
delete: function() {
},
save: function() {
}
}
};
Object.keys(functions).forEach(t =>
Object.keys(functions[t]).forEach(t2 => {
var func = functions[t][t2];
Object.defineProperty(functions[t], t2, {
get: function() {
console.log(t);
//func();
return func;
}
});
})
);
functions.coolfunction2.add();
functions.coolfunction2.add();
functions.coolfunction1.add();

Data is not recognised inside setTimeout inside another closure

I have my data, and I am trying to access it within an initializer inside setTimeout.
data() {
return { val: {} }
},
methods: {
test() {
console.log(this.val) // works
var self = this
setTimeout(function() {
console.log(this.val) // works
var check = this.myMethod()
$.validate({
onError: function($form) {
console.log(self.val) // doesn't work
}
})
}, 500)
},
myMethod() {
// some stuff
return true
}
}
This is the updated code. Using the var self = this approach, I am now gettign:
Uncaught TypeError: this.myMethod is not a function
data() {
return { val: {} }
},
methods: {
test() {
console.log(this.val) // works
var self = this;
setTimeout(function() {
console.log(self.val) // works
$.validate({
onError: function($form) {
console.log(self.val) // doesn't work
}
})
}, 500)
}
}
Try this. You often lose the value of this when calling functions within functions, so we store this in a variable to make it accessible from nested functions.
methods: {
test() {
console.log(this.val) // works
// for this -> self trick
let self = this;
setTimeout(function() {
console.log(self.val) // works
$.validate({
onError: function($form) {
console.log(self.val) // doesn't work
}
})
}, 500)
}
}

how to call this with libraries that changing the default this?

so I started to using interactjs
and I have this simple code:
class example {
registerTouchEvents() {
var self = this;
interact('.touchy').draggable({
onstart: self.onStart,
});
}
onStart(event) {
this.someAction();//<-- not working as this is interact object
}
someAction() {
console.log('touch has been started') //<-- I need to call this function
}
}
is there someway of calling the current object without using global variable?
Move the handler where you declare "self":
class example {
registerTouchEvents() {
var self = this
, onStart = function onStart(event) {
self .someAction();
}
;
interact('.touchy').draggable({
onstart: onStart,
});
}
someAction() {
console.log('touch has been started') //<-- I need to call this function
}
}

Using callback function with prototype functions

I am having trouble figuring out how to pass the objects method rather than sort "generic prototype" method when doing callback.
function Client() {
this.name = "hello";
}
Client.prototype.apiCall = function(method, params, callback) {
callback();
}
Client.prototype.onLogin = function(error, data) {
console.log(this.name);// undefined!!!!
}
Client.prototype.start = function() {
var self = this;
self.apiCall('rtm.start', {
}, self.onLogin) // passing of method like this does not work.
}
I am passing the onLogin method but well it does not work. This is code I have re-written. Previously I nested all methods inside the Client function but well, I learned that that is not the way to do it so now I am trying using prototype.
I know there is some solution "binding" the onLogin function inside the Client() function but well I want to understand the issue.
You need to bind the apiCalls context to the callback using bind:
Client.prototype.apiCall = function(method, params, callback) {
var bound = callback.bind(this);
bound();
}
Otherwise, the this within onLogin is set to the global object.
See Call, Apply And Bind for further details.
Basically .bind(obj) returns a function which, when called, will internally use (obj) as this.
So you create this bound and then you call it.
You can use call or apply to bind this, see snippet. I've modified your code for demonstration purposes. Hope it clarifies things for you
function Client() {
this.name = "hello";
}
Client.prototype = {
apiCall: function(method, params, callback) {
try {
var trial = method.call(this, params);
callback.apply(this, [null, trial]);
} catch (e) {
callback.apply(this, [e, null]);
}
},
onLogin: function(error, data) {
if (error) {
Helpers.report('<b style="color: red">' +
'An error occured!</b> <i>' +
error.message + '</i>')
} else {
Helpers.report(this.name, ' (data.result = ' + data.result + ')');
}
},
start: function() {
Helpers.useCSS(1);
// error from this.rtm.start
Helpers.report('Command: <code>', 'this.apiCall(this.rtm.start, {will: \'not work\'}, this.onLogin);','</code>');
this.apiCall(this.rtm.start, {will: 'not work'}, this.onLogin);
// this.rtm.works is ok
Helpers.report('<br>Command: <code>',
'this.apiCall(this.rtm.works, {will: \'work\'}, this.onLogin);',
'</code>');
this.apiCall(this.rtm.works, {
will: 'work'
}, this.onLogin);
},
// --------------------------------
// added rtm for snippet demo
rtm: {
start: function(params) {
return anerror;
},
works: function(params) {
return {
result: 'worked, <code>params: ' + JSON.stringify(params) + '</code>'
};
}
},
};
new Client().start(); //<= here
<script src="https://rawgit.com/KooiInc/Helpers/master/Helpers.js"></script>

Export React mixin in a separated file

I am trying to separate the SetIntervalMixin into a different file that the component class file. Maybe I am not fully understand how module.export works but... If I do like this:
module.exports = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
};
inside a SetIntervalMixin.js, then it works fine using from the component:
var SetIntervalMixin = require('../util/mixins/SetIntervalMixin')
But if I write it like this:
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
};
module.export = SetIntervalMixin;
It doesn't work (undefined when trying to call setInterval()). I think something is missing after:
SetIntervalMixin = ...
Like when you define a component, you use:
var yourComponent = React.createClass(...
Is there is something similar like a React.createMixin(.. ? Or how would be the best way to do this.
Thanks.
Your code is right, you just have a typo in the second version (should be module.exports instead of module.export):
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
};
module.exports = SetIntervalMixin;

Categories

Resources