How to make "this" in nested function refer to parent object or what is the best practice to this problem? - javascript-objects

From example code below
I'm trying to add a method inside an object where it generates new name based on object properties
but "this" in this context is referring global object but not the parent one
It's ok using arrow function but the functions won't be hoisted and I need to express the function before using it. I prefer knowing what it does before seeing the code.
using arrow function expression it does work but is not hoisted
using function declaration it doesn't work but is hoisted
Is there any best practice approach to this kind of problem?
const agent = {
firsName: "John",
lastName: "Depp",
born: 12/04/1987,
secretName: function() {
return newFirstName() + newLastName()
function newFirstName() {
// complex codes here
return "Agent " + this.firstName // for simplicity
}
function newLastName() {
// complex codes here
return "Agent " + this.LastName // for simplicity
}
},
};
console.log(agent.secretName()) // logs "Agent undefinedAgent undefined"

It's probably due to the lexical scope of your functions. You can read more about it here and here.
So if you move your helper functions one level up, it solves the problem:
const agent = {
firstName: 'John',
lastName: 'Depp',
born: 12 / 04 / 1987,
newFirstName: function () {
// complex codes here
return 'Agent ' + this.firstName; // for simplicity
},
newLastName: function () {
// complex codes here
return 'Agent ' + this.lastName; // for simplicity
},
secretName: function () {
return this.newFirstName() + this.newLastName();
},
};
console.log(agent.secretName()); // logs "Agent undefinedAgent undefined"

you should use arrow function so they can target their parent scope.
const agent = {
firstName: "John",
lastName: "Depp",
born: 12/04/1987,
secretName: function() {
const newFirstName = () => {
return "Agent " + this.firstName;
}
const newLastName = () => {
return " Agent " + this.lastName;
}
return newFirstName() + newLastName();
},
};
console.log(agent.secretName())

Ok I have found the answer from this thread How to access the correct `this` inside a callback?
In short: just define the outer scope
let self = this
Example
const agent = {
firstName: "John",
lastName: "Depp",
born: 12/04/1987,
secretName: function() {
const self = this;
return newFirstName() + newLastName()
function newFirstName() {
return "Agent " + self.firstName
}
function newLastName() {
return "Agent " + self.lastName
}
}
}
console.log(agent.secretName()) // logs JohnJohn it works!!

Related

Angularjs specific routing

Ok my fellow friends and associates,
I am trying to display only information based off of a url: parameter,
I have a service that has save a ton of data so I can use it across my application, but the problem I am having is displaying the data that I want to show based off of my parameter.
so here is my route, I am sending my id based off an link attribute
/CourseMaterials/:ID"
I have my service which stores all of my json objects to use accross the app.
angular.module('app').service('SaveService', function () {
this.textExpress = {};
this.courseMaterials = {};
this.courses = {};
this.student = {};
this.receipts = {};
this.webOrders = {};
return {
//This saves the textExpress object to reuse
getTextExpress: function () {
return this.textExpress;
},
setTextExpress: function (t) {
this.textExpress = t;
},
//This saves the courseMaterials object
getCourseMaterials: function () {
return this.courseMaterials;
},
setCourseMaterials: function (cm) {
this.courseMaterials = cm;
},
//This saves the courses object
get
Courses: function () {
return this.courses;
},
setCourses: function (c) {
this.courses = c;
},
//This saves the student object
getStudent: function () {
return this.student;
},
setStudent: function (s) {
this.student = s;
},
//This saves the receipts object
getReceipts: function () {
return this.receipts;
},
setReceipts: function (r) {
this.receipts = r;
},
//This saves the webOrders object
getWebOrders: function () {
return this.webOrders;
},
setWebOrders: function (wo) {
this.webOrders = wo;
},
}
});
I am a little stumped at how to say to my controller of my new view display only this info from my service
CourseMaterials/:id
So an object I might have looks like this
CourseMaterials[0] {
course: cit298,
author: ben stein,
isbn: xxxxxxxxxxxxxx,
}
CourseMaterials[1] {
course: cit298,
author: george stein,
isbn: xxxxxxxxxxxxxx,
}
On my new page I want to display something based off of the course applicable to what a user has selected based of the params in the link.
That looks like
http://localhost:55436/#!/CourseMaterials/cit%20298 etc......

Dynamically nested $resource Promises with ordered $q.all Promises

2nd UPDATE
We are implementing a BulkEdit functionality which sends async CRUD requests to a Backend.
So what I require here is a dynamically created set of nested promises.
In an abstract version the data array could look like:
var objArr = [
{
name: 'A',
subs: [
{
id: 1,
_action: 'create'
},
{
id: 2,
_action: 'create'
},
{
id: 3,
_action: 'delete'
}
]
},
{
name: 'B',
subs: [
{
id: 4,
_action: 'create'
},
{
id: 5,
_action: 'put'
}
]
},
{
name: 'C',
subs: []
}
];
I try to illustrate how the requests should be sent for this data following the order given by '_action'.
Get some transaction ID (see below)
As soon as transaction ID is there start to send requests for every Object in the Array given the following rules:
Per Object send all 'delete' requests at once if there are any.
After that or if there weren't any 'delete' requests send all 'put'
requests if there are any.
After 'delete' and/or 'put' send all 'create' requests if there are any.
As soon as all requests for an Object are done, do something per Object.
As soon as all Objects are done, close the Transaction.
How is it possible to create this dynamic nested/non-nested promise chain?
UPDATED CODE contains now Promise Creation
When calling the function below, first a TransactionService gets a transaction id which is required to be sent with each request. When everything is successful, the Transaction will be closed.
My current issue is that promises are not resolved in the correct order (while the OPTIONS preflight requests seem to be) and that this example creates Promises even if they are not required (e.g. for Object 'C' in the example above).
function startIt(objArr) {
TransactionService.getTransaction().then(function (transaction) {
var promiseArray = MyService.submit(objArr, transaction.id);
$q.all(promiseArray).then(function () {
Transactions.closeTransaction(transaction.id, function () {}).then(function () {
});
};
});
}
This is the function for 'submit':
function submit(objArr, transactionId) {
var promises = objArr.map(function (obj) {
return submitWithTransId(transactionId, obj)
.then(function (response) {
// Object done
});
});
return promises;
}
And this function is actually creating the Promises:
function submitWithTransId(transactionId, obj) {
var promisesDelete = [];
var promisesUpdate = [];
var promisesCreate = [];
angular.forEach(obj['subs'], function (sub) {
switch (sub._action) {
case 'delete':
promisesDelete.push(createPromise(sub, bulktransactionId));
break;
case 'put':
promisesUpdate.push(createPromise(sub, bulktransactionId));
break;
case 'create':
promisesCreate.push(createPromise(sub, bulktransactionId));
break;
}
});
var chainedPromises = $q.all(promisesDelete).then(function (deleteResponse) {
return $q.all(promisesUpdate).then(function (updateResponse) {
return $q.all(promisesCreate).then(function (createResponse) {
});
});
});
return chainedPromises;
And this is my createPromise function:
/** only simplified handling create case **/
function createPromise(sub, bulktransactionId) {
var queryParams = {};
if (bulktransactionId !== undefined && bulktransactionId !== null) {
queryParams.transaction_id = bulktransactionId;
}
var promise = MyResourceService.create(queryParams, sub).$promise;
promise.then(function (newSub) {
// do something with the new/updated/deleted sub, especially update view model
});
return promise;
}
/** MyResourceService **/
return $resource(ENV.apiEndpoint + 'api/v1/subs/:_id',
{
_id: '#id'
}, {
create: {
method: 'POST'
}
}
);
You can have a look at the following solution. The objective is to provide you some sort of structure. Please see, you will have to modify to your use.
var objArr = [{
name: 'A',
subs: [{
id: 1,
_action: 'create'
},
{
id: 2,
_action: 'create'
},
{
id: 3,
_action: 'delete'
}
]
},
{
name: 'B',
subs: [{
id: 4,
_action: 'create'
},
{
id: 5,
_action: 'put'
}
]
},
{
name: 'C',
subs: []
}
];
var promises = objArr.map(function(obj) {
return firstLevelPromise(obj)
.then(function(response) {
console.log(response); // promise for each object
return response;
});
});
$q.all(promises)
.then(function(response) {
console.log(response); // completion - close transaction
});
function firstLevelPromise(obj) {
var deletePromises = [];
var putPromies = [];
var insertPromies = [];
obj.subs.forEach(function(sub) { // Preparing promises array for delete, put and insert
if (sub._action === "delete") {
deletePromises.push(deletePromise(sub));
} else if (sub._action === "put") {
putPromies.push(putPromise(sub));
} else {
insertPromies.push(insertPromise(sub));
}
});
return $q.all(deletePromises) // executing delete promises
.then(function(deleteResponse) {
console.log("deleteExecuted: " + obj.name);
return $q.all(putPromies) // on completion of delete, execute put promies
.then(function(putResponse) {
console.log("putExecuted: " + obj.name);
return $q.all(insertPromies) // on completion of put, execute insert promises
.then(function(insertResponse) {
console.log("insertExecuted: " + obj.name);
return "object promise completed: " + obj.name; // on completion, return
});
});
});
}
function deletePromise(task) {
return $q.resolve(task); // write your delete code here
}
function putPromise(task) {
return $q.resolve(task); // write your put code here
}
function insertPromise(task) {
return $q.resolve(task); // write your insert code here
}
Please note, the above code will do the following
Prepare a collection of promises where there is one promise for each object in objectArray
Each promise will have promises chain i.e. delete promises, followed by put promises and finally followed by insert promises i.e. it ensures that for each object perform the delete tasks, then on completion perform the put tasks and then on its completion perform the insert tasks.
Here is a plunker and documentation for $q
UPDATE
The problem is with the createPromise function only. Update your code to following. The problem was that you are calling the then before returning, hence, it is likely that the you are returning a resolved promise like $q.resolve(). In this case, it is possible that a create request gets resolved before delete or put and a put calls gets resolved before delete. Hence, you should return the promise from here and perform the post action things in the $q.all block.
function createPromise(sub, bulktransactionId) {
var queryParams = {};
if (bulktransactionId !== undefined && bulktransactionId !== null) {
queryParams.transaction_id = bulktransactionId;
}
return MyResourceService.create(queryParams, sub).$promise;
}
Try this - the trick is to accumulate the promise (that's what I'm using the reduce for. Hope it helps.
// reversed the order => delete actions first; otherwise you may have to do extra logic may be needed
var objArr = [
{ name: 'A',
subs: [
{ id: null,
_action: 'delete'
},
{ id: 2,
_action: 'create']
}
},
{ name: 'B',
subs: [
{ id: 3.
_action: 'create'
}
]
];
Promise.all(objArr.map(obj => {
return obj.subs.reduce((accumulatedPromisedSub, sub) => {
return accumulatedPromisedSub.then(_ => yourRequestCallHere(sub) )
},
Promise.resolve(true) // you could also do your delete here if you like
)
}))
// OR going sort order agnostic:
Promise.all(objArr.map(obj => {
const deleteAction = obj.subs.find(sub => sub._action === 'delete');
return obj.subs.reduce((accumulatedPromisedSub, sub) => {
if (sub._action === 'delete') return accumulatedPromisedSub;
return accumulatedPromisedSub.then(_ => yourRequestCallHere(sub) )
},
deleteAction ? yourRequestCall(deleteAction) : Promise.resolve(true)
)
}))
I am so happy and thankful, I found it.
To my understanding I had two main issues in my code.
As $resource is not providing a (please forgive me, pros!) real $promise even when I use something like .get().$promise I had to
use bind to map the createPromise function to my arrays
do the mapping only directly before returning the $q.all promise.
In any other case $resource seemed to immediately fill its promise with an empty object and $q.all did not work anymore as promises looked like they where resolved.
Special thanks to #nikhil who supported me in finding this ugly complex thing and who earned the bounty.
This is the working snippet:
function submitWithTransId(transactionId, obj) {
var arrayDelete = [];
var arrayUpdate = [];
var arrayCreate = [];
angular.forEach(obj['subs'], function (sub) {
switch (sub._action) {
case 'delete':
arrayDelete.push(sub);
break;
case 'update':
arrayUpdate.push(sub);
break;
case 'create':
arrayCreate.push(sub);
break;
}
});
var promisesDelete = flattenArray(arrayDelete.map(createPromise.bind(undefined, obj, transactionId)));
return $q.all(promisesDelete).then(function (deleteResponse) {
console.log('Delete Promises ' + obj.name + ' resolved');
var promisesUpdate = flattenArray(arrayUpdate.map(createPromise.bind(undefined, obj, transactionId)));
return $q.all(promisesUpdate).then(function (updateResponse) {
var promisesCreate = flattenArray(arrayCreate.map(createPromise.bind(undefined, obj, transactionId)));
console.log('Update Promises ' + obj.name + ' resolved');
return $q.all(promisesCreate).then(function (createResponse) {
console.log('Create Promises ' + obj.name + ' resolved');
});
});
}).catch(function (error) {
console.log('Catched an error: ');
console.log(error);
});
});

angular chaining arrays of promises

I am building a website over a database of music tracks. The database is as follows :
music table contains musicid and title
musicrights table contains musicid and memberid
members table contains memberid and memberinfo.
I'm trying to build an array of objects in my database service, which each entry represents a track containing its rightholders (contains information aubout one rightholder but not his name) and their member info (contains name etc). The backend is sailsjs and the code is as follows :
angular.module("myapp").service("database", ["$q", "$http", function($q, $http) {
var database = {};
function getHolderMember(rightHolder) {
return ($http.get("/api/members?where=" + JSON.stringify({
memberid: rightHolder.memberid
})).then(function (res) {
rightHolder.member = res.data[0];
return (rightHolder);
}));
}
function getRightHolders(doc) {
return ($http.get("/api/musicrights?where=" + JSON.stringify({
musicid: doc.musicid
})).then(function(res) {
// array of promises :
// each rightholder of a document has to solve member info
var rightHolders = [];
for (var i in res.data) {
var rightHolder = {
member: res.data[i].memberid,
type: res.data[i].membertype,
rights: res.data[i].memberrights
};
rightHolders.push(getHolderMember(rightHolder));
}
return ($q.all(rightHolders));
}).then(function(rightHolders) {
// expected array of one or two rightholders,
// enriched with member information
// actually returns array of one or two arrays of 30 members
// without rightholder info
console.log(rightHolders);
doc.rightHolders = rightHolders;
return (doc);
}));
}
database.music = function(q) {
return ($http.get("/api/music?where=" + JSON.stringify({
or: [{
title: {
contains: q
}
}, {
subtitle: {
contains: q
}
}]
})).then(function(res) {
// array of 30 promises :
// each one of 30 documents has to resolve its rightholders
var documents = [];
for (var i in res.data) {
documents.push(getRightHolders(res.data[i]));
}
return ($q.all(documents));
}));
}
return (database);
}]);
The first array of promises seems to work as expected, but not the second one in getRightHolders. What is strange is that this function returns an array of one or two promises, which are rightHolders waiting for their memberinfo. But in the callback where I console.log the response, i get an array of one or two (as per the number of pushed promises) but this array's elements are arrays of 30 memberinfo instead of one memberinfo. I don't understand how this $q.all() call gets mixed with the previous-level $q.all.
The data structure is roughly like this
documents [ ] ($http => 30 responses)
music.musicid
music.rightHolders [ ] ($http => 1, 2, 3 responses)
rightholder.rights
rightholder.member ($http => 1 response)
member.memberinfo
Any help appreciated. Thank you !
UPDATE : Thank you for your answer, it worked like a charm. Here's the updated code, with also the migrate service which formats data differently (there is some database migration going on). I kept it out of the first example but your answer gave me this neat syntax.
angular.module("myApp").service("database", ["$q", "$http", "migrate", function($q, $http, migrate) {
var database = {};
function getHolderMember(rightHolder) {
return ($http.get("/api/members?where=" + JSON.stringify({
memberID: rightHolder.member
})).then(function(res) {
return (migrate.member(res.data[0]));
}).then(function(member) {
rightHolder.member = member;
return (rightHolder);
}));
}
function getRightHolders(doc) {
return ($http.get("/api/rightHolders?where=" + JSON.stringify({
musicID: doc.musicID
})).then(function(res) {
return (
$q.all(res.data
.map(migrate.rightHolder)
.map(getHolderMember)
)
);
}).then(function(rightHolders) {
doc.rightHolders = rightHolders;
return (doc);
}));
}
database.music = function(q) {
return ($http.get("/api/music?where=" + JSON.stringify({
or: [{
title: {
contains: q
}
},
{
subtitle: {
contains: q
}
}
]
})).then(function(res) {
return (
$q.all(res.data
.map(migrate.music)
.map(getRightHolders)
)
);
}));
}
return (database);
}
I'm not quite sure how you're getting the result you describe, but your logic is more convoluted than it needs to be and I think this might be leading to the issues you're seeing. You're giving the getRightsHolders function the responsibility of returning the document and based on your comment above, it sounds like you previously had the getHolderMember() function doing something similar and then stopped doing that.
We can clean this up by having each function be responsible for the entities it's handling and by using .map() instead of for (please don't use for..in with arrays).
Please give this a try:
angular
.module("myapp")
.service("database", ["$q", "$http", function($q, $http) {
var database = {};
function getHolderMember(memberId) {
var query = JSON.stringify({ memberid: memberid });
return $http.get("/api/members?where=" + query)
.then(function (res) {
return res.data[0];
});
}
function populateRightsHolderWithMember(rightsHolder) {
return getHolderMember(rightsHolder.memberid)
.then(function (member) {
rightsHolder.member = member;
return rightsHolder;
});
}
function getRightHolders(doc) {
var query = JSON.stringify({ musicid: doc.musicid });
return $http.get("/api/musicrights?where=" + query)
.then(function(res) {
return $q.all(res.data.map(populateRightsHolderWithMember));
});
}
function populateDocumentWithRightsHolders(document) {
return getRightsHolders(document)
.then(function(rightsHolders) {
document.rightsHolders = rightsHolders;
return document;
});
}
database.music = function(q) {
return $http.get("/api/music?where=" + JSON.stringify({
or: [{
title: {
contains: q
}
}, {
subtitle: {
contains: q
}
}]
})).then(function(res) {
return $q.all(res.data.map(populateDocumentWithRightsHolders));
});
}
return (database);
}]);

My service is returning the function's text and not an object

I have a service to share an object in my app... I want to post that object to the mongo db but when I call the function that should return the object it gives me the function's text.
The service is here:
angular.module('comhubApp')
.service('markerService', function () {
this.markers = [];
this.newMarker = { title: '',
description: '',
lat: '',
lon: '',
user: '',
created_at: '' };
// This is supposed to return the marker object
this.newMarker = function () {
return this.newMarker;
};
this.setTitle = function (title) {
this.newMarker.title = title;
console.log('title service set: ' + title);
};
this.setDescription = function (description) {
this.newMarker.description = description;
console.log('Description service set: ' + description);
};
this.setLat = function (lat) {
this.newMarker.lat = lat;
console.log('lat service set: ' + lat);
};
this.setLon = function (lon) {
this.newMarker.lon = lon;
console.log('lon service set: ' + lon);
};
this.reset = function () {
this.newMarker = { title: '',
description: '',
lat: '',
lon: '',
user: '',
created_at: ''};
}
this.setMarkers = function (markers) {
this.markers = markers;
}
this.markers = function () {
return this.markers;
}
this.addMarker = function (marker) {
//todo append marker
}
});
newMarker returns:
this.newMarker = function () {
return this.newMarker;
};
The Controller using the service is here
$scope.addMarker = function() {
if($scope.newMarker.title === '') {
console.log('newMarker title is empty');
return;
}
markerService.setTitle($scope.newMarker.title);
markerService.setDescription($scope.newMarker.description);
console.log(markerService.newMarker());
// $http.post('/api/markers', { name: $scope.newMarker });
// $scope.newMarker = '';
};
$scope new marker is form data.. i tried to put that right into my service with no success. Instead I out the form data into the controller then push it to the service. If there is a better way to do that please let me know.
If this service is bad in any other way let me know I am new to all this and so I followed another answer I saw on here.
You are overriding your object with function. Just give them different names and it should work just fine.
this.newMarker = { ... };
this.getNewMarker = function () { return this.newMarker };
EDIT:
You should also always create new instance from marker. Otherwise you just edit the same object all the time. Here is example I made. Its not best practice but hope you get the point.
angular.module('serviceApp', [])
.factory('Marker', function () {
function Marker() {
this.title = '';
this.descrpition = '';
}
// use setters and getters if you want to make your variable private
// in this example we are not using these functions
Marker.prototype.setTitle = function (title) {
this.title = title;
};
Marker.prototype.setDescription = function (description) {
this.description = description;
};
return Marker;
})
.service('markerService', function (Marker) {
this.markers = [];
this.getNewMarker = function () {
return new Marker();
}
this.addMarker = function (marker) {
this.markers.push(marker);
}
})
.controller('ServiceCtrl', function ($scope, markerService) {
$scope.marker = markerService.getNewMarker();
$scope.addMarker = function () {
markerService.addMarker($scope.marker);
$scope.marker = markerService.getNewMarker();
}
$scope.markers = markerService.markers;
});
You could also create Marker in controller and use markerService just to store your object.
And working demo:
http://jsfiddle.net/3cvc9rrs/
So, that function is the problem. I was blindly following another example and it was wrong in my case. The solution is to remove that function and access markerService.newMarker directly.
I am still a big enough noob that I am not sure why the call was returning the function as a string. It seems to have something to do with how it is named but it is just a guess.

Knockout model breaking on array length in computed observable

Slowly extending my nested form at http://jsfiddle.net/gZC5k/1004/ I'm running into a difficulty getting to work ko.computed that I want to use to compute the number of children in the nested JSON array. The code that breaks is at // this breaks
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (contact) {
return {
firstName: ko.observable(contact.firstName),
lastName: ko.observable(contact.lastName),
isKey: ko.observable(contact.isKey),
gender: ko.observable(contact.gender),
phones: ko.observableArray(ko.utils.arrayMap(contact.phones, function (phone) {
return {
type: ko.observable(phone.type),
number: ko.observable(phone.number),
calls: ko.observableArray(phone.calls),
callsVisible: ko.observable(false)
};
})),
addresses: ko.observableArray(contact.addresses),
optionGender: optionGender,
phonesVisible: ko.observable(false),
addressesVisible: ko.observable(false),
// this breaks
// numberOfPhones: ko.computed(function (contact) {
// return contact.phones.length;
// });
};
}));
Where is the error?
I think that you're going to need to create each contact as a function:
var ContactModel = function(contact)
{
var self = this;
self.firstName = ko.observable(contact.firstName);
self.lastName = ko.observable(contact.lastName);
self.isKey = ko.observable(contact.isKey);
self.gender = ko.observable(contact.gender);
self. phones = ko.observableArray(ko.utils.arrayMap(contact.phones, function (phone) {
return {
type: ko.observable(phone.type),
number: ko.observable(phone.number),
calls: ko.observableArray(phone.calls),
callsVisible: ko.observable(false)
};
}));
self.addresses = ko.observableArray(contact.addresses);
self.optionGender = optionGender;
self.phonesVisible = ko.observable(false);
self.addressesVisible = ko.observable(false);
self.numberOfPhones = ko.computed(function () {
return self.phones().length;
});
return self;
};
And create it like this:
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (contact) {
return new ContactModel(contact);
}));
You can try this:
numberOfPhones: ko.computed(function () {
return contact.phones.length;
})

Resources