AngularJS using service to pass data without $scope - angularjs

I started using angularjs and the first examples i saw were ones that use this instead of $scope.
for example
app.controller('TabController',function(){
this.tab = 1;
});
I am trying to pass data between controllers. every example i see uses a service that is using $rootScope and then broadcast an event. the controller then uses the $scope.$on to listen to that event.
for example
app.service('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.tab = 1;
sharedService.setTab= function(tab) {
this.tab = tab;
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
app.controller('TabController',['$scope','mySharedService',function($scope,mySharedService){
$scope.tab = 1;
$scope.$on('handleBroadcast', function() {
$scope.tab = sharedService.tab;
});
}]);
my question is how do i do this without using $scope but instead using this.

I took #Dieter Goetelen answer and changed it a bit
the result:
app = angular.module('app', []);
app.service('SharedService',function(){
this.counter = {value:0};
this.add = function (amount){
this.counter.value++;}
});
app.controller('TabController',['SharedService',function(SharedService){
this.sharedService = SharedService;
this.counter = SharedService.counter;
this.add = function(amount){
this.sharedService.add(amount);
}
}]);
here is the complete plunk

Related

View not updating when using $controller service in angular

I have two controllers. The view is tied to the firstCtrl. I'm trying to run the function in the second one so the view is updated. Function runs but view not updated. Any ideas?
<div>{{people.length}}</div>
angular.module('myApp').controller('firstCtrl', function($scope, $http) {
var self = this;
$scope.people = [];
function getData() {
$http.get('/people').then(function(res) {
$scope.people = res.data;
});
}
getData();
$scope.getData = getData;
self.getData = function(){
$scope.getData();
};
return self;
});
angular.module('myApp').controller('secondCtrl', function( $controller, $scope, $http) {
var firstCtrl= $controller('firstCtrl', { $scope: $scope.$new() });
firstCtrl.getData(); //This runs but view is not updated above.
});
I think your code has some problem with the $scope. So instead of pass data directly in the firstCtrl. I pass a callback to getData function and assign data to $scope.people in the callback.
Here is the working Plunker:
> http://plnkr.co/edit/QznxFL

AngularJS routing inside a factory

I want to create a factory for routing purposes. I want to encapsulate this in a factory because I want to exchange info from one page to another. I don't know if this is the best practice. Please tell me if there are other better ways.
This is my controller:
angular.module('app.core')
.controller('mainCtrl', ['ShowService', 'ChangeViews', function(ShowService, ChangeViews){
var vm = this;
vm.namespaces = [];
vm.servers = [];
vm.p4path = '';
vm.gitpckname = '';
vm.server = '';
vm.ns = '';
ShowService.getNamespaces().then(function(response){
var data = angular.fromJson(response.data);
angular.forEach(data.namespaces, function(value){
vm.namespaces.push(value);
vm.initNamespaceSelVal = vm.namespaces[0];
vm.ns = vm.namespaces[0];
});
});
ShowService.getServers().then(function(response){
var data = angular.fromJson(response.data);
angular.forEach(data.servers, function(value){
vm.servers.push(value);
vm.initServerSelVal = vm.servers[0];
vm.server = vm.servers[0];
});
});
vm.doClick = function(value){
if(value){
var body = {};
body['server'] = vm.server;
body['p4path'] = vm.p4path;
body['packagename'] = vm.gitpckname;
body['namespace'] = vm.ns;
ShowService.getBraches(body).then(function(response){
console.log(response);
//$location.path('/hidden');
//ChangeViews.changeView('/hidden');
});
};
};
}]);
In the above code, I injected two custom made factories into controller. The "ShowService" works properly but the "ChangeViews" returns some errors.
Using $location service inside the controller(the commented line) works.
The factory code is:
angular
.module('app.services')
.constant('BASE_URL', 'http://localhost:8066')
.factory('ShowService', dataService)
.factory('ChangeViews', changeViews);
function dataService($http, BASE_URL){.....}
function changeViews($location, view){
var data = {
'changeView': changeView,
};
function changeView(view){
return $location.path(view);
};
return data;
}
The path to the html template is routed.
The error I'm receiving when injecting the ChangeViews factory is:
"Error: [$injector:unpr] http://errors.angularjs.org/1.5.5/$injector/unpr?p0=viewProvider%20%3C-%20view%20%3C-%20ChangeViews
What I'm missing?
Thank you
The problem is that you don't have injectable service named view, hence the unknown provider error.
Remove view from changeViews parameters list:
function changeViews($location) {
var data = {
changeView: changeView
};
function changeView(view) {
return $location.path(view);
};
return data;
}

self-updating angular factory with socket.io

I'm trying to use an angular factory to provide a shared piece of state to my controllers. I also want this factory to automatically update its state by listening to a websocket. The goal is that I can write my data-fetching logic in one place, and then have the UI components update themselves automatically.
The problem I'm running into is that, while the factory is updating itself, the controllers never see the updates. Here's an example of what I'm trying to do:
app.factory('Socket', function () {
var Socket = io.connect();
return Socket;
});
app.factory('UpdateCounter', ['Socket', function (Socket) {
var counter = 0;
Socket.on('update', function () {
counter += 1;
});
return counter;
}]);
app.controller('MyController', ['$scope','UpdateCounter', function ($scope,UpdateCounter) {
$scope.counter = UpdateCounter;
...
}]);
MyController will see UpdateCounter = 0 and never see the changes.
I'm not surprised that this doesn't work, but I don't know why it doesn't work; nor do I know what I should be doing to get the behaviour I need.
You need to tell Angular that an update has occurred.
app.factory('UpdateCounter', ['Socket', '$rootScope', function (Socket, $rootScope) {
var counter = 0;
Socket.on('update', function () {
counter += 1;
$rootScope.$apply();
});
return counter;
}]);
There are a few ways you can do this. The first and simple is adding a watch so that the controller knows to watch the value for change.
app.controller('MyController', ['$scope','UpdateCounter', function ($scope,UpdateCounter) {
$scope.$watch(function(){
return UpdateCounter;
}, function(newVal){
$scope.counter = newVal;
})
}]);
The second and probably better way is to return an object that can be referenced
app.factory('UpdateCounter', ['Socket', function (Socket) {
var counter = { count: 0 };
Socket.on('update', function () {
counter.count += 1;
});
return counter;
}]);
app.controller('MyController', ['$scope','UpdateCounter', function ($scope,UpdateCounter) {
$scope.counter = UpdateCounter;
}]);
In your view, you can reference counter.count to get the count and it will be automatically updated.

How do I test a controller that watches for changes on an injected service?

I'm using a service to share data between controllers. If a value on the service changes, I want to update some data binding on my controllers. To do this, I'm using $scope.$watchCollection (because the value I'm watching is a simple array). I'm having trouble trying to figure out how to test this in Jasmine + Karma.
Here is a simple Controller + Service setup similar to what I'm doing in my app (but very simplified):
var app = angular.module('myApp', []);
// A Controller that depends on 'someService'
app.controller('MainCtrl', function($scope, someService) {
$scope.hasStuff = false;
// Watch someService.someValues for changes and do stuff.
$scope.$watchCollection(function(){
return someService.someValues;
}, function (){
if(someService.someValues.length > 0){
$scope.hasStuff = false;
} else {
$scope.hasStuff = true;
}
});
});
// A simple service potentially used in many controllers
app.factory('someService', function ($timeout, $q){
return {
someValues: []
};
});
And here is a test case that I've attempted (but does not work):
describe('Testing a controller and service', function() {
var $scope, ctrl;
var mockSomeService = {
someValues : []
};
beforeEach(function (){
module('myApp');
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope,
someService: mockSomeService
});
});
});
it('should update hasStuff when someService.someValues is changed', function (){
expect($scope.hasStuff).toEqual(false);
// Add an item to someService.someValues
someService.someValues.push(1);
//$apply the change to trigger the $watch.
$scope.$apply();
//assert
expect($scope.hasStuff).toEqual(true);
});
});
I guess my question is twofold:
How do I properly mock the service that is used in the controller?
How do I then test that the $watchCollection function is working properly?
Here is a plunkr for the above code. http://plnkr.co/edit/C1O2iO
Your test (or your code ) is not correct .
http://plnkr.co/edit/uhSdk6hvcHI2cWKBgj1y?p=preview
mockSomeService.someValues.push(1); // instead of someService.someValues.push(1);
and
if(someService.someValues.length > 0){
$scope.hasStuff = true;
} else {
$scope.hasStuff = false;
}
or your expectation makes no sense
I strongly encourage you to lint your javascript (jslint/eslint/jshint) to spot stupid errors like the first one.Or you'll have a painfull experience in writing javascript. jslint would have detected that the variable you were using didnt exist in the scope.

How to watch a variable defined in a service in angularjs

I am trying to watch changes on an json array defined in an angularj service, but when the change occures, the $watch function is not firing. My controller and service code goes as follows (plunker demo):
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,cityService) {
//$scope.cities = [];
$scope.service = cityService;
cityService.initCities();
$scope.$watch('service.getCity()', function(newVal) {
$scope.cities = newVal;
console.log(newVal)
});
});
app.service('cityService', function($http) {
this.cities = [];
this.initCities = function() {
$http.get('data.js').success(function(data) {
this.cities = data;
});
};
this.getCity = function() {
return this.cities;
};
});
This is because the callback from get set this to window object. Keep the reference of the service in self.
See this plunker
http://plnkr.co/edit/CrgTWRBsg5wi7WOSZiRS?p=preview
I changed several things to make it work:
http://plnkr.co/edit/PDMaEvmx7hG1fKvAmR7R?p=preview
Function watch instead of variable
In the service, removed the keyword this because this has not the same context inside functions.
Return functions in service
Seems ok

Resources