Run same piece of code when injecting an angular service - javascript

I want to run the same line of code to each function a specific service is injected. For instance:
app.service('sameInitService', function ($rootScope) {
this.init = function (scope) {
scope.foo = $rootScope.foo;
}
});
app.controller('oneController', function ($scope, sameInitService) {
sameInitService.init($scope); // <- This line of code
});
app.controller('twoController', function ($scope, sameInitService) {
sameInitService.init($scope); // <- Is the same
});
Is there an angular-ish way to avoid having to write the same line of code when this service is injected?

It can be done by decorating the $controller service but in this case all your controllers will be initialized this way.
angular.module('App', [])
.config(['$provide', function($provide) {
$provide.decorator('$controller', [
'$delegate', 'sameInitService',
function controllerDecorator($delegate, sameInitService) {
return function(constructor, locals) {
sameInitService.init(locals.$scope);
return $delegate(constructor, locals, true);
}
}
]);
}])
See jsfiddle here.

I want to extend on #janusz 's answer and present the code that I actually ended up using, which takes care of the "apply behavior only to controllers with injected dependency":
angular.module('App').decorator('$controller', function ($delegate, sameInitService) {
return function (constructor, locals) {
var controller = $delegate.apply(null, arguments);
return angular.extend(function () {
var controllerInstance = controller()
var DIList = controllerInstance .__proto__.constructor.$inject;
console.log(DIList);
if (DIList.indexOf('sameInitService') !== -1) {
sameInitService.init(locals.$scope);
}
return controllerInstance;
}, controller);
};
});
Here is a modified fiddle

Related

Angular Controller Testing: check dependency injection is done right

Sometimes, often when refactoring a Controller - or also a Factory -, I end up passing my Controller tests but when my app is up and running it keeps crashing at some point because I forgot to add/update dependency injection.
With this I mean, let's say I have the following Controller where I used to have an oldDependency but due to refactoring I'm using a newDependency instead. I update MyCtrl.$inject with the new changes but I forget to update the dependencies passed to the MyCtrl function:
angular
.module('my-module')
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency' // this has been only updated here
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
// My Controller's code
// etc...
function someFunc(x) {
// here I use newDependency
return newDependency.doSomething(x);
}
}
So what happens then? I go and update MyCtrl tests, where I actually remember to update the dependency object passed to $controller():
// MyCtrl testing code
var MyCtrl = $controller('VolunteerResignCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
Because of this, all MyCtrl tests keep passing, so I think that nothing broke. But it actually did.
Could anyone tell me if this can be tested somehow and avoid my app failing for this reason in the future?
This seems like an issue with JS scoping rather than Angulars dependency injection. Try wrapping your test cases and the functions that you use for them in an IIFE as it they're not, JS will go up the scope until it can find a variable called newDependency which is probably why your tests are running but it's crashing in your app.
As you can see from the examples below
Without an IIFE
var myApp = angular.module('myApp', []);
myApp.factory('firstDependency', function() {
return {}
}());
myApp.factory('secondDependency', function() {
return {}
}());
myApp.factory('newDependency', function() {
return {}
}())
myApp.controller('MyCtrl', MyCtrl);
myApp.controller('TestCtrl', TestCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency'
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
vm.someFunc = function(x) {
// Will work fine as newDependency is defined in a higher scope :c
return newDependency.doSomething(x);
}
}
var firstDependency = function() {
return {}
}();
var secondDependency = function() {
return {}
}();
var newDependency = function() {
return {
doSomething: function(x) {
return x;
}
}
}()
function TestCtrl($scope, $controller) {
var test = $controller('MyCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
$scope.someFunc = test.someFunc("you're inside the new dependency");
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="TestCtrl">
{{someFunc}}!
</div>
</div>
With an IIFE
var myApp = angular.module('myApp', []);
myApp.factory('firstDependency', function() {
return {}
}());
myApp.factory('secondDependency', function() {
return {}
}());
myApp.factory('newDependency', function() {
return {}
}())
myApp.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency'
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
vm.someFunc = function(x) {
//Will throw an error, but this time it's a good error because that's what you want!
return newDependency.doSomething(x);
}
}
(function(myApp) {
var firstDependency = function() {
return {}
}();
var secondDependency = function() {
return {}
}();
var newDependency = function() {
return {
doSomething: function(x) {
return x;
}
}
}()
myApp.controller('TestCtrl', TestCtrl);
function TestCtrl($scope, $controller) {
var test = $controller('MyCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
$scope.someFunc = test.someFunc("you're inside the new dependency");
}
})(myApp)
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="TestCtrl">
{{someFunc}}!
</div>
</div>
Name of variables are not important from js-point of view.
Following functions are SAME:
function f(SomeService) {
SomeService.test();
}
function f(NewService) {
NewService.test();
}
function f(a) {
a.test();
}
Thats the reason why you need $inject - cause you pass string their.
After you change name in $inject - you may change it in function, but you also may not change it. Doesnt matter at all.

AngularJS call a function from another controller

I want to call a function or change the value of variable/s which is there inside another controller. I looked online for the solution and understood that I have to create a service and use it inside both the controller, however I am not able to understand that how service will have access to $scope.home_main and $scope.home_main variables as they are in different scope.
JS
app.controller('Controller1', function ($scope, $window) {
$scope.toggle = function() {
$scope.home_main = !$scope.home_main;
$scope.full_page_place_holder = !$scope.full_page_place_holder;
};
});
app.controller('Controller2', function ($scope, $window) {
$scope.onTabSelect=function(){
// here I want to call toggle function which is inside another controller.
};
});
Updated HTML
<div ng-controller="Controller1">
<div ng-hide="home_main"></div>
</div>
<div ng-controller="Controller2">
<div ng-hide="full_page_place_holder"></div>
</div>
Looked here: SO-Q1, SO-Q2. Thanks.
you can create a service as follows,
angular.module('someApp').service('shareDataService', function() {
var popup;
var setDetails = function(param) {
popup = param;
};
var getDetails = function() {
return popup;
};
return {
setDetails: setDetails,
getDetails: getDetails,
};
});
This service will not have access to the $scope variables of the two controllers, instead you can call getDetails and setDetails to get and set the variable in the service.
suppose you want to send the value of 'home_main' from controller1 to controller2, in controller1, you call the service function setDetails
app.controller('Controller1', function ($scope, $window, shareDataService) {
$scope.toggle = function() {
$scope.home_main = !$scope.home_main;
$scope.full_page_place_holder = !$scope.full_page_place_holder;
shareDataService.setDetails($scope.home_main);
};
});
and in controller2, you get the value by calling the service
app.controller('Controller2', function ($scope, $window) {
var home_main_value = shareDataService.getDetails();
});
You can do a similar thing for functions also.....
Hope it helps
You misunderstood the concept service will have a single variable that is going to be shared by two controllers:-
$scope is local for controller and cannot accessed by another controller:-
FOR Example:-
myApp.factory('Data', function () {
var data = {
home_main : ''
};
return {
gethome_main : function () {
return data.home_main ;
},
sethome_main : function (home_main ) {
data.home_main = home_main ;
}
};
myApp.controller('FirstCtrl', function ($scope, Data) {
$scope.home_main= '';
$scope.$watch('home_main', function (newValue, oldValue) {
if (newValue !== oldValue) Data.sethome_main(newValue);
});
});
myApp.controller('SecondCtrl', function ($scope, Data) {
$scope.$watch(function () { return Data.gethome_main(); }, function (newValue, oldValue) {
if (newValue !== oldValue) $scope.home_main= newValue;
});
});

How can i access scope in angular service

I have two controllers
app.controller('TestCtrl1', ['$scope', function ($scope) {
$scope.save = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
app.controller('TestCtrl2', ['$scope', function ($scope) {
$scope.var1 = 'test1'
$scope.save = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
Then i have two services
.service('Service1', function($q) {
return {
save: function(obj) {
}
}
})
.service('Service2', function($q) {
return {
save: function(obj) {
}
}
})
For my 60% of stuff i just call save on ctrl1 which then called service save method
Now There are cases where before saving i need to do some stuff like chnaging some object parameters different than genral case there i check e,g
if(model == 'User'){
//Here i do this (sample of code)
var service = $injector.get('Service2');
service.save()
Now my problem is in Service 2 i need access to var1. How can i do that
Use the service(s) itself to share the variable as part of the service object as well as methods of each service
.service('Service2', function($q) {
var self = this;
this.var1 = 'test1';
this.save = function(obj) {
}
});
app.controller('TestCtrl2', ['$scope','Service1','Service2', function ($scope, Service1, Service2, ) {
// bind scope variable to service property
$scope.var1 = Service2.var1;
// add a service method to scope
$scope.save = Service1.save;
// now call that service method
$scope.save( $scope.var1 );
}]);
You can also inject a service into another service if needed
injecting services into other services (one possible method) :
html:
<div id="div1" ng-app="myApp" ng-controller="MyCtrl">
<!--to confirm that the services are working-->
<p>service three: {{serviceThree}}</p>
</div>
js:
angular.module('myApp',[])
.service('s1', function() {
this.value = 3;
})
.service('s2', function() {
this.value = 10;
})
.service('s3', function(s1,s2) { //other services as dependencies
this.value = s1.value+s2.value; //13
})
.controller('MyCtrl', function($scope, $injector) { //$injector is a dependency
$scope.serviceThree = $injector.get('s3').value; //using the injector
});
here's the fiddle: https://jsfiddle.net/ueo9ck8r/

Injecting filter in directive for angular unit test

this might be simple but I cant find any solution anywhere. Right now i have a directive which uses a filter function. Everything works fine but I am trying to create a unit test for it and it is giving me
Error: [$injector:unpr] Unknown provider: inArrayFilterProvider <- inArrayFilter
This is my directive
var directiveApp = angular.module('myApp.directives',[]);
directiveApp.directive('navbar', function($filter) {
return {
templateUrl: 'partials/navbar.html',
controller: function($scope, $location) {
$scope.checkLocation = function(){
return $location.path();
};
$scope.isActive = function (viewLocation) {
var locations = viewLocation.split(',');
//return viewLocation === $location.path();
var path = '/' + $location.path().split('/')[1];
//return $filter('inArray')(locations, $location.path());
return $filter('inArray')(locations, path);
};
}
};
});
And this is my unit test
'use strict';
describe('myApp.directives module', function() {
beforeEach(module('myApp.directives'));
describe('navbar directive', function() {
var $scope,controller,template;
beforeEach(inject(function($rootScope,$compile,$location,$filter,$templateCache){
$scope = $rootScope.$new();
$templateCache.put('partials/navbar.html','I am here');
spyOn($location,'path').andReturn("/festivals");
var element = angular.element('<div data-navbar=""></div>');
template = $compile(element)($scope);
controller = element.controller;
$scope.$digest();
}));
it('Check Festival nav', function()
{
expect($scope.isActive('/festivals')).toBeTruthy();
});
});
});
I think i will have to spyOn filter when it is called with inArray and return a mock value but not really sure how to do it.
Thank you
Fixed.
I had to include my main module which had inArray function
describe('myApp.directives module', function() {
var $filter;
beforeEach(module('myApp'),function(_$filter_){
$filter = _$filter_;
});
.../*Rest as Before*/
...
...});

Angular: Update data every few seconds

Maybe this question has already been asked, but I searched and tried most of my afternoon without any success so I really hope somebody can help me with this.
I want to be able to update my $http.get() - my data - that I have set in a factory service, every few seconds.
I added some comment to my code and also left some old stuff for you guys to see what I have tried. (the old stuff is also commented out)
My code:
ovwid.factory('recentClients', [
'$http',
'$rootScope',
function ($http, $rootScope) {
var apiURL = '/plugins/data/get_client.php';
var promise;
var recentClients =
{
async: function()
{
if ( !promise )
{
// $http returns a promise, which has a 'then' function, which also returns a promise
promise =
$http.get(apiURL)
.then(function (response) {
// The then function here is an opportunity to modify the response
// The return value gets picked up by the then in the controller.
return response.data;
});
}
// Return a promise to the controller
return promise;
}
}
return recentClients;
}]);
ovwid.controller(‘client’Ctrl, [
'$scope',
'recentClients',
'$interval',
function ($scope, recentClients, $interval) {
$scope.loading = true;
function reloadData() {
// a call to the async method
recentClients().async().then(function(data) {
// console.log(data);
$scope.loading = false;
$scope.client = data;
});
}
// Initizialize function
reloadData();
// Start Interval
var timerData =
$interval(function () {
reloadData();
}, 1000);
// function myIntervalFunction() {
// var cancelRefresh = $timeout(function myFunction() {
// reloadData();
// console.log('data refres')
// cancelRefresh = $timeout(myFunction, 5000);
// },5000);
// };
// myIntervalFunction();
// $scope.$on('$destroy', function(e) {
// $timeout.cancel(cancelRefresh);
// });
}]); // [/controller]
I see several issues.
First:
if ( !promise ) is only going to return true the first time. You are assigning it to the $http call.
Secondly:
You never access the async method in your factory.
You either need to return that from factory return recentClients.async or call it from scope recentClients.async().then(..
may be it will help
function reloadData() {
// a call to the async method
$scope.loading = true;
recentClients().then(function(data) {
// console.log(data);
$scope.loading = false;
$scope.client = data;
});
}
// Start Interval
var timerData =
$interval(function () {
if(!$scope.loading){
reloadData();
}
}, 1000);
A few things :)
recentClients().then(function(data)... will not work, in your current code it should be: recentClients.async().then(function(data)
(same remark would apply to ` and ’ qoutes that can get really tricky.
This is the syntax I use for designing services:
ovwid.factory('recentClients', ['$http', '$rootScope', function ($http, $rootScope) {
var apiURL = 'aaa.api';
var recentClients = function() {
return $http.get(apiURL)
}
return {
recentClients : recentClients
};
}]);
Full example:
(just create aaa.api file with some dummy data, fire up a server and you'll see that data is changing)
<!DOCTYPE html>
<html>
<head>
<title>Sorting stuff</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
<script>
var ovwid = angular.module("ovwid", []);
ovwid.factory('recentClients', ['$http', '$rootScope', function ($http, $rootScope) {
var apiURL = 'aaa.api';
var recentClients = function() {
return $http.get(apiURL)
}
return {
recentClients : recentClients
};
}]);
ovwid.controller('clientCtrl', [
'$scope',
'recentClients',
'$interval',
function ($scope, recentClients, $interval) {
$scope.loading = true;
function reloadData() {
// a call to the async method
recentClients.recentClients().then(function(response) {
// console.log(data);
$scope.loading = false;
$scope.client = response.data;
});
}
// Initizialize function
reloadData();
// Start Interval
var timerData =
$interval(function () {
reloadData();
}, 1000);
}]);
</script>
</head>
<body ng-app="ovwid" ng-controller="clientCtrl">
{{ client }}
</body>
</html>
You can set up a service to perform periodic server calls for you. I had found this code somewhere awhile back and refined it a bit. I wish I could remember where I got it.
angular.module('my.services').factory('timeSrv',['$timeout',function($timeout){
//-- Variables --//
var _intervals = {}, _intervalUID = 1;
//-- Methods --//
return {
setInterval : function(op,interval,$scope){
var _intervalID = _intervalUID++;
_intervals[_intervalID] = $timeout(function intervalOperation(){
op($scope || undefined);
_intervals[_intervalID] = $timeout(intervalOperation,interval);
},interval);
return _intervalID;
}, // end setInterval
clearInterval : function(id){
return $timeout.cancel(_intervals[id]);
} // end clearInterval
}; // end return
}]); // end timeSrv
And then in your controller you'd make a call like so:
$scope.getSomethingID = timeSrv.setInterval(function($scope){
[... Do stuff here - Access another service ...]
},10000,$scope);
This will execute the passed function every 10 seconds with the scope of the controller. You can cancel it at anytime by:
timeSrv.clearInterval($scope.getSomethingID);

Categories