AngularJS - Service exposing a string - javascript

I have a problem where I have a service which exposes a string. There is a function on this service which updates the value of the string. The service internally knows that the value has changed, however, externally the value NEVER updates.
If I nest the string inside an object then it all works, but I don't really want to nest it.
Can anyone explain why this is happening? It feels like it should work and feels like I am missing something basic.
Service:
myApp.service('neverChanges', function () {
var id = 'Hello';
var changeId = function () {
console.log('pre change:' + id);
id = 'World';
console.log('post change:' + id);
};
return {
id: id,
changeId: changeId
};
});
Controller:
myApp.controller('Controller1', ['neverChanges', function (neverChanges) {
this.idValue = function() {
return neverChanges.id;
}
this.clickHandler = function () {
console.log('Trust me, I did fire...');
neverChanges.changeId();
console.log('external post change:' + neverChanges.id);
};
}]);
Markup:
<div ng-app="myApp">
<div ng-controller="Controller1 as vm">
<h3>This will never change:</h3>
<button ng-click="vm.clickHandler()">Click Me!</button>
<p>Values:</p>
<p>id: {{vm.idValue()}}</p>
</div>
Fiddle showing the two scenarios: http://jsfiddle.net/KyleMuir/2nhoc2rz/

You have to use this:
var changeId = function () {
console.log('pre change:' + id);
this.id = 'World';
console.log('post change:' + id);
};

The problem is that you have local variable id: var id = 'Hello';
Later in the function you copy the value of this local variable into an object that you return:
return {
id: id,
changeId: changeId
};
So from here on the returned object has a propery id which is COPY of your original id variable, and your changeId function just changes your local variable, but of course not the copy.
To prevent that you would need to keep a reference to the object that you return. That could e.g. look like this:
var result = {id:'Hello'};
result.changeId = function () {
console.log('pre change:' + result.id);
result.id = 'World';
console.log('post change:' + result.id);
};
return result;
See working version: http://jsfiddle.net/y4mxazqh/
This way you get rid of the local variable id and you can change the object that you returned.
Of course the return also creates a copy of the reference to your local variable result. But since both the returned reference and your local variable point to the same object, you can change the content of that object and after that both references still reference an object that's id has now changed.
EDIT:
Essentially originof's answer solves the same problem with a different approach: Because you call vm.clickHandler() the function clickHandler() gets the this set to vm and vm in turn is the object that you returned. Thus you can access the returned object. However be aware, that if you execute code like this:
var func = vm.clickHandler();
func();
this would not be the same. In this case this would not get set to vm and you are lost. You should be aware of this situation when you choose the this-based solution.

You pass function in another object and function scope is changed
Try:
this.id = "World"
instead of
id = "World"

Related

Assigning a declared variable to a string in array javascript

Let's say I have these global variables:
var name, car;
Then I declare some values:
const languageStrings = {
WELCOME_MESSAGE: 'Welcome #name, you have a #car',
NAME_USER: "#name",
CAR_USER: '#car'
};
And then I need to assign it to a function.
For example:
if (firstTime){
welcomeMsg = WELCOME_MESSAGE;
}
Now, I have two questions:
1) How would I insert a variable inside of a string so it is dynamically updated when the value pair is called?
2) How would I do the same using JSON?
You can't have a JSON structure or string "automatically" update when some variable changes. There are other ways to do this type of templating, though. You could create a function to create a welcome message when you need one:
function getWelcomeMessage(name, car) {
return "Welcome "+name+", you have a "+car;
}
Then you'd do something like welcomeMsg = getWelcomeMessage("Joe", "Camry");
If you don't want to write a function for every template (i.e. if you have lots of them), then you could use String.replace like this:
function applyTemplate(template, params) {
return template.replace(/#(\w+)/g, (m, name) => params[name]);
}
Example usage:
function applyTemplate(template, params) {
return template.replace(/#(\w+)/g, (m, name) => params[name]);
}
const WELCOME_TEMPLATE = "Welcome #name, you have a #car";
var name = "Joe";
var car = "Camry";
var welcomeMessage = applyTemplate(WELCOME_TEMPLATE, {name, car});
console.log(welcomeMessage);
You would have to make a function that returns the value of the variable.
In your case:
welcomeMessage = function(){
return WELCOME_MESSAGE
}
and you would reference the variable with:
welcomeMessage()
So, you'd be assigning a variable as a function that returns the current value of the other variable. You get the value by calling your variable as a function.
String in JavaScript is primitive type, it's passed by value. So once a variable is assigned with a string, it will never change until you explicitly assign another value (or object reference) to it.
However, you can ask object type for help, which could make your data reactively (or dynamically, if you prefer this word) update under certain conditions.
var languageStrings = {
WELCOME_MESSAGE: '',
NAME_USER: '',
CAR_USER: ''
}
Object.defineProperty(languageStrings, 'name', {
get: function (name) {
return this.NAME_USER
},
set: function (name) {
this.NAME_USER = name
this.WELCOME_MESSAGE = `Welcome ${this.name}, you have a ${this.car}.`
}
})
Object.defineProperty(languageStrings, 'car', {
get: function (car) {
return this.CAR_USER
},
set: function (car) {
this.CAR_USER = car
this.WELCOME_MESSAGE = `Welcome ${this.name}, you have a ${this.car}.`
}
})
Now, whenever you change languageStrings.name or languageStrings.car, all three strings you want will automatically adopt the new value you just set:
languageStrings.name = 'Leo'
languageStrings.car = 'BMW'
for (let key in languageStrings) {
console.log(`${key}: ${languageStrings[key]}`)
}
// WELCOME_MESSAGE: Welcome Leo, you have a BMW.
// NAME_USER: Leo
// CAR_USER: BMW
You don't have to manually call applyTemplate all the time, like in #qxz's answer (I'm not saying his wrong, though).
Also, please notice that even name and car are not enumerable - they will not be accessed with for in, for of, or Object.keys! This is great, your implementation details are concealed, no worries or confusions to other developers who use your code.
In fact, such reactive model is widely used in front-end MV* frameworks nowadays, e.g. Vue.
Regarding your second question, I didn't get it honestly. Just JSON.parse then it's all ordinary JavaScript, isn't it?
the answer to your question on how to insert variables inside a string is:
WELCOME_MESSAGE: 'Welcome ' + name + ', you have a ' + car,
or before defining:
function mesg(name, car){
return "Welcome" + name + ", you have a " + car;
}
mesg(bob, BMW);
in this case, the name and car is defined after.

Data change inside Service does not update the Scope

I am storing my data inside Angular service (to share across controllers) and provide a function to update it:
.factory('DataManager', function($rootScope){
var _json = {init: 'from DataManager'};
return {
json: _json,
changeTo: function (newVal) {
_json = newVal;
console.log("Changing to ", _json);
}
};
})
Then I simply pull the data into controller:
.controller('MainCtrl', function(DataManager){
var MC = this;
MC.json = DataManager.json;
MC.changeTo = DataManager.changeTo;
})
The problem is - when MC.changeTo function is called, it does update _json variable inside the Service, but not in the controller!
Here is JSbin illustrating the problem.
Any idea what I am doing wrong here?
Try this. Your code doesn't work because _json variable is refering to diffrent object each time. angular.copy copies the new object to the same reference.
changeTo: function (newVal) {
angular.copy(newVal, _json);
console.log("Changing to ", _json);
}

Can't pass around returned object from server

I am picking up a project started by a peer, and having trouble with an API call.
This is my angular controller:
angular.module('oowli').factory('Note', ['$http', function($http) {
var Note = function(data) {
angular.extend(this, data);
};
Note.prototype.save = function() {
var note = this;
return $http.post('/notes', this).then(function(response) {
note = response.data;
}, function(error) {
return error;
});
};
return Note;
}]);
and the call is executed in this function:
var saveNote = function(Note, scope){
return function(span, phrase){
var noteObj = new Note({
user: scope.global.user._id,
content: span.innerText
});
noteObj.save().then(function(){
console.log(noteObj);
});
};
};
problem is, after saving the note, noteObj is the original, not the one that comes back from the server (with an _id field).
Debugging on the Note.prototype.save, response.data comes with the _id;
I need to know how to have access to the returned Note object, in the saveNote function.
You are assigning the new object to the local variable 'note' in the Note.prototype.save function, but then not doing anything with it.
The quickest solution is to copy the properties of the returned object to your note rather than assign to it, so instead of:
note = response.data;
This might work:
angular.copy(response.data, note)
In general though I don't like the approach of having the Note class responsible for saving itself. I would create a note service that saves the object.

AngularJS: how to change scope in one controller with trigger in the other controller

I have two Controllers and one shared Service.
First Controller renders some text. Other Controller has a button that had to change the First Controllers scope and rerender text (with new value)
HTML
<div ng-controller="Controller1">
Value is: {{object}}
</div>
<div ng-controller="Controller2">
<button ng-click="changeValue()">Click to change Value</button>
</div>
JS
function Controller1($scope, mainServices) {
$scope.object = mainServices.getValue();
};
function Controller2($scope, mainServices) {
$scope.changeValue = function(){
mainServices.getValue('Hello');
console.log('button is pressed');
};
};
testApp.factory('mainServices', function(){
return {
getValue: function(update) {
var defaultValue = 'Hi';
if( typeof(update)==='undefined') { value = defaultValue; }
else { value = update; }
console.log('should be changed to', update);
return value;
}
}
});
On Plunker http://plnkr.co/edit/IWBEvAfvLbzJ0UD2IqGB?p=preview
Why doesn't this work? How to tell Angular to watch for changes?
The code
Well, I think this is not the best solution, but it is possible for your problem.
Instead of just changing the value itself, you have to create an object before the return statement in the factory.
var obj = {}
And then you just change a property of this object:
if( typeof(update)==='undefined') { obj.val = defaultValue; }
else { obj.val = update; }
And return the object:
return obj;
The other controller can be left unaffected, however you have to change your html:
You have to put in
{{object.val}}
in order to listen to changes.
http://plnkr.co/edit/rjsrkbaQ8QyWGGFI9HYl?p=preview
Why does this work?
It is quite simple: In javascript, if you call a function with a primitive return value like a string (yes, strings CAN be primitives in javascript), it is just a copy of this primitive, so with
$scope.object = mainServices.getValue();
you just pass a value to $scope.object, which is not affected by other calls of getValue()
But if we return an object from getValue(), we get a reference to this object. So if we make changes to this referenced object in getValue(), angularjs will be able to notice the changed object because it is tracked in both controllers.
Therefore we have to reference the same object again and again, but since javascript supports closures, this is pretty easy.

Change var in object literal function

Hi guys I am writing some code using the object literal pattern, I have function that returns a value:
'currentLocation': function() {
var cL = 0;
return cL;
},
I then need to update the variable 'cL' from another function like this:
teamStatus.currentLocation() = teamStatus.currentLocation() + teamStatus.scrollDistance();
This part is part of another function - however I get an error back stating: invalid assignment left-hand side
I am guessing I can not update the variable in this way, could anyone suggest a better method or point me in the right direction.
Any help would be greatly appreciated.
Going to add more code to highlight what I am trying to do:
'currentLocation': function() {
var cL = 0;
return cL;
},
'increaseTable': function() {
if (teamStatus.currentLocation() <= teamStatus.teamStatusTableHeight() ) {
teamStatus.currentLocation = teamStatus.currentLocation() + teamStatus.scrollDistance();
$("#tableTrackActual").animate({scrollTop: (teamStatus.currentLocation)});
$("#tableMembers").animate({scrollTop: (teamStatus.currentLocation) });
//console.log(teamStatus.currentLocation());
teamStatus.buttonRevealer();
}
}
As you can see increaseTable should update the value of currentLocation - help this sheds more light on what I am trying to achieve.
You're writing teamStatus.currentLocation() =, which calls the function teamStatus.currentLocation and tries to assign to the return value. That isn't valid. You want just teamStatus.currentLocation = — no function call.
The variable inside your function is completely private to that function (and any functions defined within it). If you need to create a number of functions that share a set of private variables, you can do that with a closure. For instance:
var Thing = (function() {
var thingWideData;
function getData() {
return thingWideData;
}
function setData(newData) {
thingWideData = newData;
}
return {
getData: getData,
setData: setData
};
})();
What that does is create a Thing object which has getData and setData functions available for it, which get and set the completely private thingWideData variable contained by the anonymous closure. More about this pattern here and here, although the latter of those is more about private methods than private data.
What your code produces is:
0 = 0 + <some number>
Which variable do you want to update? cL? You are declaring it in the function, you cannot assign a value to it from outside. Depending on the rest of your code, you might be better off with getters and setters:
var object = {
_cL = 0,
get currentLocation() {
return this._cL;
},
set currentLocation(value) {
this._cL = value;
}
}
then you can do:
teamStatus.currentLocation = teamStatus.currentLocation + teamStatus.scrollDistance();
Update:
Regarding IE: If currentLocation should actually be just a number, it might be sufficient to just declare it as property:
var obj = {
currentLocation: 0
}

Categories