Angular JS $scope, databinding, and variables changing. - javascript

So I have an APP I'm working on in Angular JS v1.4.0 and I'm running into a scoping issue. There's a section that has a form that needs to be submitted, but the data needs to be modified before its sent over. I'm currently trying to do this in the javascript before making the call to the server.
I have $scope.msgEditor, that is an object of a bunch of different values that are necessary for the form, as well as the message variables itself. The important part looks something like this:
msgEditor [
msg: {
groups: {
selected: {
0: '1',
1: '2',
}
}
}
]
I'm trying to take this $scope variable, assign it to a local variable, and begin parsing the data like such:
$scope.formOnSubmit = function () {
formattedMessage = formatDataForSave($scope.msgEditor.msg);
};
function formatDataForSave(message) {
message.groups = message.groups.selected.join(', ');
return message;
}
What I want to happen, is $scope.msgEditor.msg to not change at all, and formattedMessage to be returned from the second function, so it can be placed into a $http call. However, the join changes message, formattedMessage, AND $scope.msgEditor.msg
I did a bit more testing, to see what was happening:
$scope.formOnSubmit = function () {
$scope.test = $scope.msgEditor.msg;
var formattedMessage = $scope.test;
formattedMessage = formatDataForSave(formattedMessage);
};
And found that the change made to formattedMessage, would change $scope.test, which would change $scope.msgEdtior.msg.
Any direction on why this is happening, or how to prevent it would be amazing.

I believe you are confusing about passing arguments into functions in javascript: in javascript all arguments are passed by reference so the consequence is what you are experiencing. Have a look at angular.copy function.
https://code.angularjs.org/1.3.17/docs/api/ng/function/angular.copy
I cannot test this but you could try:
$scope.formOnSubmit = function () {
var msgCopy = angular.copy($scope.msgEditor.msg);
formattedMessage = formatDataForSave(msgCopy);
};
function formatDataForSave(message) {
message.groups = message.groups.selected.join(', ');
return message;
}

Related

Vanilla Javascript using function on Arrays with multiple values per element

I want to use the following (creating a gallery card with information being provided through array for initial and a form for adding additional) I've been able to get the following function to work on information being added through the form but have not been able to get the function to work on the array I have.
function galleryAddItems(image, title){
const galleryElement = galleryTemplate.cloneNode(true);
galleryElement.querySelector(".gallery__image").src = image;
galleryElement.querySelector(".gallery__text").textContent = title;
galleryContainer.prepend(galleryElement);
}
on the array I have
const initialCards = [
{
name: "The End Place",
link: "images/cliffside__Katie-Rodriguez.jpg"
},
{
name: "Turn That Leaf Over",
link: "images/leaf__chuttersnap.jpg"
},
...
];
Currently have a second function set as
initialCards.forEach(function(thingy){
const galleryElement = galleryTemplate.cloneNode(true);
galleryElement.querySelector(".gallery__image").src = thingy.link;
galleryElement.querySelector(".gallery__text").textContent = thingy.name;
galleryContainer.append(galleryElement);
});
For this specific instance and want to avoid duplicating the code to make this work.
I've previously had galleryAddItems include .value in the function as it's needed in a different place but added that when it's being called with the hopes of it working here.
I've tried
initialCards.forEach(galleryAddItems(link, name))
and tried things like initialCards.link/.name
initialCardsLink/initialCardsName
If I don't put in parameters I'm not getting the image URL working and index is placed where I'm looking to have the name/title.
An easy way without changing your galleryAddItems() function would be:
initialCards.forEach(thingy => galleryAddItems(thingy.link, thingy.name));
Personally, I would probably think about changing the first function to something like:
function galleryAddItems(image) {
const galleryElement = galleryTemplate.cloneNode(true);
galleryElement.querySelector(".gallery__image").src = image.link;
galleryElement.querySelector(".gallery__text").textContent = image.name;
galleryContainer.prepend(galleryElement);
}
so that you could simply do:
initialCards.forEach(galleryAddItems);
You need to pass a function to the forEach function, currently, you're passing the return value of galleryAddItems since you're calling it.
I would suggest refactoring your code so that galleryAddItems takes a card as a parameter and then call it for each card using forEach.
function galleryAddItems(card){
const galleryElement = galleryTemplate.cloneNode(true);
galleryElement.querySelector(".gallery__image").src = card.link;
galleryElement.querySelector(".gallery__text").textContent = card.name;
galleryContainer.prepend(galleryElement);
}
// Call galleryAddItems for each card
initialCards.forEach(galleryAddItems)

Passed-in Values Not Available at Run-time of Function in Angular App

I realize there is something I'm missing in terms of how and specifically when the products of certain functions are available in JavaScript.
In my Angular app, in order to get a user's initials, I am parsing data being returned from the API, and retrieving the first letter of the firstName, as well as the first letter of lastName in two different functions. These two functions are working as expected, and I can see the correct results in the console:
getFirstNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const firstName = userInfo.name.first;
const firstNameFirstLetter = firstName.trim().charAt(0);
console.log(firstNameFirstLetter);
return firstNameFirstLetter;
}
}
getLastNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const lastName = userInfo.name.last;
const lastNameFirstLetter = lastName.trim().charAt(0);
console.log(lastNameFirstLetter);
return lastNameFirstLetter;
}
}
Now comes the part I'm not fully understanding. When I then pass the returned values of these two functions, in order to get the initials, like this:
getInitials(firstNameFirstLetter, lastNameFirstLetter) {
if (this.authenticationService.isAuthenticated()) {
if (!this.firstNameFirstLetter || !this.lastNameFirstLetter) {
console.log('Names not ready!');
return;
} else if (this.firstNameFirstLetter && this.lastNameFirstLetter) {
console.log(firstNameFirstLetter + lastNameFirstLetter);
return firstNameFirstLetter + lastNameFirstLetter;
}
}
}
... I get "Names not ready!" printed to the console each time.
By the way, I am running these functions within Angular's ngOnInit life cycle hook, like this:
ngOnInit() {
this.getFirstNameFirstLetter();
this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
I know this has something to do with what's available when, because I get 'undefined' when I use break points and debug the two values being passed into the "getInitials()" function. In other words, the function doesn't have access to the returned values of the other two functions at the time it's run -- hence I'm getting 'Names not ready!' printed to the console. My question is, what am I missing, architecturally, to resolve this kind of issue?
So what is happening here is that JavaScript doesn't think you are using the return values for getFirstNameFirstLetter and getLastNameFirstLetter, so when it makes the call, instead of waiting for that call to finish, it goes on to the next one, which introduces a race condition. if you simply change it to
ngOnInit() {
let temp1 = this.getFirstNameFirstLetter();
let temp2 = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
then it will wait for the previous functions to finish before calling the next.
Also, I don't use const very often, so I could be wrong and it could follow different scope rules, but by normal scope rules, setting a variable in that function, it is only available in that function, you would need to set it as
this.firstNameFirstLetter = firstName.trim().charAt(0);
to have access to it outside the function.
Or, so as to kill two birds with one stone, you could do
ngOnInit() {
this.firstNameFirstLetter = this.getFirstNameFirstLetter();
this.lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
or
ngOnInit() {
let firstNameFirstLetter = this.getFirstNameFirstLetter();
let lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(firstNameFirstLetter, lastNameFirstLetter);
}
depending on if you need the variables again or just for that function.

Node.JS Global Variable Not Staying Updated

I have a global object variable set called _storage. It looks like this:
_storage = {
test : {
}
};
When a user sends a request to add their info to the "test" object inside of storage, it's supposed to set _storage.test[1] = true; for example.
After it sets that, I did this to see what the variable looked like:
console.log(_storage.test); and it outputted the correct response:
{ '1' : true }
All seems perfect, until the user sends a request to get all the current items in the test object. This is where the problem is. The variable seems to empty itself and go back to an empty object {}.
The user sends the request to see what's inside of test after setting the data in test, so it can't be an issue caused by the user requesting to see the data before even setting it. This is done inside two separate modules, one sets the data and another one gets the data (the two modules aren't just for that only, obviously) but the _storage variable is global and shouldn't be affected by two separate modules.
Here's an example of how it goes down:
User sends request to set 1 = true inside of _storage.test, making _storage.test[1] = true;
_storage.test is now { test : { '1' : true } } according to console.log
User sends request to view all data from _storage.test
_storage.test is {} according to console.log, as if it isn't staying set
Here's the code that I use to Get, Set, and Remove the data:
exports.GetUserData = function () {
return global._storage.test;
};
exports.RemoveUserData = function (uid) {
delete global._storage.test[Number(uid)];
};
exports.AddUserData = function (uid) {
global._storage.test[Number(uid)] = true;
};
Ok, so I think you're not showing all your code, but here's what I did which worked:
// index.js
'use strict';
global._storage = global._storage || {};
global._storage.test = global._storage.test || {};
exports.GetUserData = function () {
return global._storage.test;
};
exports.RemoveUserData = function (uid) {
delete global._storage.test[Number(uid)];
};
exports.AddUserData = function (uid) {
global._storage.test[Number(uid)] = true;
};
// other.js
'use strict';
global._storage = global._storage || {};
global._storage.test = global._storage.test || {};
exports.GetUserData = function(){
return global._storage.test;
}
And then I ran this from my repl:
> var it = require('./');
undefined
> var other = require('./other');
undefined
> it.AddUserData('1234');
undefined
> it.GetUserData();
{ '1234': true }
> other.GetUserData();
{ '1234': true }
>
So that works as designed. Thing is this... I've been working with node since 2009, and I've never felt the need to use global variables. I doubt your app needs it. So even though this'll work for you, I recommend you think of another way to do it.

Meteor - Where to put calculating methods properly

I'm reading and watching Tutorials about Meteor since 1-2 Weeks. I've learned about how to structure a meteor app regarding server and client side code, accounts, security etc.
What i could not figure out:
Where do i put the calculation logic properly?
For example:
A user puts data in a form and the data is saved in the database. Depending on this input data i want to do several calculations by putting the data through lets say a chaining of around 20 Methods, and finally display some results.
At the moment i have all of these Methods inside the file where the Template.displayResults.helper is.
When i put them in another file they don't get recognized, i think because of the wrapper Meteor puts around.
Example: I have a collection of DIY projects and each of the projects has a field with an array of utilities that are neccessary for the project.
Projects = new Mongo.Collection('projects');
/*
exampleProject = {
"name": "Kitchen table",
"utilities": ["Hammer", "Glue"]
}
*/
I want to display all possible DIY projects depending on the utilities the user has checked.
The UI has a group of checkboxes via the user can select a bunch of utilities he wants to use.
These values are saved in a collection.
Utilities = new Mongo.Collection('utilities');
/*
exampleUtility = {
"name": "Hammer",
"checked": true
}
*/
Then i want to calculate the possible Projects...
Template.displayResults.helpers({
projectsPossible: function () {
var utilitiesCheckedDB = Utilities.find({
checked: true
}).fetch();
var projectsAll = Projects.find().fetch();
return projectsPossible(utilitiesCheckedDB, projectsAll);
}
});
// Returns an array of all possible projects depending on the selected utilities
function projectsPossible(utilitiesCheckedDB, projectsAll) {
var result = [];
_.each(projectsAll, function (project) {
if (project.utilities.length === _.intersection(project.utilities, checkedCheckboxesList(utilitiesCheckedDB)).length) {
result.push(project);
}
});
return result;
}
// Returns an array of all checked utilities in the current checkbox database
function checkedCheckboxesList(checkedCheckboxesDB) {
var result = [];
_.each(checkedCheckboxesDB, function (checkbox) {
result.push(checkbox.name);
});
return result;
}
The question is: There are more methods like "projectsPossible" and "checkedCheckboxesList". Where do i put these methods to get a good structure?
Thanks in advance!
Vin
If you want to register global helpers, just use Template.registerHelper(name, function), for instance:
Template.registerHelper('projectsPossible', function() {
var utilitiesCheckedDB = Utilities.find({
checked: true
}).fetch();
var projectsAll = Projects.find().fetch();
return projectsPossible(utilitiesCheckedDB, projectsAll);
});
If you want to make the functions projectsPossible(utilitiesCheckedDB, projectsAll) or checkedCheckboxesList(checkedCheckboxesDB) accessible from other (client) files, you can make them global. For example:
projectsPossible = function(utilitiesCheckedDB, projectsAll) {
var result = [];
_.each(projectsAll, function(project) {
if (project.utilities.length === _.intersection(project.utilities, checkedCheckboxesList(utilitiesCheckedDB)).length) {
result.push(project);
}
});
return result;
};
You can make model classes, using the transform option for collections. For an ES5 example, see the docs: http://docs.meteor.com/#/full/mongo_collection
Also, you have to make that model class or function global by not using var.
(function() {
foo = function foo() {
alert("fooh")
}
})()
In the above example, without foo =, the foo function would only be visible inside its own file because of the wrapper.

Updating a 'this' value in a service via a function

I'm quite new to Angular and am trying to understand how everything works. I've been poking around and couldn't find any information on how to do this. So, I've got a service that defines
this.totalCount = 0;
In my controller, my get request retrieves some emails and then executes a function called addMessage for each message it retrieves. The addMessage function is in my service.
The function in my service looks like this:
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
}
Basically, I am trying to increment this.totalCount each time this function is executed so that it will update and then can be displayed in the view. I have it displaying in the view currently, however its number always remains 0.
I've tried the following:
1.
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
this.totalCount++;
}
2.
var count = this.totalcount
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
count++; //and then attempted to display this value in the view but with no luck
}
Any tips would be greatly appreciated.
try this:
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
}
I assume that you're binding the var this way in your controller and your view
Service :
this.totalCount = 0;
this.totalCount++;
Controller :
$scope.totalCount = service.totalCount;
view :
{{totalCount}}
And if you're actually doing it like this, you should face this kind of trouble.
The main problem is that totalCount is a primitive var and doing this.totalCount++ will break the reference. If you want to keep some var you should bind it as a sub-object.
This way :
Service :
this.utils = {};
this.utils.totalCount = 0;
this.utils.totalCount++;
Controller :
//This is the most important part. You bind an object. Then even if you loose the totalCount reference, your object will keep its own reference.
$scope.myServiceUtils = service.utils;
View :
{{myServiceUtils.totalCount}}
Actually in service (it's a matter of taste) i prefer a lot to use the object syntax instead of this (as "this" can be confusing)
This way :
var service = {};
service.utils.totalCount = 0;
service.addItem = function(){
...
}
return service;
Hope that was your issue.
You pass argument to another function which has different scope than your service. It is trick with assigning current object to variable, which is visible from function.
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
that.totalCount++;
}
Should work.
So you assign that variable with current object, which is visible in inner function scope.
In a function addMessage body, this refers to function scope which is new, and there is no compiler error, but messagesList is a null object and totalCount is incremented, but after program leave function, it's not visible in service, because it is in a function scope which isn't assigned to any variable.
To update service variable as it changes in your controller, use $watch.
$scope.$watch(function() {
return messagesService.totalCount;
}, function(new,old){
$scope.totalmessagecount = messagesService.totalCount;
});
First parameter of $watch if function which return observed for change element. Another is standard function to perform operation after update.

Categories

Resources