Force angular scope to update from service - javascript

I've create an angular controller and service. In my service I have an array that starts off blank when the app loads, but is filled later on. In my controller I have a property on $scope that points to that array. When the array in the service is updated I assumed the $scope property would also be updated and the DOM would update accordingly. Here is the sample code.
app.controller("myCtlr", ["$scope", "$service", function($scope, $service){
$scope.friends = $service.friends
}]);
app.factory("$service", function($http){
var friends = {};
friends = {
get: function(){
$http.get("/someurl").success(function(data){
// data is the array of friends
friends = data;
});
}
};
});
I've tried using angular.extend but there aren't many good example of it online so I don't fully understand it yet. Any help is much appreciated. Thanks!

Angular $http automatically kicks off a digest cycle, you should also return friends from your service and also use .then to continue the promise pattern:
app.factory("$service", function($http){
var friends = {};
friends = {
get: function(){
$http.get("/someurl").then(function(data){
return data.data
});
}
return friends;
});
And in your controller:
$service.friends.get().then(function(data) {
console.log(data);
});

Related

testing angularjs 1 factory method is automatically called inside a controller with jasmine

I'm using ruby on rails with angularjs one, and testing it with teaspoon-jasmine for the first time and am running into issues. Basically, I have a controller that creates an empty array and upon load calls a factory method to populate that array. The Factory makes an http request and returns the data. Right now, i'm trying to test the controller, and i'm trying to test that 1) the factory method is called upon loading the controller, and 2) that the controller correctly assigns the returned data through it's callback. For a while I was having trouble getting a mocked factory to pass a test, but once I did, I realized I wasn't actually testing my controller anymore, but the code below passes. Any tips on how I can still get it to pass with mock, promises/callbacks, but accurately test my controller functionality. Or should I even test the this at all in my controller since it calls a factory method and just gives it a callback? My 3 files are below. Can anyone help here? It would be greatly appreciated
mainController.js
'use strict';
myApp.controller('mainController', [ 'mainFactory', '$scope', '$resource', function(factory, scope, resource){
//hits the /games server route upon page load via the factory to grab the list of video games
scope.games = [];
factory.populateTable(function(data){
scope.games = data;
});
}]);
mainFactory.js
'use strict';
myApp.factory('mainFactory', ['$http', '$routeParams', '$location', function(http, routeParams, location) {
var factory = {};
factory.populateTable = function(callback) {
http.get('/games')
.then(function(response){
callback(response.data);
})
};
return factory;
}]);
And finally my mainController_spec.js file
'use strict';
describe("mainController", function() {
var scope,
ctrl,
deferred,
mainFactoryMock;
var gamesArray = [
{name: 'Mario World', manufacturer: 'Nintendo'},
{name: 'Sonic', manufacturer: 'Sega'}
];
var ngInject = angular.mock.inject;
var ngModule = angular.mock.module;
var setupController = function() {
ngInject( function($rootScope, $controller, $q) {
deferred = $q.defer();
deferred.resolve(gamesArray);
mainFactoryMock = {
populateTable: function() {}
};
spyOn(mainFactoryMock, 'populateTable').and.returnValue(deferred.promise);
scope = $rootScope.$new();
ctrl = $controller('mainController', {
mainFactory: mainFactoryMock,
$scope: scope
});
})
}
beforeEach(ngModule("angularApp"));
beforeEach(function(){
setupController();
});
it('should start with an empty games array and populate the array upon load via a factory method', function(){
expect(scope.games).toEqual([])
mainFactoryMock.populateTable();
expect(mainFactoryMock.populateTable).toHaveBeenCalled();
mainFactoryMock.populateTable().then(function(d) {
scope.games = d;
});
scope.$apply(); // resolve promise
expect(scope.games).toEqual(gamesArray)
})
});
Your code looks "non-standard" e.g still using scope.
If you are just starting with angular I hardly recommend you to read and follow this:
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
Angular controllers cannot be tested, extract the logic into factories/services and test from there.

Factory is returning a function instead of the return value of the function in AngularJS

So, I had an application pretty much figured out until I realized that I am making way to many calls to database api and as a result the app is lagging. I am trying to set up a way to keep the current scope accurate when adding and removing items from the $scope object instead of having to request the entire list from the database every time. So I figured from all I've read that a factory is the way to go. That way I can pull the data from the database once, then after successfully adding or deleting entries through the api I can just manipulate the current scope instead of requesting the entire list again. My question is (with an example), How do I fetch a list of users from the api and add that to the scope. Then when I add or delete users from the list I can just remove them from the scope without fetching the list all over again from the api?
Here is my factory:
app.factory("UsersFactory",['Data',function(Data){
var users;
function init(){
Data.get('users')
.then(function (results) {
users = results;
});
}
init();
function getUsers(){
return users;
}
return {
getUsers:getUsers
}
}]);
And after injecting the factory in the controller:
console.log(UsersFactory.getUsers);
This returns the function but not the return value of the function.
I have also tried:
console.log(UsersFactory.getUsers());
Which returns undefined;
I would actually like to do all this in the resolve for the state (ui-router) but in the controller directly is ok too i guess.
Any help would be ap;appreciated and feel free to ask me to clarify anything. I'm not very good at clearly asking questions.
The correct way to do this is to return a promise from your factory:
app.factory("UsersFactory", ['Data', function(Data) {
var factory = {
getUsers: getUsers
};
return factory;
function getUsers() {
return Data.get('users');
}
}]);
Then, in your controller:
app.controller("UsersController", ['$scope', 'UsersFactory', function($scope, UsersFactory) {
function getResponse(response) {
$scope.users = response.data;
}
function getError(response) {
console.log(response);
}
UsersFactory.getUsers()
.then(getResponse);
.catch(getError);
}]);
Factory:
app.factory("UsersFactory",function(Data) {
UpDateUserinfo:function(){
var inf;
inf=Data.get('users');
return inf;
}
})
Controller:
app.controller("UsersController", function($scope, UsersFactory){
UsersFactory.UpDateUserinfo().$promise.then(function(data){
$scope.users=data
})
})

Which practice is best for state and model saving in angular.js

Over the time in using angular i have switched from one practive of saving state and data to another here are the two.
Please suggest me which one to continue saving and why?
angular.module('app')
.controller('personController', ['$scope', '$log',personsService, cntrl])
.factory('personsService', ['$http', '$q', service]);
//First
function cntrl($scope, $log, service){
$scope.state = {creating:false; updating:false;}
$scope.createPerson = createPerson;
$scope.person = null;
function createPerson(fn, ln, age){
$scope.state.creating = true;
service.createPerson({fn:fn, ln:ln, age:age})
.success(function(r){/*something*/})
.error(function(){/*something*/})
.finally(function(){
$scope.state.creating = true;
});
}
}
function service($http, $q){
var r = {
createPerson : createPerson
};
return r;
function createPerson(o){
return $http.post('/person/create', o);
}
}
//Second
function cntrl($scope, $log, service){
$scope.person = service.person;
$scope.state = service.state;
$scope.service = service;
}
function service($http, $q){
var state = {creating:false, updating:false};
var person = {};
var r = {
state : state,
person : person,
createPerson : createPerson
}
return r;
function createPerson(o){
state.creating = true;
var def = $q.defer();
$http.post('/person/create', o)
.success(function(dbPerson){
def.resolve(dbPerson);
angular.extend(dbPerson, person);
})
.error(function(e){
def.rreject(e);
angular.extend({}, person); //or any other logic
})
.finally(function(){
state.creating = false;
});
return def.promise;
}
}
As you can see in the
1. first example
The ajax state is maintained in the controller. and service only exposes functions that are required for that ajax state. The Person object is also maintained inside the controller so i dont have to maintain the reference to the same object as the object is directly attached to the cart. I can simpply do $scope.person = {};
2. Second Example
The ajax state is maintained by the service which now exposes the person object as well as the functions used to manipulate the object. Now i need to maintain the reference to object bby using functions sucn as angular.extend and angular.copy. Now the ajax state exposed by a controller is divided over multiple services. Advantages are modularity of the code and almost complete logic less controller.
I would suggest you to Go for the second Method.
An AJAX Call for data should always be separate from Controller . Controllers require data that should be shown in the UI.
Tomorrow you might require the same data from the same API to be used in a different way . In this way you are using the same service to get the data and modify according to the need . More over the Controller should not be worried about where the data is coming from. i.e it could come from An AJAX call , a web socket, local storage etc.
So in your case angular.extend makes more sense as you are working on a copy of the data and not the original data which might be used by some other controller over time. Re-usable and clean.
I would say the correct approach would be the mix of both the approaches.
#Parv mentioned the state object is being used at the UI, it should rest in teh controller. But the service function in approach 1 is at fault. The logic there should use the $q defer object and be something like what you are doing in your approach 2..
angular.module('app')
.controller('personController', ['$scope', '$log',personsService, cntrl])
.factory('personsService', ['$http', '$q', service]);
//First
function cntrl($scope, $log, service){
$scope.state = {creating:false; updating:false;}
$scope.createPerson = createPerson;
$scope.person = null;
function createPerson(fn, ln, age){
$scope.state.creating = true;
service.createPerson({fn:fn, ln:ln, age:age})
.then(function(r){/*assign $scope.person = r;*/}),function()
{/*something*/}).finally(function() {
$scope.state.creating = false;
});
}
}
function service($http, $q){
var r = {
createPerson : createPerson,
person: {}
};
return r;
function createPerson(o){
var def = $q.defer();
$http.post('/person/create', o)
.success(function(dbPerson){
def.resolve(dbPerson);
angular.extend(dbPerson, person);
})
.error(function(e){
def.reject(e);
angular.extend({}, person); //or any other logic
});
return def.promise;
}
}
This way we have segregated the post logic from the things that matter at the UI.

Angular - Update scope when change in Factory data

I'm using a factory to poll a particular web service. This web service is used to update data any the factory. I initiate this factory in the main controller, and populate a scope variable through a factory function. The variable initializes correctly, and I get the right data on the screen, but I'm struggling on getting the data to bind automatically.
Edit for additional notes:
The reason this code is in a Factory is that I plan on using the factory data across multiple views.
Here is what I have so far:
App.factory('metrics', function($http, $q, $timeout){
var service;
var users = [{laps:[]}];
var updateMetrics = function(){
//updates the users array in the factory
};
service.load = function (){
var deferred = $q.defer();
$http.get('http://www.example.com/api/random').success(function(data) {
var temp_array = data.split("\n");
updateMetrics(0, temp_array);
deferred.resolve({status: 'good'});
$timeout(service.load,3000);
});
return deferred.promise;
};
service.lastLapInfo = function(){
var lastlap = [];
for (var i=0; i<users.length;i++)
{
var lap = users[i].laps[users[i].laps.length-1];
lastlap.push(lap);
}
return lastlap;
};
return service;
});
App.controller('mainController', function($scope, metrics) {
metrics.load().then(function(response){
$scope.$watch(function () { return metrics.lastLapInfo() }, function (newVal, oldVal) {
if (newVal !=oldVal)
{
$scope.users=metrics.lastLapInfo();
}
});
});
});
When I try the above, I get an error saying '10 $digest() iterations reached'. I don't see how that's possible, asI'm not calling the watch function multiple times.
Any suggestions (or other means to accomplish what I'm trying to do?)
If you're not 100% set on using $watch, a pattern that I prefer is to bind new instances of (not references to) modules to the current scope and keep the controllers strictly as components used for wiring together the project's views and models. This excludes the use of $watch, even for coordinating data across modules. I prefer to use $rootScope's $broadcast, $emit and $on methods within modules/factories (after passing in $rootScope as a service, which may or may not work for all situations, though it has for all that I've come across) rather than the comparatively sluggish $watch or $watchCollection methods. Using the latter makes me feel dirty inside... But I digress.
Would something like the following work in your situation?
App.factory('metrics', function($http, $q, $timeout){
var service;
service.users = [{laps:[]}];
service.updateMetrics = function(){
// updates the users array in the current instance of `metrics`
// ex:
// this.users = updatedMetrics;
// don't do:
// service.users = updatedMetrics;
};
service.load = function (){
var deferred = $q.defer();
$http.get('http://www.example.com/api/random').success(function(data) {
var temp_array = data.split("\n");
this.updateMetrics(0, temp_array);
deferred.resolve({status: 'good'});
$timeout(service.load,3000);
}.bind(this));
return deferred.promise;
};
service.lastLapInfo = function(){
var lastlap = [];
for (var i=0; i<this.users.length;i++)
{
var lap = this.users[i].laps[this.users[i].laps.length-1];
lastlap.push(lap);
}
return lastlap;
};
return service;
});
App.controller('mainController', function($scope, metrics) {
$scope.metrics = angular.copy(metrics);
$scope.metrics.load();
});
By setting $scope.metrics = angular.copy(metrics), we are creating a new instance of metrics, rather than setting $scope.metrics as a reference to metrics ($scope.metrics = metrics). This has several benefits, including that you can now use multiple instances of the same module in the controller (ie $scope.foo = angular.copy(foo); $scope.bar = angular.copy(foo); since the objects bound to $scope are completely new objects, rather than references to the same module.
Another benefit is that the instance of metrics attached to $scope can be used to call methods on metrics which can allow any changes to metrics to automatically be applied to your controller's views. I frequently faced odd issues when trying to get this to work when not using angular.copy or $.extend, seemingly because changes to the referenced module attached to $scope were not always being registered.

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.

Categories

Resources