Angular – How to Make Resource Global? - javascript

I currently am loading audio track resources to the "Works" page of my website successfully. Here is how my "Works" controller is achieving this:
'use strict';
angular.module('appName')
.controller('WorksCtrl', function ($scope, $http, socket) {
$scope.works = [];
$http.get('/api/works').success(function(works) {
$scope.works = works;
socket.syncUpdates('work', $scope.works);
});
});
However, now I need my tracks to be accessed globally across the entire site. Can I achieve this with a factory in my central app.js, and if so, how?

'use strict';
angular.module('appName')
.controller('WorksCtrl', function ($scope, WorksFactory) {
$scope.WorksFactory = WorksFactory; //can refer to works in the view from WorksFactory.works
});
.factory('WorksFactory', function ($http, socket) {
var fact = { works: []};
$http.get('/api/works').success(function(works) {
angular.copy(works, fact.works);
socket.syncUpdates('work', fact.works);
});
return fact;
});

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.

how to define normal javascript function inside the controller in angularjs

I have a code snippet below.
sample.js
(function() {
/*global angular */
'use strict';
angular.module('myapp', ['spinner'])
.controller('myCtrl', ['$scope', '$window', function ($scope, $window ) {
$scope.methodname = function() {
if(something){
/* Doing some operations */
}
};
/* Here I need to define the callme javascript function */
function callme(response){
/* If I call like this, I'm getting error in console. */
}
}]); /* Controller ends here */
/* Creating a new anonymous function to perform some operations */
(function () {
'use strict';
/* Edited */
code.util.myHTTP(url, function (response) {
// Adding response to session storage
callme(response);
}, function () {
// Removing from session storage
});
})();
}());
Here, I can't able to call callme javascript function inside angular controller . I'm getting error in console like
Uncaught ReferenceError: callme is not defined
Is there any way to achieve this?
I need to use some controller parameters like $window inside callme function, that's why I'm defining callme function inside controller.
I have run function in my js file already like below
.run(function($rootScope, $log, $window) {
});
My requirement is like call anonymous function, on load there will be some api response, I need to call one method to process the response. because of these controller parameters, I wanted to define my method inside the controller. Is there any alternate way to achieve this?
Your indentation is all over the place and is what makes this code hard to understand. Here's your correctly formatted code..
(function () {
/*global angular */
'use strict';
angular.module('myapp', ['spinner'])
.controller('myCtrl', ['$scope', '$window', function ($scope, $window) {
$scope.methodname = function () {
if (something) {
/* Doing some operations */
}
};
/* Here I need to define the callme javascript function */
function callme (response) {
/* If I call like this, I'm getting error in console. */
}
}]); /* Controller ends here */
/* Creating a new anonymous function to perform some operations */
(function () {
'use strict';
/* Edited */
code.util.myHTTP(url, function (response) {
// Adding response to session storage
callme(response);
}, function () {
// Removing from session storage
});
})();
}());
The reason this doesn't work is because function declarations are hoisted to the top of their scope, but do not exist outside of that scope. If we remove some cruft, this is what your code simplifies to:
(function() {
function MyController($scope) {
$scope.methodname = function() {}
function callme(response) {
}
}
(function() {
callme()
}())
}())
callme is hoisted, but only to the top of MyController. That symbol doesn't exist outside of that scope, and that's why you can't do callme() in your nested scope.
What you are doing seems like an anti-pattern; even if you could achieve this, core.util.myHTTP would not execute within the Angular digest cycle, so you'd have to call $scope.$apply inside of your controller, which is generally considered a bad thing. Why not just use $http instead?
Nevertheless, if you really want to do this (and you don't), you could define your function like so:
(function() {
function callme(response) { }
function MyController($scope) {}
(function() {
...
callme()
}())
}())
Alternatively you could use directives (or components, depending on your angular version) to handle this instead.. which is how it should be done.
function SpinnerCtrl($http) {
this.$http = $http
}
SpinnerCtrl.$inject = ['$http']
SpinnerCtrl.onInit = function onInit() {
var that = this
return this.$http.get(url)
.then(function (response) {
// Store it in session storage, do whatever.
// Presumably there's some data you want to make accessible to other parts of the app, so I assign it to the controller here
that.response = response
})
}
angular.module('myapp', ['spinner'])
.component('spinner', {
controller: SpinnerCtrl,
template: '<h1>{{ $ctrl.response }}</h1>'
})
// or
function SpinnerCtrl($scope, $http) {
return $http.get(url).then(function (response) {
$scope.response = response
})
}
angular.module('myapp', ['spinner'])
.directive('spinner', function () {
return {
controller: SpinnerCtrl,
template: '<h1>{{ response }}</h1>'
}
})
Note that really you should move the $http call to a service and handle the session storage in there too; the session storage thing is implementation detail and the component/directive using it should not have to care. I haven't done that just for brevity.

Loading view configuration

I would like to do something like this:
app.config(function($routeProvider){
$routeProvider.when('products/list', {
controller: 'ProductListCtrl',
templateUrl : 'products/list/view.html',
resolve : { data : function(){
...
},
loadingTemplateUrl : 'general/loader.html'
}
});
I would like to have the loading page in a different view.
This would make the code in the view and controller of every page cleaner, (no <...ng-include ng-show="loading"...>). This would also mean that I don't have to $scope.$watch the data for changes. Is there a clean solution to do something similar (not necessarily in the .config method) or an alternative library to do this?
Assuming you want to show some general template for all state transitions while the data is resolved, my suggestion is to listen to the events fired by the routing library. This allows to use one central point to handle all state transitions instead of polluting the routing config (which I think will not be that easy to do).
Please see the docs for $routeChangeStart, $routeChangeSuccess and of course $routeChangeError at the angular router docs
Maybe someone could be interested in what I did: I created a new service and a new view directive. It could seem like a lot of work, but doing this was much easier than I had expected. The new service enables me to separate the main view from the loading view, that I could reuse in all pages of the application. I also provided the possibility to configure an error template url and error controller, for when the loading failed.
The Angular $injector, $templateRequest and $controller services do most of the work. I just had to connect a directive, that depends on these services, to the right event ($locationChangeSuccess), and to the promise, retrieved (using $q.all) from the resolve object's functions. This connection was done in the route service. The service selects the right template url and comtroller, and passes it on for the directive to handle.
A shortened version (with the getCurrentConfig method left out):
RouteService:
(function () {
'use strict';
// provider:
angular.module('pikcachu')
.provider('pikaRouteService', [function () {
var routeConfigArray;
var otherwiseRouteConfig;
//configuration methods
this.when = function (url, routeConfig){
routeConfigArray.push({url: url, routeConfig: routeConfig});
return this;
}
this.otherwise = function(routeConfig){
otherwiseRouteConfig = routeConfig;
return this;
}
// service factory:
this.$get = ['$rootScope', '$location', '$q', '$injector', '$templateRequest',
function ($rootScope, $location, $q, $injector, $templateRequest) {
function RouteService() {
this.setViewDirectiveUpdateFn = function(){ /*...*/ }
function init(){
$rootScope.$on('$locationChangeSuccess', onLocationChangeSuccess);
}
function onLocationChangeSuccess(){
// get the configuration based on the current url
// getCurrentConfig is a long function, because it involves parsing the templateUrl string parameters, so it's left out for brevity
var currentConfig = getCurrentConfig($location.url());
if(currentConfig.resolve !== undefined){
// update view directive to display loading view
viewDirectiveUpdateFn(currentConfig.loadingTemplateUrl, currentConfig.loadingController);
// resolve
var promises = [];
var resolveKeys = [];
for(var resolveKey in currentConfig.resolve){
resolveKeys.push(resolveKey);
promises.push($injector.invoke(resolve[resolveKey]));
}
$q.all(promises).then(resolveSuccess, resolveError);
function resolveSucces(resolutionArray){
// put resolve results in an object
var resolutionObject = {};
for(var i = 0; i< promises.length;++i){
resolved[resolveKeys[i]] = resolutionArray[i];
}
viewDirectiveUpdateFn(currentConfig.errorTemplateUrl, currentConfig.errorController);
}
function resolveError(){
viewDirectiveUpdateFn(currentConfig.errorTemplateUrl, currentConfig.errorController);
}
}
}
init();
}
return new RouteService();
}]
})();
View directive
(function () {
'use strict';
angular.module('pikachu')
.directive('pikaView', ['$templateRequest', '$compile', '$controller', 'pikaRouteService', function ($templateRequest, $compile, $controller, pikaRouteService) {
return function (scope, jQdirective, attrs) {
var viewScope;
function init() {
pikaRouteService.listen(updateView);
}
function updateView(templateUrl, controllerName, resolved) {
if(viewScope!== undefined){
viewScope.$destroy();
}
viewScope = scope.$new();
viewScope.resolved = resolved;
var controller = $controller(controllerName, { $scope: viewScope });
$templateRequest(templateUrl).then(onTemplateLoaded);
function onTemplateLoaded(template, newScope) {
jQdirective.empty();
var compiledTemplate = $compile(template)(newScope);
jQdirective.append(compiledTemplate);
}
}
init();
};
}
]);
})();

How do I add result to my scope ng-click?

This is a relatively simple piece of code that calls a service and returns some data. I need to set the $scope with the result of the data. Is there an easy way to set this data to the scope without resorting to to binding the scope to the function in the then clause?
Angular Code
(function () {
var app = angular.module('reports', []);
var reportService = function($http, $q) {
var service = {};
service.getMenuData = function() {
var deffered = $q.defer();
$http.get('/Report/MenuData').success(function(data) {
deffered.resolve(data);
}).error(function(data) {
deferred.reject("Error getting data");
});
return deffered.promise;
}
return service;
};
reportService.$inject = ['$http', '$q'];
app.factory('reportService', reportService);
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = function(e) {
reportService.getMenuData().then(function(data) {
// Need to set the $scope in here
// However, the '$scope' is out of scope
});
}
};
reportMenuController.$inject = ['$scope', '$http', 'reportService'];
app.controller('ReportMenuController', reportMenuController);
})();
Markup
<div>
<div ng-controller="ReportMenuController">
<button ng-click="getMenuData()">Load Data</button>
</div>
</div>
There is absolutely no problem to set the $scope from within the function passed to then(). The variable is available from the enclosing scope and you can set your menu data to one of its fields.
By the way: You should consider to use then() instead of success() for your http request. The code looks much nicer because then() returns a promise:
service.getMenuData = function() {
return $http.get('/Report/MenuData').then(function(response) {
return response.data;
}, function(response) {
deferred.reject("Error getting data");
});
}
success() is deprecated by now.
I didn't notice the small detail missing in the plunker where my code was different.
(function () {
...
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = getMenuData;
function getMenuData(e) {
reportService.getMenuData().then(function(data) {
// Now I have access to $scope
});
}
};
...
})();
Notice the changes to the two lines as below:
$scope.getMenuData = getMenuData;
function getMenuData(e) {
This also begs a small question which is, "Why is it okay to set getMenuData to the $scope before it is declared?

How to test angular app with John Papa style, Karma, and Jasmine with data service?

For some reason, even following the example provided by Josh in the post reply How to test John papa vm.model unit testing with jasmine?, I can't get my controller's values to show up in the testing area. I think it's because of the data service, but it is a necessary component for our SPA, as is using John Papa's styling.
Below is a code snippet to hold the code and display the errors I'm receiving.
(function() {
'use strict';
angular.module('tickets')
.service("DataService", DataService)
/* #ngInject */
DataService.$inject = ["$rootScope", "$q"];
function DataService($rootScope, $q) {
var vm = this;
vm.nameDefault = "Name -- Please Authenticate";
vm.name = vm.nameDefault;
};
})();
(function() {
'use strict';
angular.module('tickets')
.controller('HomeController', HomeController);
/* #ngInject */
HomeController.$inject = ['$scope', '$location', 'DataService'];
function HomeController($scope, $location, DataService) {
var vm = this;
vm.name = DataService.name;
vm.nameDefault = DataService.nameDefault;
};
})();
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/jasmine.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/jasmine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/jasmine-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/boot.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.4/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.4/angular-mocks.js"></script>
<script>
'use strict';
describe('Controller: HomeController', function() {
beforeEach(module('tickets'));
var controller, $location, DataService;
var tests = 0;
beforeEach(inject(function($controller, _$location_, _DataService_) {
$location = _$location_;
DataService = _DataService_;
scope = {};
controller = $controller('HomeController', {});
}));
var controller, scope, $location, DataService;
var tests = 0;
/* // This version works up until I try to verify the name and nameDefault in controller \\
*
* beforeEach(inject(function ($rootScope, $controller, _$location_, _DataService_) {
* $location = _$location_;
* DataService = _DataService_;
* scope = $rootScope.$new();
*
* controller = function () {
* return $controller('HomeController', {});
* };
* }));
*/
afterEach(function() {
tests += 1;
});
describe('local variables', function() {
describe('load the data model and values for all pertinent variables', function() {
it('should be instantiated', function() {
expect(DataService).toBeDefined();
});
it('should contain a name with an initial value before authentication', function() {
expect(DataService.nameDefault).toBe('Name -- Please Authenticate');
expect(DataService.name).toEqual(DataService.nameDefault);
});
});
describe('should load the controller with data model values, and update as data model values update', function() {
it('should be instantiated', function() {
expect(controller).toBeDefined();
})
it('should not have a vm attribute that can be reached from here', function() {
expect(controller.vm).toBeUndefined();
})
it('should contain a name with an initial value before authentication, both from the data model', function() {
expect(controller.name).toBe(DataService.name);
expect(controller.nameDefault).toBe(DataService.nameDefault);
});
});
});
it('should have tests', function() {
expect(tests).toBeGreaterThan(0);
});
});
</script>
My code, when I use it in our native environment, works to verify that everything in the data service has been instantiated properly (and using the commented out beforeEach block), but the styling using the example in the question referenced above throws even more errors about the scope not being instantiated, even though it is the same (with added dependencies) as that question.
I would expect the answer to be similar to the (currently unanswered) question: How to test John papa vm.model controllers and factories unit testing with jasmine?
I appreciate any help you guys offer.
-C§
Edit Even though I've answered and have success, I would love to get some feedback on why the implementation below works and the other two attempts do not. This is using Karma version 0.13.8 (latest), jasmine 2.1.0, and Angular 1.4.0.
I know it seems like I came up with the solution pretty quickly, but I've been wrestling with this since Friday afternoon (8/7) and have tried a dozen different formats without success.
Again, I welcome your comments and votes so that I can better understand why the version below works and the others have not, especially since I am still very green to AngularJS (1 month in, now).
Thanks again,
-C§
I get it now. Just had to look at Globally mock object in angularjs for jasmine/karma testing and my brain clicked.
The declaration and beforeEach block in the beginning of the test needs to look like this:
var controller, scope, $location, DataService;
var tests = 0;
beforeEach(inject(function ($rootScope, $controller, _$location_, _DataService_) {
$location = _$location_;
DataService = _DataService_;
scope = $rootScope.$new();
controller = $controller('HomeController', {
$scope: scope
});
}));
I think since I've messed with our initial setup a little too much (started the SPA from a template), I needed a strange implementation to make it all work. I'm now getting successful tests throughout:
I hope this helps someone else with similar issues.

Categories

Resources