So this is really weird, maybe it has a simple answer I'm missing. The following code gives an unknown provider error:
var foo = angular.module('foo', [ 'ngRoute', 'ngAnimate', 'ngCookies' ]);
foo.factory('fooApi', function ($scope, $http) {
var url = '/api/';
var factory = {};
factory.action = function (fields) {
fields.userid = $scope.userid;
fields.token = $scope.token;
console.log(JSON.stringify(fields));
return $http.post(url, { data: fields });
};
return factory;
})
.controller('loginController', function ($scope, fooApi) {
// do stuff
});
It's all loading together in the same file, and I'd think the factory being first would resolve and the injector would be able to find it when referenced below. But it gives an unknown provider error.
However, if I comment out the controller and wait for the page to load and then do the exact same controller declaration in the Chrome JS console it works fine.
Anyone run into this before and know how to deal with it? I haven't been able to find this same exact issue anywhere.
Like #tasseKATT said, you can not inject $scope into a service, particularly a factory. Maybe your confusion is because $scope can be injected in controllers, so you tried to injected into a factory.
An interesting thing is that the $scope that you see being injected into controllers is not a service - like the rest of the injectable stuff -, but is a Scope object.
The main purpose of $scope is a king of glue between views and controllers, it doesn't make much sense to pass a $scope into a service.
The services only have access to the $rootScope service.
If you need to pass the $scope of a specific controller to a service always you can pass it like parameter of a function in the service. This approach is not recommended because starting to break the SoC and the single responsibility principle, but maybe could fit you.
Good luck :-)
Related
I am using Angular Websockets to define a factory which is intended to be globally active. The current factory is defined as:
ngApp.factory('FixtureFeed', [
'$websocket', '$rootScope', function($websocket, $rootScope) {
var dataStream;
dataStream = $websocket("ws://" + window.location.hostname + "/");
dataStream.onMessage(function(message) {
return $rootScope.$broadcast('message', message.data);
});
return dataStream;
}
]);
And then I simply include it as a dependency on one of my controllers, such as:
ngApp.controller('FixtureCustomCtrl', function($scope, $http, $rootScope, FixtureFeed) {
# other code...
});
And simply having it there and not touching it further appears to allow it to be active beyond the life of the controller, where by if I switch to a different angular path which calls a different controller, the FixtureFeed continues to handle messages and broadcast.
I could include the factory as a dependency on all my controllers, and this appears to work fine, only ever instantiating the factory once and handling each message once, but it feels like a dirty hack of course. I also can't guarantee that any given controller will become immediately active as I have all sorts of routes that could be the entry point to using the app. What is the correct way to handle this please?
This snippet of code is giving error when running my test case.
beforeEach(module(function($scope) {
// do something interesting with the service
//console.log($controller);
}));
Error coming up in console.
debug.js:21 Uncaught Error: [$injector:modulerr] Failed to instantiate
module function ($scope) due to:
Error: [$injector:unpr] Unknown provider: $scope
Could anyone please explain why it is not able to find $scope. It is also not working if I change it to $timeout or $controller.
Updated Code
If I change the code to this
Then it works
module(function($controllerProvider) {
// we could also access the $controllerProvider.allowGlobals() function,
// which allows us to register a controller on the window object.
$controllerProvider.register('ProductsController', function() {
// logic of the controller...
});
});
You're mixing up two different functions module and inject.
module configures the jasmine environment to use the angular module you specify. That's probably app. If you don't call this, the injector will not be able to find your services, controllers, directives, etc. In the module function, you can inject providers, not services, factories, etc. You use providers to configure how the services will behave later.
inject takes the function you supply and "injects" the services, constants, factories, etc by name before calling it. You use this to inejct the services etc themselves.
Here's a sample of how to split the calls in your code. I also changed the $scope to use $rootScope and create a scope.
beforeEach(function() {
module("app"); //or if your module is name something different, use that name instead
inject(function($controller, $rootScope) {
var $scope = $rootScope.$new();
var myCtrl = $controller("myCtrl", { $scope: $scope });
}));
});
I have a sidebar that contains a feed from various social medias, along with a service in AngularJS that queries my API for the data. Below is the controller, along with the service (in that order). Why isn't it being executed on page load, and how should I rewrite my code to make it load the data when the page is rendered to the client?
angular.module('HomeCtrl', ['MediaServ']).controller('HomeController', [
'$scope',
'MediaServ',
'$rootScope',
function($scope, $rootScope, MediaServ){
if ($rootScope){
$scope = $rootScope;
}
function handleRes(response){
console.log('hello!');
}
angular.element(document).ready(function () {
$scope.SocialMedia = function(){
MediaServ.feed()
.then(handleRes, handleRes);
}
});
}]);
angular.module('MediaServ', []).service('MediaServ', [
'$http',
function($http){
this.feed = function(){
return $http.get('/api/social/feed');
}
}]);
You can only use things (be it services, factories, filters, etc) from another module if you have first injected that module into your current one. As the code above is written your modules don't know about each other, and so you can't inject MediaServ into HomeController.
To fix it, inject the MediaServ module into the HomeCtrl module like this:
angular.module('HomeCtrl', ['MediaServ'])...
I will also suggest not shortening names (minifiers should shorten things, developers should not) and not using the same name for services and apps. The last one in particular can cause a lot of confusion in a large project. Personally I prefer to name modules things like "media.services" and services "MediaService" but that is personal taste, just keep a clear naming convention so that you always know what is what.
You shouldn't need to wrap your code in angular.element(document).ready(function () { });
The controller will execute on page load automatically, provided it is reference in the page.
So, UncleDave and Erik Honns' responses both led me to realize what was wrong (on top of having a typo in handleRes). Here is my new code:
angular.module('HomeCtrl', ['MediaServ']).controller('HomeController', [
'$scope',
'$rootScope',
'MediaServ',
function($scope, $rootScope, MediaServ){
if ($rootScope){
$scope = $rootScope;
}
function handleRes(response){
if (response.data.tweets){
$scope.SocialMedia = response.data.tweets;
}
}
MediaServ.feed()
.then(handleRes, handleRes);
}]);
It is now working. Thank you everybody for your help. I won't pick a best answer, and instead will let others vote. Feel free to flag this question to be deleted, as the errors were more on my side (kind of being lazy about reading through my code, but I thought that this was one of those Angular things you just kind of have to learn).
Thanks everyone for your help!
I was wondering what the best moment is while initializing the app to retrieve data from:
1 - a REST service
2 - $routeParams
to define application wide constant.
config phase only accepts providers and during the config / run phase $routeParams properties are undefined.
This seems like somewhat in the right direction:
Can you pass parameters to an AngularJS controller on creation?
Also: how to define a constant within a controller, is that possible?
app.controller('MainCtrl', function($scope) {
//define app.constant here
}
--edit: typo
During run phase all the providers should be initialized and working correctly, you can use the $http service to retrieve what ever parameters are needed for you.
I am pretty sure the $routeParams are initialized at run phase as well.
Defining constants in a controller isn't a good practice (in my opinion), if they are unique to that controller then they are just variables, and if you want real application wide constants, use a service, that's what they are for :)
I know of one easy way to pass parameters to controllers on creation, which is using the angular ui router project: https://github.com/angular-ui/ui-router
In the resolve function you can do http calls if necessary, inject constants etc, it's very handy and personally I never build an angular project without it.
I am pretty sure there are more ways, but usually the best practice to pass data between controllers is using a service.
On a side note, if you have a piece of data that is common to more than 1 controller, the easiest way is to put that data on a service and do a watch on that service return value, for example, say I have isLoggedIn, which can change at any moment and a lot of controllers will want to be notified about it, use a UserService and watch for it's value:
UserService.isLoggedIn = function() {
return _isLoggedIn;
}
And in your Controller:
$scope.$watch(function() {
return UserService.isLoggedIn();
}, doSomeAction);
I hope this helps!
This http://www.jvandemo.com/how-to-resolve-angularjs-resources-with-ui-router/ seems like a nice guide, basically, you add the resolve to the state:
.state("customers", {
url : "/customers",
templateUrl: 'customers.html',
resolve: {
//any value you want, this function should return a promise,
//only when that promise is resolved, it will instantiate the controller
//Make sure however you add some signal that something is happening because
//while fetching it can seem like the page is not responding
customers: ['$http', 'anyOtherServiceYouMightNeed', function($http, otherService){
//return a promise
return $http.get('api/customers');
}],
//and for constant
constants: ['ConfigService', function(config) {
return config.appConstants;
}]
},
//customersCtrl will have customers resolved already
controller : 'customersCtrl'
});
app.controller('customersCtrl', ['$scope', 'customers', 'constants',
function($scope, customers, consts) {
//customers will be ready and resolved when the controller is instantiated
//you can do this with anything you might need inside a controller
}
Is there a way to provide a non-Angular injection target to the Angular $injector such that Angular constructs like $http, $scope, $location or $q can be injected into it?
//non-angular injection container
var injectionTarget= {
$http:undefined,
$scope:undefined
}
//means to inject into target - this is the part in question
var injector = angular.injector();
injector.injectInto( injectionTarget, ["$http", "$scope"]);
I'm having the hardest time finding any info on how to accomplish what I would assume is a very sought-after feature.
I think that probably the easiest way to do this would be to register your objects as services with the module.
var myObject = {} //Defined elsewhere or here as empty
app.service(‘aReferenceName’, function($http){
myObject.$http = $http;
return myObject;
});
This would have the double effect of setting the properties you want on your object, and making it accessible from angular as needed. It's also a pretty simple block of code. Note the implication though that as a service it would be a singleton from angular's perspective. If you need to do it as a class with many instances, you'll be wanting a factory.