Dependency Injection of functions with Factories (AngularJS) - javascript

I have a few functions that are used in different controllers, and rather than copy and paste it multiple times into the controllers, I want to pull it out and place it in a factory.
However, when I try to call the function in the HTML via Angular {{expressions}} it doesn't work.
Instead, I've made functions inside each of the controllers' $scopes that call the factory's functions so the DOM can read the expressions--but that seems redundant. Is there a way to fix this so I can simply call functions from the factory?
Here was what I originally had tried:
index.html:
<div ng-controller="MyController">
The rating of this item is: {{MakeGraph.getRating(whichItem)}}<br />
The votes of this item is: {{MakeGraph.getVotes(whichItem)}}
</div>
MyController.js:
controllers.controller('MyController', ['$scope', 'MakeGraph', '$routeParams', function($scope, MakeGraph, $routeParams){
$scope.whichItem = $routeParams.itemId;
//no other reference to MakeGraph here
}])
factory.js:
app.factory('MakeGraph', function(){
var service = {};
service.getRating = function(itemNo){
...
return ...;
};
service.getVotes = function(itemNo){
...
return ...;
};
return service;
});
Instead, I can only get it to work when I change MyController to have something like this:
$scope.getRating = function(){
return MakeGraph.getRating(itemNo);
};
Can I fix this so I don't have to call the function inside the $scope? Thanks.

add $scope.MakeGraph = MakeGraph to your controller
controllers.controller('MyController', ['$scope', 'MakeGraph', '$routeParams', function($scope, MakeGraph, $routeParams){
$scope.whichItem = $routeParams.itemId;
$scope.MakeGraph = MakeGraph
}])
then you could access MakeGraph from your view
example:
<a ng-click="MakeGraph.getVotes(12)"> Click me </a>
note that if you return promises in your service, you will probably still need to wrap then in your controller in order to properly handle the .success / .then / .error ... events

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.

AngularJS using service variable with ng-if

I want to be able to determine which directive is displayed based on a variable from a shared service. This is what I have so far.
main.html
<character-select ng-if="stateChangeService.playerState === 'characterSelect'"></character-select>
<fight-display ng-if="stateChangeService.playerState === 'fight'"></fight-display>
service
angular
.module('outerZone')
.service('stateChangeService', stateChangeService);
function stateChangeService() {
var vm = this;
vm.playerState = 'characterSelect';
}
Currently when I load the page, nothing displays. The service is injected into both the character-select directive and the fight-display directive as well as the MainController. Any ideas?
There is no way your view knows anything about your service. You should assign your service to context.
angular.module('outerZone').controller('MainController', MainController)
MainController.$inject = ['stateChangeService'];
function MainController(stateChangeService){
var vm = this;
vm.stateChangeService = stateChangeService;
}
<body ng-controller="MainController as mc">
<character-select ng-if="mc.stateChangeService.playerState === 'characterSelect'"></character-select>
<fight-display ng-if="mc.stateChangeService.playerState === 'fight'"></fight-display>
</body>
View in Angular can access variables in current $scope or parent scopes.
so you have to assign service to current $scope or $rootScope.
(function() {
angular.module('app')
// your service
.service('myService', myServiceFunction);
function myServiceFunction(){
this.data = function(){ return true; };
};
// your controller
.controller('myController', myControllerFunction);
// inject $scope and service
myControllerFunction.$inject=['$scope','myService'];
function myControllerFunction($scope, myService){
//attatch service to current scope
$scope.myService = myService;
};
})();
<div ng-app='app' ng-controller="myController">
{{myService.data()}}
</div>
You can only use those variable on view/HTML which are bounded to $scope or this(while using controllerAs pattern). You should expose your service/factory/constant on view, OR rather better way would write a getter for accessing particular variable from service. Its best practice to expose relevant matter from service. There after you could call that getter from view like getPlayerState()
HTML
<character-select ng-if="getPlayerState() === 'characterSelect'"></character-select>
<fight-display ng-if="getPlayerState() === 'fight'"></fight-display>
Code
app.controller('myCtrl', function(stateChangeService, $scope){
$scope.getPlayerState = function(){
return stateChangeService.playerState;
}
})

How can i add a scope to a http controller in angular.js?

I also want to have acces to a $scope in this controller so I can do methods like ng-click and ng-change in my html. I know have this $http but i cant do anny angular function on it and I can't seem to figure out how I can use a $scope in this controller.
app.controller('songController', ['$http', function($http) {
$songs = this;
$songs.tracks = [];
$http({
url: "http://api.q-music.be/1.2/tracks/plays?limit=20",
dataType: "jsonp",
jsonp: "callback"
})
.success(function(lastSongsList) {
$songs.tracks = lastSongsList.played_tracks;
console.log($songs.tracks);
});
}]);
I want to be able to do methods in this controller like this :
$scope.change = function() {
$scope.counter++;
};
You need to inject your scope dependency like this :
app.controller('songController', ['$http', '$scope', function($http, $scope) {
Change your controller definition to this:
app.controller('songController', ['$http', '$scope', function($http, $scope) {
The same way that you inject $http into your controller, you need to do the same with $scope to allow your controller to use it.
Look up dependency injection and you'll find all kinds of stuff about. It's pretty useful and you'll want to understand how it works if you're going to be working with Angular.
$songs = this;
$songs.tracks = [];
should also be
$scope.songs = this;
$scope.songs.tracks = [];
same for the $http success handler
$songs.tracks = lastSongsList.played_tracks;
should be
$scope.songs.tracks = lastSongsList.played_tracks;
Your change function can remain in the controller
$scope.change = function() {
$scope.counter++;
};
Then in your html, e.g. button input use ng-click="change()"

AngularJS pass Javascript object to controller

I'm trying to pass a Javascript object into my AngularJS controller and having no luck.
I've tried passing it into an init function:
<div ng-controller="GalleryController" ng-init="init(JSOBJ)">
And on my controller side:
$scope.init = function(_JSOBJ)
{
$scope.externalObj = _JSOBJ;
console.log("My Object.attribute : " + _JSOBJ.attribute );
};
Though the above doesn't seem to work.
Alternatively, I've tried pulling the attribute from the AngularJS controller that I am interested in for use in an inline <script> tag:
var JSOBJ.parameter = $('[ng-controller="GalleryController"]').scope().attribute ;
console.log("My Object.parameter: " + JSOBJ.attribute );
Can anyone tell me: what is the best practice regarding this?
I don't have the option to rewrite the plain Javascript object as it is part of a 3rd party library.
Let me know if I need to provide further clarification and thanks in advance for any guidance!
-- JohnDoe
Try setting it as a value:
angular.module('MyApp')
.value('JSOBJ', JSOBJ);
Then inject it into your controller:
angular.module('MyApp')
.controller('GalleryController', ['JSOBJ', function (JSOBJ) { ... }]);
Since your object is a part of third-party library you have to wrap it app in something angular.
Your options are:
if it is jquery pluging init'ed for a DOM node etc you can create a directive
example
myApp.directive('myplugin', function myPlanetDirectiveFactory() {
return {
restrict: 'E',
scope: {},
link: function($scope, $element) { $($element).myplugin() }
}
});
if it is something you need to init you can use factory or service
example
myApp.service(function() {
var obj = window.MyLib()
return {
do: function() { obj.do() }
}
})
if it is plain javascript object you can use value
example
myApp.value('planet', { name : 'Pluto' } );
if it is constant ( number, string , etc) you can use constant
example
myApp.constant('planetName', 'Greasy Giant');
Reference to this doc page: https://docs.angularjs.org/guide/providers
Also I strongly encourage you to read answer to this question: "Thinking in AngularJS" if I have a jQuery background?
If you have JSOBJ accessible via global scope (via window), than you can access it through window directly as in plain JavaScript.
<script>
...
window.JSOBJ = {x:1};
...
</script>
Option 1.
<script>
angular.module('app',[]).controller('ctrl', ['$scope', function($scope) {
$scope.someObject = window.JSOBJ;
}]);
</script>
However it makes the controller code not testable. Therefore $window service may be used
Option 2.
<script>
angular.module('app',[]).controller('ctrl', ['$scope', '$window', function($scope, $window) {
$scope.someObject = $window.JSOBJ;
}]);
</script>
If you want to make some abstraction layer to make your controller agnostic for the source from where you get the object, it is recommended to define service which is responsible for fetching value and then inject it to your controllers, directives, etc.
Option 3.
<script>
angular.module('app',[])
.service('myService', function() {
var JSOBJ = ...; // get JSOBJ from anywhere: localStorage, AJAX, window, etc.
this.getObj = function() {
return JSOBJ;
}
})
.controller('ctrl', ['$scope', 'myService', function($scope, myService) {
$scope.someObject = myService.getObj();
}]);
</script>
Besides of it, for simple values you can define constant or value that may be injected to any controller, service, etc.
Option 4.
<script>
angular.module('app',[]).
value('JSOBJ', JSOBJ).
controller('ctrl', ['$scope', 'JSOBJ', function($scope, JSOBJ) {
$scope.someObject = JSOBJ;
}]);
</script>

Create a factory/service for an object that changes dynamically, and that could be passed on to a controller

I have an object that updates via socket.io.
var my_dynobj;
socket.on('update dynobj', function(dynobj){
my_dynobj = dynobj;
});
I want to have it in my angular app as a factory or a service that could be injected as a dependency.
In there I will want to attach my_object to a $scope so that it can be plugged into HTML {{my_object}} and will update whenever it is itself updated in its own factory definition via socket. socket.on('update object', …
But I can't figure out a way to make that happen.
angular.module('app', [])
.factory('dynobj_factory', [
function(){
var my_dynobj;
socket.on('update dynobj', function(dynobj){
my_dynobj = dynobj;
});
return {what?}
}])
.controller('ctrl', ['$scope', 'dynobj_factory',
function($scope, dynobj) {
$scope.my_dynobj = dynobj.what?
}])
<div>{{my_dynobj}}</div>
For
return {/* what ? */}
You'll need to return something like;
return {dynamicObj: my_dynobj}
And that will do the trick. Any changes to my_dynobj will reflect through all directives, controllers, factories it is injected into.

Categories

Resources