When I try to bind a controller to a template using the angular-ui-router $stateProvider, I run into the following error:
'ShouldWorkController' is not a function. Got undefined.
However, when I declare the controller inside the template using ng-controller, everything works fine. What could be wrong here?
app.ts
module App {
var dependencies = [
MyControllers
]
function configuration($stateProvider: ng.ui.IStateProvider) {
$stateProvider
.state("shouldWork", {
url: "/shouldWork",
templateUrl: "app/shouldWork.html"
controller: "ShouldWorkController" // does not work
});
}
}
shouldWorkController.ts
module App.MyControllers {
interface IShouldWorkViewModel {
}
class ShouldWorkController implements IShouldWorkViewModel {}
}
ShouldWork.html
<div ng-controller="ShouldWorkController as viewModel" us-spinner spinner-key="spinner-1">
^ --- this works nicely
That message means, that such controller "ShouldWorkController" is not loaded int he main angular module. Be sure that you do call register at the end:
module App.MyControllers {
...
class ShouldWorkController implements IShouldWorkViewModel {}
}
// we have to register this controller into some module (MyControllers)
// which is also referenced in the main app module
angular.module('MyControllers')
.controller('ShouldWorkController', App.MyControllers.ShouldWorkController );
I realise this is old, but I came here via Google with the same issue, not for the firs time. Things to check include:
Export statement for your controller class. From the code you posted I see that you are missing an export statement for your ShouldWorkController class. This may not be the issue in your case, but it is something you should check. I can reproduce this error by removing the export statement from my controller classes.
Check your HTML template exists (if using UI-Router). As described in the UI-Router documentation: "The controller will not be instantiated if template is not defined."
Register your controllers in the same file as the controller. Some tutorials do demonstrate controllers being registered in the module file. While this does work, I personally have found it more error prone than directly registering the controller within the controller file.
Check your typescript references. Make sure that you add typescript references (e.g. ///<reference path="../app/services/shouldWorkService.ts">) to typescript files that contain any types that you reference
Check the name of your controller matches that declared in your $stateProvider configuration.
Related
I have an angular 1.5 project with many modules and each module may depend on other modules. Trying to unit test say a controller which is part of a module I would do import the module like this:
angular.mock.module('SaidModule');
...then provide and inject its services where needed.
The problem is that SaidModule depends on AnotherModule1, AnotherModule2, AnotherModule3....
angular.module('SaidModule', ['AnotherModule1', 'AnotherModule2', 'AnotherModule3']);
So naturally when I call SaidModule the other modules are also invoked which is out of scope in terms of Unit testing
In the unit test I have tried the following solution
angular.module('AnotherModule1',[]);
angular.module('AnotherModule2',[]);
angular.module('AnotherModule3',[]);
angular.mock.module('SaidModule');
and although for the current unit test I have successfully decoupled the dependencies I have also destroyed
the actual AnotherModule1, AnotherModule2, AnotherModule3 so when its there turn to be unit tested they are
not even visible in the angular project which seems correct to me. as I am using angular.module to define a
new module which just happens to override the actual module.
This solution though is also suggested here mocking module dependencies
In the angular docs it states see angular docs mock module
If an object literal is passed each key-value pair will be registered on the module via $provide.value,
the key being the string name (or token) to associate with the value on the injector.
So it seems to me that the solution is using somehow angular.mock.module somehow to override the dependent
modules but so far I have not found a solution.
Any help much appreciated
By calling angular.module('AnotherModule1',[]) you are redefining the AnotherModule1, which I think is causing your downstream problems. Instead, use $provide for each dependent service. There's no need to mock the dependent modules.
Let's say your controller definition looks like this:
angular
.module('SaidModule', ['AnotherModule1', 'AnotherModule2'])
.controller('SaidController', [
'$scope',
'AnotherService',
function($scope, AnotherService) {
this.anotherService = AnotherService.helper();
}
);
Then your test might look like:
describe('SaidController', function() {
var controller, scope, AnotherService;
beforeEach(module('SaidModule'));
beforeEach(module(function($provide) {
AnotherService = { helper: function() { return 0; } };
$provide.value('AnotherService', AnotherService);
}));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
controller = $controller('SaidController', {
$scope: scope
});
}));
it('creates controller', function() {
expect(controller).not.toBeNull();
});
});
There's no need to mock the dependent modules, just the dependent services.
I am currently building a base for AngularJS in combination with RequireJS and so far I got everything working. there's just a little thing that I do not understand at this point. I have a file which creates the angular module, when this module is created it requires a controller and assigns it to the module. The strange thing though, the controller needs the module as dependency while in the module's file the module has not been returned yet because the require statement is executed before the return statement. This somehow seems to work but it has a bad smell to it.
Module file:
// Home is defined here and can later be used in controllers (and Services)
define('home', ['require', 'angular'], function(require, angular) {
var homeModule = angular.module('AngularBase.home', ['AngularBase.core']);
homeModule.config(['$controllerProvider', '$provide', '$compileProvider', function($controllerProvider, $provide, $compileProvider) {
// We need this in order to support lazy loading
homeModule.controller = $controllerProvider.register;
homeModule.factory = $provide.factory;
// And more, not relevant at this moment
}]);
// It loads the controller that depends on this module here
require(['modules/home/controllers/homeController'], function() {
// Dependencies loaded
});
// Yet in my mind controllers that need this module can only use it when the following return statement is called.
return homeModule;
});
Controller File:
// As you can see this controller depends on home while home hasn't returned its module yet
// Yet it seems to work just fine
define(['home'], function(home) {
home.controller('homeController', ['$scope', 'homeService', function($scope, homeService) {
$scope.title = 'Home controller';
}]);
});
I assume that it is not a good approach to do it like this and therefore I need some suggestions on how to make this happen in a clean way. I thought about grabbing the AngularBase.home module via angular.module('AngularBase.home') in the controller file and defining my controller on this. This however no longer allows me to insert a mockModule for testing in this controller via RequireJS's map function.
map: {
'*' : {
'home' : 'mock-module'
}
}
Any suggestions on how to refactor this into a more clean solution?
I have found the solution to my problem. In the end it seems to be just fine to do it the way I am currently doing it. When a file is called and has a define statement in it it will wait until all dependencies are available until the function is executed. This means that the controller will actually wait for the module to finish initializing before calling its function to register itself.
The way I am doing it above is just fine.
Source: http://www.slideshare.net/iivanoo/handlebars-and-requirejs (slides 11 till 24)
First a little background on what I want to achieve. I have a service that requires some configuration before it gets injected and used. After doing some research I figured that having a provider for the service would be the best solution. So I implemented the provider according to this example. Even thought the Typescript compiler (I'm using typescript to compile my code into valid JavaScript) thinks it's oké, JavaScript does not recognize the function that's available through the provider to set some options.
My code looks as follows (some code has been left out or renamed for a reason)
export interface ICustomServiceProvider extends ng.IServiceProvider {
setOptions(options: ICustomOptions): void;
$get($http, $window, $resource): ICustomService;
}
class CustomServiceProvider implements ICustomServiceProvider {
private options: ICustomOptions;
public $get($http, $window, $resource) {
return new CustomService($http, $window, $resource);
}
public setOptions(options: ICustomOptions): void {
this.options = options;
}
}
angular.module('custom', ['ngResource'])
.service('CustomService', CustomService)
.provider('CustomService', CustomServiceProvider);
The problem occurs when using the provider in one of my unit tests (I'm using Karma with Mocka for testing) and calling the setOptions function. Which is done like this.
describe('CustomService', function() {
var provider: ICustomServiceProvider;
var options: {hello: 'world'};
beforeEach(inject(function($injector: ng.auto.IInjectorService) {
provider = <ICustomServiceProvider> $injector.get('');
}));
it('CustomService provider test', function() {
provider.setOptions(options);
});
}
When running this test Karma throws an error saying
TypeError: provider.setOptions is not a function at Context.[anonymous]
Also the compiled JavaScript Visual Studio Code is giving me a green warning (I don't think it's a error) on the provider variable on the line .provider('CustomService', CustomServiceProvider);
Argument of type '() => void' is not assignable to parameter of type 'IServiceProvider'. Property '$get' is missing in type '() => void'.
(local var) CustomServiceProvider: () => void
Providers
I already spent hours on fixing this problem but cannot seem to find the solution. Any idea on how to fix this of what I'm doing wrong?
Thanks in advance.
Edit (30-09-2015)
The service I'm am talking about looks like this:
export interface ICustomService {
// Some function definitions
}
class CustomService implements ICustomService {
static $inject = ['$http', '$window', '$resource'];
constructor(httpService: ng.IHttpService, windowService: ng.IWindowService, resourceService: ng.resource.IResourceService, options: ICustomOptions) {
// Setting variables
}
}
The main problem here, IHMO, is the fact that your are trying to register a service & a provider with the same name (CustomService). You don't have to do this.
In your case, it seems that you just have to register the provider. Rename your CustomServiceProvider class to CustomService, then change your last lines of code to just:
angular
.module('custom', ['ngResource'])
.provider('CustomService', CustomService);
This way, in the config phase of your app, you will be able to inject CustomServiceProvider (and use setOptions), but also CustomService in controllers, directives & other services.
Please read Angular Documentation on providers (mainly "Provider Recipe") for more information.
I am kind of new to the AngularJS framework and I am trying to migrate my test project using the standard router to use the UI-router, but I get the following error:
Error: [ng:areq] Argument 'mainCtrl' is not a function, got undefined
What I have done so far is:
Controller:
// mainCtrl.js
angular.module("sm-web")
.controller('mainCtrl',['$scope',
function($scope) {
...
}]);
Router:
angular.module('sm-web', ['ui.router'])
.config(['$stateProvider', '$urlRouterProvider', function( $stateProvider, $urlRouterProvider ) {
$urlRouterProvider.otherwise('root');
$stateProvider
.state('root', {
url: '',
templateUrl: path + 'ng/sm/view/main.html',
controller: 'mainCtrl'
});
}]);
Index:
<body ng-controller="mainCtrl">
<main-menu></main-menu>
<div class="container">
<div ui-view></div>
</div>
</body>
This works when I use the standard router, but not with the UI-router. Does anyone have any idea of what I am doing wrong?
It seems you have an issue w/the order you declare things. For you to declare the module "sm-web" you need to do this:
angular.module('sm-web', ['ui.router']);
Note that the presence of that 2nd array argument is what tells Angular that you're declaring the module (eg. creating a new module). When you leave that 2nd argument out, you're retrieving the module you previously declared.
So with that in mind, look at how it all is coming together in your code:
To declare the controller, you retrieve the module "sm-web" (by leaving off the 2nd array arg).
When configuring the router states, you declare a new module "sm-web". But note that immediately after you declare this new module, you try to register a state with the controller named "mainCtrl" -- but that doesn't exist yet.
You need to create the module somewhere before doing all of the above. After creating the module, then register the controller on the module. Finally, with the controller defined, then you can register the state that uses the controller.
There's too many ways to solve this ordering problem, so I'm not going to suggest anything further. It depends on what files the code lives in and the order you load those files in index.html.
In order to avoid your problem change your code by the following code:
// mainCtrl.js
angular.module("sm-web")
.controller('mainCtrl',['$scope',
function($scope) {
...
}]);
I have a set of angular $resource defined in a module called 'App.API' in a single file which I cannot touch because it is generated. (With loopback-angular, a tool to generate angular $resource from server side model definitions)
Let's take the Product dependency as en example, later in the app, I want to override its prototype, like this :
module('App.NewModule', ['App.API']).run(['Product', function(Product) {
Product.prototype.getTitle = function() {
return 'Product name is ' + this.name;
};
// From now on I can use p.getTitle() on every Product $resource
});
It works.
The thing is, I have many different files, each containing modules, and I am experiencing a dependency injection issue : I can access the getTitle function inside NewModule, but not inside other modules.
Question : How can I override a dependency object prototype and make it available to other modules ?
I tried to define the prototype functions in this way instead, thinking that Product prototype would be modified. Maybe not early enough :
module('App.API').run(['Product', function(Product) {
Product.prototype.getTitle = function() {
return 'Product name is ' + this.name;
};
});
It does not work : using getTitle in another module (using App.API/Product as a dependency) on a Product instance still throws a undefined is not a function error, even while Product object is correctly injected.
Actually, I just messed up the dependency definitions / orders.
I have three files :
app.js for module App (dependant on module App.API)
api.js for module App.API
product.js containing Product prototype
As stated in the question, I was doing :
// in product.js
module('App.API').run(['Product', function(Product) { ... }]);
// in app.js
var appModule = module('App', ['App.API']);
But the App.API module was defined in another file, which is a bit messed up because you never know for sure which one will load first, unless dealing with in in the js loader and loosing parallel downloads.
So I explicitly specified the modules and dependencies, at the expense of adding more dependency to declare in my app (but it works and is more stable) :
// in product.js
module('ApiProduct', ['App.API']).run(['Product', function(Product) { ... }]);
// in app.js
var appModule = module('App', ['App.API', 'ApiProduct']);
Note : In my first attempt, I defined the prototype in a new module in a .config() block, but it was not working, maybe because App.API services were not loaded yet. With .run() it works and my getTitle prototype is available everywhere I need Product provider.