Why do I get the error: Unknown provider: personHubProvider <- personHub - javascript

I am trying to work things out with angularjs, dependency injections, factories and signalR hubs, but I get the error Unknown provider: personHubProvider <- personHub and I cannot figure out what is causing it.
My code is like this:
First I declare the module:
var ucp = angular.module('UCP', []);
which is used in the html tag by ng-app="UCP".
Then I do some configuration on the ucp module for the hub settings and creating a signalRHub factory to get the hubs without typing much of the same code:
ucp.config(function($routeProvider) {
//Declaring routes, removed this part of code cause I think it hasn't to do
//with the problem I got
$.support.cors = true;
$.connection.hub.url = config.signalR.connectionURL;
$.connection.hub.logging = config.signalR.logging;
$.connection.hub.start();
}).factory('signalRHub', [function() {
return {
person: $.connection.Persons.server
};
}]);
Then, I create another factory, that takes care of getting the data from the server:
ucp.factory('personHub', ['signalRHub', function(signalRHub) {
return {
get: function(onsuccess, onerror) {
signalRHub.person.get()
.done(function(persons) {
onsuccess(persons);
})
.fail(function(error) {
onerror(error);
});
}
}
}]);
and this factory I inject in my controller so I can execute the call to get the data from the server and put it in the scope which provides to show the data in the browser:
ucp.controller('personController', ['$scope', 'personHub', function($scope, personHub){
var self = this;
$scope.init = function() {
personHub.get(self.ongetsuccess, self.ongeterror);
}
self.ongetsuccess = function(persons) {
$scope.persons = persons;
};
self.ongeterror = function(error) {
};
}]);
When I open the webpage I get the error I mentioned before: Error: Unknown provider: personHubProvider <- personHub.
I think something goes wrong with creating the personHub factory service, which on his turn causes an Dependency Injection error for the controller. My question is, what is causing the error, am I doing something wrong with creating the personHub factory and if so, what am I doing wrong?

As I had stated in the comments I know this error occurs because the injected $provider isn't defined therefore it's reporting that there is nothing to provide said $provider. A $provider might be a factory, service, controller, or value (there's probably others I'm forgetting) and must be defined before it is referenced for an injection.
More on providers and injection here: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
The way I've been dealing with organizing code is by encapsulating most of the parts necessary for a given view into one js file. In that JS file I start off with a services section where I define a new module like:
angular.module("loginModule.services",[/*dependencies*/]).service("loginService", [/*dependencies*/function(){ return {get:function(){return "what up!"}};}]);
Then lower in the file I define my controllers like
angular.module("loginModule.controllers",[/*dependencies*/]).controller("LoginCtrl" ,["$scope", function($scope) { /* code here* /}]);
then at the bottom of the file
angular.module("loginModule", ["loginModule.services", "loginModule.controllers"]);
And finally in my mainApp.js (with the main ng-app module)
angular.module("mainApp", ["loginModule"]);
I've defined my script blocks in the head at the moment.
<script type="text/javascript" src="components/loginModule.js"></script>
<script type="text/javascript" src="mainApp.js"></script>
Also to note my loginModule actually depends on other services for which the javascript files come afterward. This doesn't seem to be an issue since the mainApp.js is being deferred until the end.
I just checked some of this in Chrome and it appears the JS files from my computer do load in order and as pairs (only two per domain at a time then when a response comes back the next request is fired). I also tried moving the script blocks from the head to the bottom of the HTML and I can't tell any difference (my laptop has an SSD and mostly local files so probably more appreciable in a production scenario).

Related

Delete or Redefine angular module

with the following I would like to be able to define a module the first time and then change it on the fly later on. The reason is that my modules are dynamically generated and I'd rather reload one module than the whole app once One JavaScript file changes; it is Huge and Slow to load. The JS files are watched for changes and loaded lazily but always the initial module persists, i.e. 'This will be logged the first time' always gets logged even though the new JavaScript executes it does not overwrite the old module.
I think this would be a great tool for the AngularJS community but I don't know as yet it if it's already possible (I keep reading it's not). Alternatively I'd be happy if I could achieve the same using controllers/directives/1.5components
Thank you
// First state of angular module
angular.module('myApp.view1', [])
.controller('View1Ctrl', ['$scope',function($scope) {
$scope.buttonClick = function () {
console.log('This will be logged first time');
}
}]);
// Second state of angular module
angular.module('myApp.view1', [])
.controller('View1Ctrl', ['$scope',function($scope) {
$scope.buttonClick = function () {
console.log('This will be logged the second time');
}
}]);
<button ng-click="buttonClick()">Button</button>
However that's not a groovy thing to redefine your angular module.
You can do that by just defining the angular.module('YourAppModule', ['Dependencies']);
This will overwrite your previously bootstrapped module.
I have made a feature request for this issue, but unfortunately it's been denied, understandably so. Further details are here: https://github.com/angular/angular.js/issues/14743#issuecomment-224868610

Minified $provider injection with jasmine and angular

We have a project (angular) and some unittests for it (jasmine+sinon), which when minified creates some issues. For the actual code, we've solved these problems by injecting using the staticly typed string array, e.g. ['locationService', 'etcService'].
Unfortunately for the unittests, the minification has some more problems to solve. As an example:
module(function($provide){
$provide.service('etc..',...);
}
Code above immediately becomes unusuable since the provider variable gets renamed to something like 'a'. I've tried to tweak it a bit wrapping the function with something like below:
function injectTest($provide){
// do the same stuff
}
injectTest.$inject = ['$provide'];
which was a recommended solution in some other online posts. The problem is with modules this really doesn't work. I've tried both:
module(angular.injector().invoke(injectTest)); // which results in 'Unknown provider: $provideProvider <- $provide
and
module(injectTest); // which results in 'Unknown provider: nProvider <- n'
Is there any way to inject the $provider into a module without breaking on minification?
Inline injection :
var myFN = ['$provide', function($provide){
// do stuff
}]
Now if you want to bind a function to a 3rd party library where you need service let's say in my sample your function need the service CRUDService and receive a params objects from the 3rd party :
var myFN = ['CRUDService', function(CRUDService){
// do some init stuff
// you can either make it a singleton by sotrng the function and return the reference or either return new function on each call
return function(params){
// do stuff
};
}] ;
// now to bind it to your 3rd party
objectFor3rdParty = {fn:$injector.invoke(myFN)};
I use only inline injection instead of $inject, matter of taste i guess.

How to unit test an AngularJS service / factory

I'm already some time in the development using AngularJS, and what I do write works, but know I've come to a point where I would like to run unit tests on my AngularJS code.
I have created a very simple service that will inject a stylesheet onto the page,
see the example code below:
var OfficeSuiteStylesheetInjectorModule = angular.module('OfficeSuiteStylesheetInjectorModule', []);
OfficeSuiteStylesheetInjectorModule.factory('stylesheetInjectorService', ['$q', function($q) {
// Returns the service itself.
return {
inject: function(id, uri) {
var deferred = $q.defer();
// Embed the stylesheet into the page, but only when it's non-existing.
if (!angular.element('#' + id).length) {
var link = StylesheetFactory.Create(id, uri);
{
link.onload = deferred.resolve;
angular.element('head').append(link);
}
return deferred.promise;
}
}
}
}]);
It's not a big service, it's just dependend on $q for promises so that I can run additional logic when the stylesheet has been embedded in the page.
Now, I'm using Jasmine (I'm quite new to this) for testing my JavaScript code and I would like to test this module.
I have a skeleton:
// Tests for the angular 'StylesheetInjectorService'.
describe('StylesheetInjectorService', function() {
var stylesheetInjectorService = {};
// This code is executed before every test.
beforeEach(function() {
// Tells jamine that the module we're working on is the 'OfficeSuiteStylesheetInjectorModule'.
angular.module('OfficeSuiteStylesheetInjectorModule');
});
// Ensures that it injects a stylesheet element into the page.
it('Should inject a stylesheet element into the page.', function() {
// How to test here that the stylesheet is injected?
});
});
});
How can I inject te service in the page and ensures that the stylesheet is loaded?
Edit: Loading service now works:
beforeEach(module('OfficeSuiteStylesheetInjectorModule'));
// This code is executed before every test.
beforeEach(function() {
// Inject the required dependencies into the page.
inject(function($injector) {
stylesheetInjectorService = $injector.get('stylesheetInjectorService');
});
});
The same question is still open however. How to test if a stylesheet was embedded in the page?
Any help is highly appreciated.
Kind regards
To write a spec for the attachment of a stylesheet to angular.element('head') I would change the logic a bit to attach it to $document.head.
If you dont want to do that, I would recommend that you change your service into a directive seeing as how injecting a script element, is manipulating the DOM. That way you would kill two birds with one stone, as you would need to inject $compile to test your directive (which would enable you to $compile a custom head element to boot). But this is slightly "over the top" for now.
Implementation:
if (!angular.element('#' + id).length) {
var link = StylesheetFactory.Create(id, uri);
link.onload = deferred.resolve;
$document.head.append(link);
return deferred.promise;
}
}
beforeEach:
/**
* Sorry, this was previously $location which was just
* such a silly mistake.
*/
var $timeout;
beforeEach(function () {
inject(function ($injector) {
$timeout = $injector.get('$timeout');
});
});
it:
it('attaches the stylesheet to $document.head', function () {
styleSheetInjectorService.inject('someId', '/path/to/some/stylesheet');
$timeout.flush(); // flush promises
expect(angular.element($document.head).lastChild[0].nodeName).to.eq('LINK');
});
Something along those lines should get you up and running. Bare in mind that the spec I wrote uses the chai#expect style assertions, and the mocha test framework. Edit the syntax to fit Jasmine if you mean to copy-paste.
I had this doubt a while ago.
To inject your controllers and services into your tests you need to use a tool called Angular Mocks. Here's some official reference about it.
I sugest you use it together with the Karma enviroment.
Here's a great Getting Started tutorial:
https://www.airpair.com/angularjs/posts/unit-testing-angularjs-applications
This other one is for the Ionic Framework, but can still aplly to your case
Hope it can help.

Dynamically add AngularJS Script

I've got a project that's has a very rapid development cycle which causes many changes in the main app.js file. This file has the configs as well as controllers for the AngularJS app that's being used as part of the project. This created Caching issues which I tried solving as follows:
<script type="text/javascript">
var s = document.createElement('script')
s.setAttribute('src', 'assets/js/app-v2.js?v='+(new Date().getMilliseconds()));
document.body.appendChild(s);
</script>
However, this gives me an error in angular saying:
Failed to instantiate module appName due to:
Error: [$injector:nomod] Module 'appName' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
My HTML is setup as
<html ng-app="appName">....</html>
So I tried setting the ng-app dynamically after loading the script but that doesn't work either. Gives me the same issue as earlier. Is it possible for me to add the appName dynamically during or after the load of the app-v2.js file?
I have found this answer from the stackoverflow while I was facing same issue.
bootstrap() will call the AngularJS compiler for you, just like ng-app.
// Make module Foo and store $controllerProvider in a global
var controllerProvider = null;
angular.module('Foo', [], function($controllerProvider) {
controllerProvider = $controllerProvider;
});
// Bootstrap Foo
angular.bootstrap($('body'), ['Foo']);
// .. time passes ..
// Load javascript file with Ctrl controller
angular.module('Foo').controller('Ctrl', function($scope, $rootScope) {
$scope.msg = "It works! rootScope is " + $rootScope.$id +
", should be " + $('body').scope().$id;
});
// Load html file with content that uses Ctrl controller
$('<div id="ctrl" ng-controller="Ctrl" ng-bind="msg">').appendTo('body');
// Register Ctrl controller manually
// If you can reference the controller function directly, just run:
// $controllerProvider.register(controllerName, controllerFunction);
// Note: I haven't found a way to get $controllerProvider at this stage
// so I keep a reference from when I ran my module config
function registerController(moduleName, controllerName) {
// Here I cannot get the controller function directly so I
// need to loop through the module's _invokeQueue to get it
var queue = angular.module(moduleName)._invokeQueue;
for(var i=0;i<queue.length;i++) {
var call = queue[i];
if(call[0] == "$controllerProvider" &&
call[1] == "register" &&
call[2][0] == controllerName) {
controllerProvider.register(controllerName, call[2][1]);
}
}
}
registerController("Foo", "Ctrl");
// compile the new element
$('body').injector().invoke(function($compile, $rootScope) {
$compile($('#ctrl'))($rootScope);
$rootScope.$apply();
});
Hope it will help you

When I reference local angular lib I get error: Argument is not a function, got undefined and CDN works ok? [duplicate]

I am writing a sample application using angularjs. i got an error mentioned below on chrome browser.
Error is
Error: [ng:areq] http://errors.angularjs.org/1.3.0-beta.17/ng/areq?p0=ContactController&p1=not%20a%20function%2C%20got%20undefined
Which renders as
Argument 'ContactController' is not a function, got undefined
Code
<!DOCTYPE html>
<html ng-app>
<head>
<script src="../angular.min.js"></script>
<script type="text/javascript">
function ContactController($scope) {
$scope.contacts = ["abcd#gmail.com", "abcd#yahoo.co.in"];
$scope.add = function() {
$scope.contacts.push($scope.newcontact);
$scope.newcontact = "";
};
}
</script>
</head>
<body>
<h1> modules sample </h1>
<div ng-controller="ContactController">
Email:<input type="text" ng-model="newcontact">
<button ng-click="add()">Add</button>
<h2> Contacts </h2>
<ul>
<li ng-repeat="contact in contacts"> {{contact}} </li>
</ul>
</div>
</body>
</html>
With Angular 1.3+ you can no longer use global controller declaration on the global scope (Without explicit registration). You would need to register the controller using module.controller syntax.
Example:-
angular.module('app', [])
.controller('ContactController', ['$scope', function ContactController($scope) {
$scope.contacts = ["abcd#gmail.com", "abcd#yahoo.co.in"];
$scope.add = function() {
$scope.contacts.push($scope.newcontact);
$scope.newcontact = "";
};
}]);
or
function ContactController($scope) {
$scope.contacts = ["abcd#gmail.com", "abcd#yahoo.co.in"];
$scope.add = function() {
$scope.contacts.push($scope.newcontact);
$scope.newcontact = "";
};
}
ContactController.$inject = ['$scope'];
angular.module('app', []).controller('ContactController', ContactController);
It is a breaking change but it can be turned off to use globals by using allowGlobals.
Example:-
angular.module('app')
.config(['$controllerProvider', function($controllerProvider) {
$controllerProvider.allowGlobals();
}]);
Here is the comment from Angular source:-
check if a controller with given name is registered via $controllerProvider
check if evaluating the string on the current scope returns a constructor
if $controllerProvider#allowGlobals, check window[constructor] on the global window object (not recommended)
.....
expression = controllers.hasOwnProperty(constructor)
? controllers[constructor]
: getter(locals.$scope, constructor, true) ||
(globals ? getter($window, constructor, true) : undefined);
Some additional checks:-
Do Make sure to put the appname in ng-app directive on your angular root element (eg:- html) as well. Example:- ng-app="myApp"
If everything is fine and you are still getting the issue do remember to make sure you have the right file included in the scripts.
You have not defined the same module twice in different places which results in any entities defined previously on the same module to be cleared out, Example angular.module('app',[]).controller(.. and again in another place angular.module('app',[]).service(.. (with both the scripts included of course) can cause the previously registered controller on the module app to be cleared out with the second recreation of module.
I got this problem because I had wrapped a controller-definition file in a closure:
(function() {
...stuff...
});
But I had forgotten to actually invoke that closure to execute that definition code and actually tell Javascript my controller existed. I.e., the above needs to be:
(function() {
...stuff...
})();
Note the () at the end.
I am a beginner with Angular and I did the basic mistake of not including the app name in the angular root element. So, changing the code from
<html data-ng-app>
to
<html data-ng-app="myApp">
worked for me. #PSL, has covered this already in his answer above.
I had this error because I didn't understand the difference between angular.module('myApp', []) and angular.module('myApp').
This creates the module 'myApp' and overwrites any existing module named 'myApp':
angular.module('myApp', [])
This retrieves an existing module 'myApp':
angular.module('myApp')
I had been overwriting my module in another file, using the first call above which created another module instead of retrieving as I expected.
More detail here: https://docs.angularjs.org/guide/module
I just migrate to angular 1.3.3 and I found that If I had multiple controllers in different files when app is override and I lost first declared containers.
I don't know if is a good practise, but maybe can be helpful for another one.
var app = app;
if(!app) {
app = angular.module('web', ['ui.bootstrap']);
}
app.controller('SearchCtrl', SearchCtrl);
I had this problem when I accidentally redeclared myApp:
var myApp = angular.module('myApp',[...]);
myApp.controller('Controller1', ...);
var myApp = angular.module('myApp',[...]);
myApp.controller('Controller2', ...);
After the redeclare, Controller1 stops working and raises the OP error.
Really great advise, except that the SAME error CAN occur simply by missing the critical script include on your root page
example:
page: index.html
np-app="saleApp"
Missing
<script src="./ordersController.js"></script>
When a Route is told what controller and view to serve up:
.when('/orders/:customerId', {
controller: 'OrdersController',
templateUrl: 'views/orders.html'
})
So essential the undefined controller issue CAN occur in this accidental mistake of not even referencing the controller!
This error might also occur when you have a large project with many modules.
Make sure that the app (module) used in you angular file is the same that you use in your template, in this example "thisApp".
app.js
angular
.module('thisApp', [])
.controller('ContactController', ['$scope', function ContactController($scope) {
$scope.contacts = ["abcd#gmail.com", "abcd#yahoo.co.in"];
$scope.add = function() {
$scope.contacts.push($scope.newcontact);
$scope.newcontact = "";
};
}]);
index.html
<html>
<body ng-app='thisApp' ng-controller='ContactController>
...
<script type="text/javascript" src="assets/js/angular.js"></script>
<script src="app.js"></script>
</body>
</html>
If all else fails and you are using Gulp or something similar...just rerun it!
I wasted 30mins quadruple checking everything when all it needed was a swift kick in the pants.
If you're using routes (high probability) and your config has a reference to a controller in a module that's not declared as dependency then initialisation might fail too.
E.g assuming you've configured ngRoute for your app, like
angular.module('yourModule',['ngRoute'])
.config(function($routeProvider, $httpProvider) { ... });
Be careful in the block that declares the routes,
.when('/resourcePath', {
templateUrl: 'resource.html',
controller: 'secondModuleController' //lives in secondModule
});
Declare secondModule as a dependency after 'ngRoute' should resolve the issue. I know I had this problem.
I was getting this error because I was using an older version of angular that wasn't compatible with my code.
These errors occurred, in my case, preceeded by syntax errors at list.find() fuction; 'find' method of a list not recognized by IE11, so has to replace by Filter method, which works for both IE11 and chrome.
refer https://github.com/flrs/visavail/issues/19
This error, in my case, preceded by syntax error at find method of a list in IE11. so replaced find method by filter method as suggested https://github.com/flrs/visavail/issues/19
then above controller not defined error resolved.
I got the same error while following an old tutorial with (not old enough) AngularJS 1.4.3. By far the simplest solution is to edit angular.js source from
function $ControllerProvider() {
var controllers = {},
globals = false;
to
function $ControllerProvider() {
var controllers = {},
globals = true;
and just follow the tutorial as-is, and the deprecated global functions just work as controllers.

Categories

Resources