AngularJS - Using Model in Controller causing Model to update - javascript

I have an Angular application where in I'm pulling from a model some data which is saved on the load of the app. For simplicity sake, I've explicitly defined the data which is being pulled.
The issue I have is that in one of my controllers I am running a function on load of the controller which modifies the data pulled from the model. The point is that I want that extra data for that page which is using that controller only. I don't want that data to be saved back into the model (which is what's happening).
My model:
'use strict';
(function () {
var PotsMod = function ($log, _) {
return {
pots: [
{"comp" : "comp1"},
{"comp" : "comp2"}
],
getPots: function () {
return this.pots;
},
};
};
angular
.module('picksApp.models')
.factory('PotsMod', PotsMod);
})();
My controller:
(function () {
function AdmCtrl($log, $routeParams, PotsMod) {
var vm = this;
vm.pots = PotsMod.getPots();
vm.init = function() {
// populate pot.competition
_.forEach(vm.pots, function(pot) {
pot.comp = "test";
});
console.log(PotsMod.getPots());
}
vm.init();
}
angular
.module('picksApp.controllers')
.controller('AdmCtrl', AdmCtrl);
})();
The final line in vm.init(), PotsMod.getPots(), returns to me the updated model, with the values of "comp" as test.
So I tried this instead - I put the debug line under vm.pots like so:
var vm = this;
vm.pots = PotsMod.getPots();
console.log(vm.pots);
vm.init = function() {....
This also returns to me the array where the object values are test...
So I tried one final thing and added an extra debug line in the vm.init() function too:
var vm = this;
vm.pots = PotsMod.getPots();
console.log(vm.pots);
vm.init = function() {
// populate pot.competition
_.forEach(vm.pots, function(pot) {
console.log(pot.comp);
pot.comp = "test";
});
console.log(PotsMod.getPots());
}
vm.init();
The result of this confuses me... The output in the console reads:
[{"comp":"test"},{"comp","test"}]
comp1
comp2
[{"comp":"test"},{"comp","test"}]
I must be missing something here because I don't understand how it can be defining a variable using a model's value, printing that variable with the updated values, then using the old values and printing them, then printing the updated values again from the model (even though nothing in this code touches the model).
Any help would be brilliant please, I see to be making a fundamental mistake somewhere. Thank you.

You're referencing the service's pots object in your controller, so your controller code is also modifying the service's code.
I created a Plunker to demonstrate how angular.copy() creates a deep copy of your service's 'pots', and thus your controller's model is no longer referencing the original.
In your case, all you need to change is vm.pots = angular.copy(getPots());
http://plnkr.co/edit/jg5mWIWds1KMJd51e3o5?p=preview

Related

AngularJS - Share scope value between controllers

I have a textarea in my HTML like this:
<textarea ng-model="commentBox"></textarea>
To access this i simply use "$scope.commentBox" in my controller. But my question is, how do i access the same commentBox within a different controller?
I do have a factory/service setup for this purpose, but i cant figure out how to get the commentBox value in there, for my other controller to use it.
In my factory i have an object var saved = {} and I want to add a property called "comment", with the value of whatever is inside the textarea. Like this saved.comment = commentbox And then access that value from the other controller.
I'm still new at Angular and tried to send the scope information in a parameter to the facory.
var saved = {};
factory.addComment = function (commentbox) {
saved.comment = commentbox
}
Then have my controller send the scope information on a button click,
$scope.testFunction = function () {
myFactory.addComment($scope.commentBox);
}
But yeah, that did not work out.
Note that i need this to work within the factory, and not by using another ng-controller in the HTML.
You need to return your saved var in your factory
var saved = {};
factory.addComment = function (commentbox) {
saved.comment = commentbox
}
return saved
Here there is an example using a service
app.service('fakeService', function() {
var savedData = {};
var addComment = function(newComment) {
savedData.commnet = newComment;
};
var getComment = function(){
return savedData.comment;
};
return {
addComment: addComment,
getComment: getComment
};
});
To inject a factory/service in your controller
app.controller('fakeController', ['$scope','yourFactory',function ($scope, yourFactory) {
console.log(yourFactory.comment) // here you should log saved comment
}])
Then in your controller, you can inject the factory/service and access to saved data. But remember, is your refresh your page, data will be lost, so, to avoid that, you should persist data on your DB.

AngularJS - Sort array inside service, retain binding in controller/view

I'm looking for a way to sort an array inside an Angular service, and still retain the correct bindings in the controller.
If I skip the sorting, the bindings work great, but the array isn't ordered as I need it to be.
Whenever I perform the sort using Lodash's _.sortBy or angular's $filter('orderBy') service, one of two things happens:
The array in the service is sorted correctly, but the binding to the controller is severed due to it no longer referencing the same array anymore.
If I attempt to fix this by using Lodash's _.cloneDeep or angular's angular.copy, the browser freezes due to circular references (?).
Service.js
angular.module('exampleapp')
.factory('ClientFeedService', function($filter, $firebase, FIREBASE_URL, FeedItemService) {
return function(clientId) {
var ClientFeedService = this;
var ref = new Firebase(FIREBASE_URL + 'feeds/clients/' + clientId);
var initialDataLoaded = false;
ClientFeedService.feedArray = [];
ClientFeedService.sortItems = function() {
// Sorting logic here
};
/**
* Bind to the initial payload from Firebase
*/
ref.once('value', function() {
// Sort items after initial payload
ClientFeedService.sortItems();
initialDataLoaded = true;
});
/**
* Bind to new items being added to Firebase
*/
ref.on('child_added', function(feedItemSnap) {
console.log('child_added');
ClientFeedService.feedArray.unshift(FeedItemService.find(feedItemSnap.name(), feedItemSnap.val()));
// Sort after new item if initial payload loaded
if (initialDataLoaded) {
ClientFeedService.sortItems();
}
});
ClientFeedService.getFeedItems = function() {
return ClientFeedService.feedArray;
};
return ClientFeedService;
};
});
Controller.js
app.controller('ClientsFeedCtrl', function($scope, $stateParams, ClientFeedService) {
var clientId = $stateParams.clientId;
$scope.clientFeed = new ClientFeedService(clientId).getFeedItems();
});
There are a couple of ways that you can solve this. First, let's look at what is happening.
You are assigning the initial array to $scope.cliendFeed. After this, as data is added, a new Array is being generated and stored in the Service, but you still have a reference to the original Array. So ultimately, what you want to do is find a way to keep $scope.clientFeed in sync with your service.
The simplest solution is probably to use a getter method instead of storing a reference to the array in your scope.
In order to do this, you would have to add something like this:
var service = new ClientFeedService(clientId);
$scope.getClientFeed = function () {
return service.getFeedItems();
};
And make sure your ng-repeat called this function:
<li ng-repeat="item in getClientFeed()">...</li>
Hope that helps!
You can push the new data returned from API to the same array in the controller and then apply the $filter
Here is example
function getData(){
$scope.array.push(returnData);
sortArrayList($scope.orderByField, $scope.reverseSort);
}
function sortArrayList(orderByField, reverseSort){
$scope.array = $filter('orderBy')($scope.array, orderByField, reverseSort);
}

knockout cross view model

var barcodeNum = ko.observable("");
VelocityMeetings.scan = function (params) {
var errorMessage = ko.observable("");
var viewModel = {
errorMessage: errorMessage,
scannumber: ko.observable(""),
errorVisible: ko.computed(function () {
return errorMessage().length != 0;
}),
scanBarcode: function () {
//Capture image with device and process into barcode
capturePhoto();
this.scannumber(barcodeNum());
//this.errorMessage(errMessage);
},
};
return viewModel;
};
I have the barcodeNum variable created outside of the view model, to try and pass data back into the scannumber variable. How do I access a variable defined inside of a view model?
The goal is to use the javascript Worker I have, to update the scannumber which will update my app accordingly, but I can't get it to function properly.
function receiveMessage(e) {
barcodeNum("Test function");
}
var DecodeWorker = new Worker("js/BarcodeScanner.js");
DecodeWorker.onmessage = receiveMessage;
The goal is something along the lines of this
VelocityMeetings.scan.viewModel.scannumber(barcodeNum());
but this isnt working properly
When you find yourself working with separate view models that have to communicate with each other, consider using knockout-postbox. You can make the communication one-way or two-way if you want. In your case, I think a one-way communication will be enough.
var barcodeNum = ko.observable('').publishOn('barcodeNum');
var viewModel = {
scannumber: ko.observable().subscribeTo('barcodeNum'),
// ...
};
I found the issue my self
scannumber: ko.observable(""),
scannumber can be defined as ko.computed with the return value being barcodeNum(), that will make any change made to barcodeNum also made to scannumber()
here is my computed
scannumber: ko.computed(function () { return barcodeNum(); },this),
I have been through this problem a lot of times before.. Then I decided to write an article on same...
You can refer to this article : http://www.wrapcode.com/knockoutjs/communication-between-multiple-view-models-in-knockoutjs-mvvm-the-right-approach/
I have explained how to deal with multiple view models and separate instances of multiple view models in this article..
Hope others will find it helpful :-)

How to set a variable in different controller in AngularJS?

I'd like to do simple notifications in angular. Here is the code I've written.
http://pastebin.com/zYZtntu8
The question is:
Why if I add a new alert in hasAlerts() method it works, but if I add a new alert in NoteController it doesn't. I've tried something with $scope.$watch but it also doesn't work or I've done something wrong.
How can I do that?
Check out this plnkr I made a while back
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview
I show a couple of ways controllers can use data from services, in particular the first two show how to do it without a watch which is generally a more efficient way to go:
// Code goes here
angular.module("myApp", []).service("MyService", function($q) {
var serviceDef = {};
//It's important that you use an object or an array here a string or other
//primitive type can't be updated with angular.copy and changes to those
//primitives can't be watched.
serviceDef.someServiceData = {
label: 'aValue'
};
serviceDef.doSomething = function() {
var deferred = $q.defer();
angular.copy({
label: 'an updated value'
}, serviceDef.someServiceData);
deferred.resolve(serviceDef.someServiceData);
return deferred.promise;
}
return serviceDef;
}).controller("MyCtrl", function($scope, MyService) {
//Using a data object from the service that has it's properties updated async
$scope.sharedData = MyService.someServiceData;
}).controller("MyCtrl2", function($scope, MyService) {
//Same as above just has a function to modify the value as well
$scope.sharedData = MyService.someServiceData;
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl3", function($scope, MyService) {
//Shows using a watch to see if the service data has changed during a digest
//if so updates the local scope
$scope.$watch(function(){ return MyService.someServiceData }, function(newVal){
$scope.sharedData = newVal;
})
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl4", function($scope, MyService) {
//This option relies on the promise returned from the service to update the local
//scope, also since the properties of the object are being updated not the object
//itself this still stays "in sync" with the other controllers and service since
//really they are all referring to the same object.
MyService.doSomething().then(function(newVal) {
$scope.sharedData = newVal;
});
});
The notable thing here I guess is that I use angular.copy to re-use the same object that's created in the service instead of assigning a new object or array to that property. Since it's the same object if you reference that object from your controllers and use it in any data-binding situation (watches or {{}} interpolation in the view) will see the changes to the object.

Updating angular.js service object without extend/copy possible?

I have 2 services and would like to update a variable in the 1st service from the 2nd service.
In a controller, I am setting a scope variable to the getter of the 1st service.
The problem is, the view attached to the controller doesn't update when the service variable changes UNLESS I use angular.extend/copy. It seems like I should just be able to set selectedBuilding below without having to use extend/copy. Am I doing something wrong, or is this how you have to do it?
controller
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
$scope.building = BuildingsService.getSelectedBuilding();
});
service 1
app.factory('BuildingsService', function() {
var buildingsList = [];
var selectedBuilding = {};
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
angular.extend(selectedBuilding, _.find(
buildingsList, {'building_id': buildingId})
);
};
var getSelectedBuilding = function() {
return selectedBuilding;
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
service 2
app.factory('AnotherService', function(BuildingsService) {
...
// something happens, gives me a building id
BuildingsService.setSelectedBuilding(building_id);
...
});
Thanks in advance!
When you execute this code:
$scope.building = BuildingsService.getSelectedBuilding();
$scope.building is copied a reference to the same object in memory as your service's selectedBuilding. When you assign another object to selectedBuilding, the $scope.building still references to the old object. That's why the view is not updated and you have to use angular.copy/extend.
You could try the following solution to avoid this problem if you need to assign new objects to your selectedBuilding:
app.factory('BuildingsService', function() {
var buildingsList = [];
var building = { //create another object to **hang** the reference
selectedBuilding : {}
}
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
//just assign a new object to building.selectedBuilding
};
var getSelectedBuilding = function() {
return building; //return the building instead of selectedBuilding
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
With this solution, you have to update your views to replace $scope.building bindings to $scope.building.selectedBuilding.
In my opinion, I will stick to angular.copy/extend to avoid this unnecessary complexity.
I dont believe you need an extend in your service. You should be able to watch the service directly and respond to the changes:
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
// first function is evaluated on every $digest cycle
$scope.$watch(function(scope){
return BuildingsService.getSelectedBuilding();
// second function is a callback that provides the changes
}, function(newVal, oldVal, scope) {
scope.building = newVal;
}
});
More on $watch: https://code.angularjs.org/1.2.16/docs/api/ng/type/$rootScope.Scope

Categories

Resources