pass 'this' to callback in a class ecmascript 6 [duplicate] - javascript

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
I have a Class in ecmascript 6 . I need to pass a value of 'this' to a callback.
I tried using .bind(this). So far does not seem to work. I also tried setting var _this = this; and using _this within the callback. it still does not work
class Modal {
constructor(modal) {
this._modal = modal;
this.id = this._options.id;
}
}
open(opts) {
let modalOptions = {
size: opts.size || '',
templateUrl: 'modal.html',
controller: function controller($scope, $uibModalInstance) {
var _this = this;
this._options = {
id: opts.id
};
this.hcbuttons: [{id: '1', name: 'test'}, {id: '2', name: 'abc'}];
publisher.subscribe('triggered', this._options.id, function(event, creator) {
//as soon as we subscribe to the published event
var result = this.hcbuttons.filter(function( obj ) {
return obj.id == creator;
})[0];
if(result.sync === true) {
console.log('disabledall');
}
}).bind(this);
}
}

You are wrongly binding the this. You are calling the bind over the returned value of subscribe function. Function object only has the function bind in its prototype. So chance your code from this }).bind(this); to }.bind(this)).
Since you want to set this as the modal class,
//change one
open(opts) {
var _this = this;
let modalOptions = {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~
//change two
}.bind(_this));

If you are using ES2015, why not use lambdas (arrow functions)? They bind this automatically
open(opts) {
let modalOptions = {
size: opts.size || '',
templateUrl: 'modal.html',
controller: function controller($scope, $uibModalInstance) {
this._options = {
id: opts.id
};
this.hcbuttons = [{
id: '1',
name: 'test'
}, {
id: '2',
name: 'abc'
}];
publisher.subscribe('triggered', this._options.id, (event, creator) => {
let result = this.hcbuttons.filter(obj => obj.id === creator)[0];
if (result.sync) {
console.log('disabledall');
}
});
}
}
}
Here you can read more on arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions and how they work (might help you in the future).

Related

Setting this inside the functions Javascript [duplicate]

This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 3 months ago.
I have the next code:
const obj = {
name: 'Hi',
first: () => {
return this
},
other: {
name: 'last',
sec: function() {
this.c = '2'
return function() {
this.s = '3'
return this; // expect {s:3}
}
}
}
}
const t = obj.other.sec()
console.log(t())
In console.log i expect {s:3} but there is not that object. Question: Why there is not {s:3}? Or function() should have its own context
It's not there because this in JS is dynamic and depends on the scope.
Arrow functions don't have their own scope, so obj.first() will not return you obj but instead returns the global context (in a browser, usually window) or undefined when running in strict mode!
Functions via function do have a dedicated scope. However, when you call obj.other.sec(), the context of this in the sec function points to obj.other, not obj.
The function returned by obj.other.sec() does (or rather: can) create a new scope, since it's called without a context. But since it's also called without new, this points to the global context (see point 1).
Depending on what you want to achieve, the simplest solution is to replace this with the correct context. For example, if you want all function to run in the context of obj, just replace every this with obj:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.c = '2'
return function() {
obj.s = '3'
return obj;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj` with a `c` and an `s` property added
Or maybe you want varying context's:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
obj.other.s = '3'
return obj.other;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj.other` with a `c` and an `s` property added
And in case obj.other.sec() should return a new object that is not related to obj or obj.other, then... well... just return a new object:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
return { s: 3 };
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs a new object with an `s` property
You can read more about the dynamic nature of this on MDN

Why is the fat arrow in a class method not binding to the parent scope's this?

I've got an ES2015 code snippet where I'm trying to dynamically populate the objects this.data.pageCategoryL1~3 keys with different data depending on the state of the originalData. I'm passing class methods as a callback to _categoryMapper, which isn't binding the callback to class's this - it's only passing a pointer to the function without binding it even though it's a method. That alone seems weird odd that it isn't automatically bound to the instance.
The truly puzzling part though: inside of _categoryMapper's reduce function, the fat arrow function's this is undefined. I thought fat arrows were supposed to bind to their parent this's scope?
class AnalyticsData {
constructor(originalData) {
this.data = {};
this.originalData = originalData;
}
mapCategories() {
debugger;
let mappedCategories = {
pageCategoryL1: '',
pageCategoryL2: '',
pageCategoryL3: ''
};
if (this.originalData.search && this.originalData.search.refinements) {
mappedCategories = this._categoryMapper({
pageCategoryL1: 'categoryl1',
pageCategoryL2: 'categoryl2',
pageCategoryL3: 'categoryl3'
},
this._getSomeCategory); // if i bind only here it will work, because it doesn't use fat arrow's this
} else if (this.originalData.items) {
mappedCategories = this._categoryMapper({
pageCategoryL1: 'a',
pageCategoryL2: 'b',
pageCategoryL3: 'c'
},
this._getSomeOtherCategory);
}
return mappedCategories;
}
_categoryMapper(mapping, callback) {
return Object.keys(mapping).reduce((acc, key) => {
// fat arrow in reduce should be implicitly bound to this
console.log(this);
let category = callback(mapping[key]).bind(this);
acc[key] = category ? category : '';
return acc;
}, {});
}
_getSomeCategory(categoryKey) {
// No access to this as currently written
console.log(this)
let refinements = this.originalData.search.refinements;
let matchedObj = refinements.find(({
refinement
}) => categoryKey === refinement.name);
return matchedObj && matchedObj.refinement.value;
}
_getSomeOtherCategory(categoryKey) {
let id = Object.keys(this.originalData.items)[0];
return this.originalData.items[id][categoryKey];
}
}
window.x = new AnalyticsData({
search: {
refinements: [{
refinement: {
name: 'categoryl1',
value: 'yup'
}
}]
}
}).mapCategories()
console.log(x)
/* this.data should be: {
pageCategoryL1: 'yup',
pageCategoryL2: '',
pageCategoryL3: ''
};*/
You're misusing bind here.
let category = callback(mapping[key]).bind(this);
bind creates a copy of a function with this set to whatever you passed it and zero or more arguments preloaded.
function log(argument1) {
console.log(this);
console.log(argument1);
}
let f = log.bind({ a: 1 }, 'a');
let g = log.bind({ b: 2 }, 'b');
f();
g();
What you probably want to use is call which calls a function with this set to it's first argument.
function log(argument1) {
console.log(this);
console.log(argument1);
}
log.call({ a: 1 }, 'a');
log.call({ b: 2 }, 'b');
The reason this === undefined is that callback is not defined with an arrow function nor does it have any other way of defining what this should be. This is essentially what you're doing.
'use strict';
let obj = {
a: 1,
log() {
console.log(this);
}
};
function callCallback(callback) {
callback();
}
// This is what you want to happen
callCallback(obj.log.bind(obj));
// This is what you're doing
callCallback(obj.log);

How to chain JavaScript methods natively from an iterator?

Say I'm using js methods that have the 'this' returned, so one can chain like such:
something.add({a: 'xyz', b: 123}).add({a: 'abc', b: 456});
How can I chain these on something from an iterator? For example:
$scope.bindings = [
{
key: 'up',
func: function() {
$scope.scroll(1)
}
},{
key: 'down',
func: function() {
$scope.scroll(-1);
}
},
];
---EDIT---
The library I was using is Angular hotkeys. I wanted to ask in a generic way to help anyone else in a similar position.
I have:
var hotBindings = hotkeys.bindTo(scope);
for (var bind of scope.bindings) {
hotBindings = hotBindings.add({
combo: bind.key,
callback: function(e) {
e.preventDefault();
bind.func();
}
})
}
This assigns the 'down' method to both keypresses. If I write out the code without the loop, using scope.bindings[index].key (for example) and chain the .add() method then it works. I also tried without "hotBindings =".
Please don't mention the scopve vs $scope as this is being passed into a link function in a angular directive - angular almost certainly has nothing to do with it.
The only problem I see is the not working for (var bind of bindings).
Edit: thought it had something to do with the for (var bind of bindings) syntax and var v.s. let, turns out the provided code just works. I'll delete this answer if the real problem surfaces. Must be in the Something class?
Everything seems to work:
var Something = function() {
this.data = [];
this.add = function(item) {
this.data.push(item);
return this;
}.bind(this);
};
var bindings = [{
x: 'hi',
func: function() {
console.log("hi");
}
}, {
x: 'bye',
func: function() {
console.log("bye");
}
}];
var something = new Something();
for (var bind of bindings) {
something.add({
x: bind.x,
callback: bind.func
})
};
console.log(something.data);
I'm not sure what SomeThing is or what it's add method returns, but you can replicate the chaining by doing
let something = new Something();
for (const bind of bindings) {
something = something.add({
x: bind.x,
callback: bind.func
});
}
If you do this a lot (perhaps using different methods than just "add"), you might consider using a helper function:
function chain(obj, ...calls) {
for(let {method, args} of calls) {
obj = obj[method](...args)
}
return obj
}
chain(new Somthing(), [
{meth: 'add', args: [
{x: 'hi', func: () => console.log('hi')}]},
{meth: 'add', args: [
{x: 'bye', func: () => console.log('bye')}]}
]})

javascript: extending methods between objects, like mixins

I want to share or reuse some logic between differents objects, that they will be pretty similar, just changing the "scope".
var Mixin = {
show: function () {
this.container.show();
},
hide: function () {
this.container.hide();
},
play: function (data) {
data.map().append();
}
};
var ObjectA = {
container: $('#container_a');
foo: function () {
this.play(otherData); // Mixin common method?
}
};
var ObjectB = {
container: $('#container_b'),
foo: function () {
this.play(data); // Mixin common method?
}
};
ObjectA.show() // show $('#container_a');
ObjectB.show() // show $('#container_b');
I was trying using underscore
_.extend(ObjectA, Mixin);
but it seems like I have issues with the reference of the Mixin (this reference to the last extended object), like if i need to clone the object and extend it?
Is there any approach to do something similar?
Thanks!!
EDIT: I having issue with the scope of 'this', that is referencing to window, when a pass as a callback a function inherits from the mixin, like this.
PersonMixin = {
mixinFoo: function () {
this.handleResponse();
}
};
Person = {
personMethod: function () {
OtherLibrary.libMehtod(this.mixinFoo);
}
};
Object.assign(Person, PersonMixin);
and then, something like this will fail, this an example stack trace
Person.personMethod();
OtherLibrary.libMethod(callbackMixin);
Ajax.post(callbackMixin);
callbackMixin(response); // this.handleResponse() is not defined, because this reference to window object.
EDIT 2: I can solve this issue using bind()
You can do this in a number of ways, my preference is adjusting the objects __proto__ property on creation which will cause it to inherit your mixin via its prototype chain. This does not require the use of underscore.
I adjusted your example for ES6 and made it a bit simpler but should get the point across.
const PlayerType = (
{ show() {
console.info(`show ${this.name}`)
}
, hide() {
console.info(`hide ${this.name}`)
}
, play: function (data) {
data.map().append();
}
}
)
const objA = { __proto__: PlayerType
, name: 'objA'
, foo(...args) {
this.play(...args)
}
}
const objB = { __proto__: PlayerType
, name: 'objB'
, foo(...args) {
this.play(...args)
}
}
objA.show()
objB.show()
Simpler and no ES6:
var Mixin = (
{ show() {
console.info('show ' + this.name)
}
, hide() {
console.info('hide ' + this.name)
}
}
)
var a = { __proto__: Mixin, name: 'a' }
var b = { __proto__: Mixin, name: 'b' }
a.show()
b.show()
Alternate - Does the same thing with Object.create().
var Mixin = (
{ show() {
console.info('show ' + this.name)
}
, hide() {
console.info('hide ' + this.name)
}
}
)
var a = Object.create(Mixin, { name: { value: 'a', enumerable: true } })
var b = Object.create(Mixin, { name: { value: 'b', enumerable: true } })
a.show()
b.show()
It works, just check your syntax also.
var Mixin = {
show: function() {
console.log(this.tmp);
}
}
var oA = {
tmp: 'tmpA'
}
var oB = {
tmp: 'tmpB'
}
var mA = Object.assign(oA, Mixin);
var mB = Object.assign(oB, Mixin)
mA.show();
mB.show()

Integrating native JavaScript classes in an Angular app

I have a native JavaScript class:
var Holder = new function(elements) {
this.elements = elements;
this.anyFunction() {
// use of this.elements
};
};
How to use it in an Angular-way? For example, if I would like to use:
.controller('AnyController', ['Holder',
function (Holder) {
var elements = [
{id: 1, label: 'foo'},
{id: 2, label: 'bar'}
];
$scope.holder = new Holder(elements);
}])
How should I register my Holder class then? What are the options (if any)?
In parallel, is it that bad to use native JavaScript classes in an Angular app (i.e. without integrating it within the framework)?
You could return a class with a factory
.factory('Holder', function() {
return (function (){
this.foo = foo;
this.bar = bar;
});
});
Now to use it
.controller('AnyController', ['Holder', function (Holder) {
var holder = new Holder();
}]);
EDIT
Use a factory instead of a service, as suggested in the comments
As I understand it, a factory is a singleton, but a factory can generate a class that can create instances. So the factory would return a reference to the constructor when you inject it, or a wrapper function around the constructor to use it without using new:
.factory('Holder', function() {
function Holder(elements) {
this.elements = elements;
}
Holder.prototype.get = function() {
return this.elements;
};
return function(elements) {
return new Holder(elements);
};
})
.controller('Main', function($scope, Holder) {
var elements = [
{id: 1, label: 'foo'},
{id: 2, label: 'bar'}
];
$scope.elements = Holder(elements).get();
});

Categories

Resources