Over at angular-logger, we are trying to enhance $log, but we would like to work in the name of the angular's module and component without changes. To do so we need to get the AngularJS context at runtime, that is, the module name and controller, service or directive names.
app.module("SampleModule").
controller("ControllerOne", function ($log) {
$log.debug("I am ready!")
}).
controller("ControllerTwo", function ($log) {
$log.debug("I am ready!")
});
Default $log output:
> I am ready!
> I am ready!
If we can obtain the module and controller name at runtime, then using the enhanced $log we can get richer output:
> SampleModule.ControllerOne: I am ready!
> SampleModule.ControllerTwo: I am ready!
The best option will be to get it without changes to the controller's code. Perhaps there is way to get some meta info about the entities that received the $log dependency injection?
Any suggestions?
There was a similar question that I found on SO, but the solution works only when using controller as syntax.
// a simple route with controller as syntax
$routeProvider.when(
'/myRoute',
{
templateUrl: '/myRoute',
controller: 'ControllerOne as vm'
}
);
// controller
app.controller("ControllerOne", ['$log', function ControllerOne($log) {
var vm = this;
$log.log(vm.constructor.name);
}]);
Related
I was implementing angular translate in my project and everything works fine, but when I've moved my $translateProviderfrom my config block to my controller.js, I'm getting this error:
Unknown provider: $translateProviderProvider <- $translateProvider <-
myController
But every module seems to be correctly referenced, am I missing something here? or maybe this translations can't work inside of a controller?
controller.js
angular.module('myapp.controller', ['pascalprecht.translate'])
.controller('myController',
['$translateProvider',
function ($translateProvider) {
function init() {
$translateProvider.useUrlLoader('myweb.com/api/lang', {
queryParameter : 'en_US'
});
$translateProvider.preferredLanguage('en_US');
}
init();
}]);
UPDATE
Now I know that $translateProvider are not available to use it in controller class.
What I'm trying to accomplish:
I don't want to load all the traductions files from the rest, because there are many components that the user never see, so if I go to the page that contains ng-controller="myController", the init() function should call to the rest and get the traductions only for the current component. I've found this on the documentation:
angular.module('contact')
.controller('ContactCtrl', function ($scope, $translatePartialLoader) {
$translatePartialLoader.addPart('contact');
});
But how can I:
Specify the URL for my REST?
Specify the parameter 'contact' in the URL to my REST know that it should retrieve me the traductions only for the contact component.
When injecting a provider in a controller, you don't need the Provider suffix.
just inject it as
.controller('myController', ['$translate', function ($translate) { ... }])
I have a controller that needs a thing provided by a route resolve function:
$routeProvider.when('/some/url', {
controller: MyController,
controllerAs: 'myCtrl',
resolve: {
theAnswer: ['deepThought', function(deepThought) {
return deepThought.computeTheAnswerAndReturnAPromise();
}]
}
});
var MyController = ['$route', function($route) {
this.theAnswer = $route.current.theAnswer;
}];
Now I want to do an end-to-end test, checking that the route matches and that parameters are propagated properly:
// ...set up the routes...
$location.path('/some/url');
$rootScope.$digest();
var ctrl = ???;
expect(ctrl.aThing).toBe(42);
In the non-test setup, I can put in a log statement and see that the controller is being created successfully and gets the correct data injected. The only problem is: how to get hold of the controller in the test?
There is $route.current.controller, but it contains the controller's constructor function and not the controller instance.
The documentation promises a $route.current.locals.$scope, from which I could get myCtrl, but the $scope property doesn't actually exist unless we also use ngView (it gets set here).
The controller isn't registered with any module, so I can't use $provide to intercept its creation and stash the controller somewhere.
Found it, thanks to #PSL's comment. The thing that actually constructs the controller is the ngView link function. We can fake that easily enough:
var ctrl = $controller(MyController, $route.current.locals);
I'm trying to create a custom filter, but when I try to inject it into my controller, I get an 'Unknown provider' error. I have checked and double checked all the references, but I can't see what's wrong.
I know that the file is referenced in my index.html correctly, it is loaded and can be found by the inspector. This is the code I have:
In my app.js:
angular.module('equiclass', ['equiclass.controllers',
'equiclass.services',
'ngRoute'])
.config(function ($routeProvider) {
$routeProvider
.when('/courses', {
templateUrl: 'views/courses.html',
controller: 'CourseCtrl'
// And some other stuff with routes
});
angular.module('equiclass.controllers', ['equiclass.services', 'equiclass.filters']);
angular.module('equiclass.services', []);
angular.module('equiclass.filters', []);
My filter:
angular.module('equiclass.filters')
.filter('testFilter', function() {
return function(input) {
return undefined;
};
});
And the controller:
angular.module('equiclass.controllers')
.controller('CourseCtrl', function ($scope, testFilter) {
});
Of course this is quite simplified, but it just doesn't work, and I can't see why. I have made several services and they all work and play along nicely.
If you want to use filter inside a controller you have to inject $filter attribute to your controller and can access it like
$filter('filtername');
You can use like
function myCtrl($scope, $filter)
{
$filter('filtername')(arg1,arg2);
}
You don't need to inject the filter itself.
This code...
angular.module('equiclass.controllers')
.controller('CourseCtrl', function ($scope, testFilter) {
});
Should be
angular.module('equiclass.controllers')
.controller('CourseCtrl', function ($scope) {
});
And inside CourseCtrl you should use your filter as you normally do.
Injecting the module 'equiclass.filters' into your module 'equiclass.controllers' is enough.
I had a similar issue and made a post about it on my blog.
--Edit
As n00dl3 mentions below the tricky part is how the auto-naming convention works in Angular. If you name your filter specialNumberFilter then when you inject it you need to refer to it as specialNumberFilterFilter. This allows you to use the filter as a function, which is what it is.
// In a controller
vm.data = specialNumberFilterFilter(vm.data, 'a');
But I believe you can also use the filter without injecting it into a controller if it is used in a string expression that is being evaluated by, say, a watch because this would be the same as the scenario when you are using it in a template.
// Inside a watch - no controller injection required
`$scope.$watch('vm.data | specialNumberFilter', function(new, old) { ... })`
According to Angular's documentation :
if you want to use your filter in a template
then you just need to inject it in your module and then use it like this {{ expression | filter }} or {{ expression | filter:argument1:argument2:... }} .
doc
if you want to use your filter in a controller, directive, and stuffs :
inject a dependency with the name <filterName>Filter, like this :
controller('MyController', ['filterFilter', function(filterFilter) {}]);
doc
so for this particular case :
angular.module('equiclass.controllers')
.controller('CourseCtrl', function ($scope, testFilterFilter) {
});
you didn't mention if it's in production or on a local server, but just in case you are minifying your files, try this:
angular.module('equiclass.controllers')
.controller('CourseCtrl', ['$scope', 'testFilter', function ($scope, testFilter) {
}]);
I have this piece of layout html:
<body ng-controller="MainController">
<div id="terminal"></div>
<div ng-view></div>
<!-- including scripts -->
</body>
Now apparently, when I try to use $routeParams in MainController, it's always empty. It's important to note that MainController is supposed to be in effect in every possible route; therefore I'm not defining it in my app.js. I mean, I'm not defining it here:
$routeProvider.when("/view1", {
templateUrl: "partials/partial1.html"
controller: "MyCtrl1"
})
$routeProvider.when("/view2", {
templateUrl: "partials/partial2.html"
controller: "MyCtrl2"
})
// I'm not defining MainController here!!
In fact, I think my problem is perfectly the same as this one: https://groups.google.com/forum/#!topic/angular/ib2wHQozeNE
However, I still don't get how to get route parameters in my main controller...
EDIT:
What I meant was that I'm not associating my MainController with any specific route. It's defined; and it's the parent controller of all other controllers. What I'm trying to know is that when you go to a URL like /whatever, which is matched by a route like /:whatever, why is it that only the sub-controller is able to access the route parameter, whereas the main controller is not? How do I get the :whatever route parameter in my main controller?
The $routeParams service is populated asynchronously. This means it will typically appear empty when first used in a controller.
To be notified when $routeParams has been populated, subscribe to the $routeChangeSuccess event on the $scope. (If you're in a component that doesn't have access to a child $scope, e.g., a service or a factory, you can inject and use $rootScope instead.)
module.controller('FooCtrl', function($scope, $routeParams) {
$scope.$on('$routeChangeSuccess', function() {
// $routeParams should be populated here
});
);
Controllers used by a route, or within a template included by a route, will have immediate access to the fully-populated $routeParams because ng-view waits for the $routeChangeSuccess event before continuing. (It has to wait, since it needs the route information in order to decide which template/controller to even load.)
If you know your controller will be used inside of ng-view, you won't need to wait for the routing event. If you know your controller will not, you will. If you're not sure, you'll have to explicitly allow for both possibilities. Subscribing to $routeChangeSuccess will not be enough; you will only see the event if $routeParams wasn't already populated:
module.controller('FooCtrl', function($scope, $routeParams) {
// $routeParams will already be populated
// here if this controller is used within ng-view
$scope.$on('$routeChangeSuccess', function() {
// $routeParams will be populated here if
// this controller is used outside ng-view
});
);
As an alternate to the $timeout that plong0 mentioned...
You can also inject the $route service which will show your params immediately.
angular.module('MyModule')
.controller('MainCtrl', function ($scope, $route) {
console.log('routeParams:'+JSON.stringify($route.current.params));
});
I have the same problem.
What I discovered is that, $routeParams take some time to load in the Main Controller, it probably initiate the Main Controller first and then set $routeParams at the Child Controller. I did a workaround for it creating a method in the Main Controller $scope and pass $routeParams through it in the Child Controllers:
angular.module('MyModule')
.controller('MainController', ["$scope", function ($scope) {
$scope.parentMethod = function($routeParams) {
//do stuff
}
}]);
angular.module('MyModule')
.controller('MyCtrl1', ["$scope", function ($scope) {
$scope.parentMethod($routeParams);
}]);
angular.module('MyModule')
.controller('MyCtrl2', ["$scope", function ($scope) {
$scope.parentMethod($routeParams);
}]);
had the same problem, and building off what Andre mentioned in his answer about $routeParams taking a moment to load in the main controller, I just put it in a timeout inside my MainCtrl.
angular.module('MyModule')
.controller('MainCtrl', function ($scope, $routeParams, $timeout) {
$timeout(function(){
// do stuff with $routeParams
console.log('routeParams:'+JSON.stringify($routeParams));
}, 20);
});
20ms delay to use $routeParams is not even noticeable, and less than that seems to have inconsistent results.
More specifically about my problem, I was confused because I had the exact same setup working with a different project structure (yo cg-angular) and when I rebuilt my project (yo angular-fullstack) I started experiencing the problem.
You have at least two problems here:
with $routeParams you get the route parameters, which you didn't define
the file where you define a main controller doesn't really matter. the important thing is in which module/function
The parameters have to be defined with the $routeProvider with the syntax :paramName:
$routeProvider.when("/view2/name1/:a/name2/:b"
and then you can retrieve them with $routeParams.paramName.
You can also use the query parameters, like index.html?k1=v1&k2=v2.
app.js is the file where you'd normally define dependencies and configuration (that's why you'd have there the app module .config block) and it contains the application module:
var myapp = angular.module(...);
This module can have other modules as dependencies, like directives or services, or a module per feature.
A simple approach is to have a module to encapsulate controllers. An approach closer to your original code is putting at least one controller in the main module:
myapp.controller('MainCtrl', function ($scope) {...}
Maybe you defined the controller as a global function? function MainCtrl() {...}? This pollutes the global namespace. avoid it.
Defining your controller in the main module will not make it "to take effect in all routes". This has to be defined with $routeProvider or make the controller of each route "inherit" from the main controller. This way, the controller of each route is instantiated after the route has changed, whereas the main controller is instantiated only once, when the line ng-controller="MainCtrl" is reached (which happens only once, during application startup)
You can simply pass values of $routeParams defined into your controller into the $rootScope
.controller('MainCtrl', function ($scope, $routeParams, MainFactory, $rootScope) {
$scope.contents = MainFactory.getThing($routeParams.id);
$rootScope.total = MainFactory.getMax(); // Send total to the rootScope
}
and inject $rootScope in your IndexCtrl (related to the index.html)
.controller('IndexCtrl', function($scope, $rootScope){
// Some code
});
EDIT: I have managed to get my unit tests running - I moved the code containing the services to a different file and a different module, made this new module a requirement for fooBar module, and then before each "it" block is called, introduced the code beforeEach(module(<new_service_module_name)). However, my application still won't run. No errors in console either. This is the only issue that remains - that when I use global scope for controllers definition, the application works, but when I use angular.module.controller - it does not.
I have a file app.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'partials/form-view.html',
controller: FormViewCtrl
}).
when('/resultDisplay', {
templateUrl: 'partials/table-view.html',
controller: TableViewCtrl
}).
otherwise({redirectTo: '/'});
}]);
app.service('searchResults', function() {
var results = {};
return {
getResults: function() {
return results;
},
setResults: function(resultData) {
results = resultData;
}
};
});
I have another file controllers.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.controller('FormViewCtrl', ['$scope', '$location', '$http', 'searchResults',
function ($scope, $location, $http, searchResults) {
//Controller code
}]);
searchResults is a service that I created that simply has getter and setter methods. The controller above uses the setter method, hence the service is injected into it.
As a result, my application just does not run! If I change the controller code to be global like this:
function ($scope, $location, $http, searchResults) {
//Controller code
}
then the application works!
Also, if I use the global scope, then the following unit test case works:
'use strict';
/*jasmine specs for controllers go here*/
describe('Foo Bar', function() {
describe('FormViewCtrl', function() {
var scope, ctrl;
beforeEach(module('fooBar'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('FormViewCtrl', {$scope: scope});
}));
}
//"it" blocks
}
If I revert to the module scope, I get the error -
Error: Unknown provider: searchResultsProvider <- searchResults
Thus, by using global scope my application and unit tests run but by using app.controller, they seem to break.
Another point that I have noted is that if I include the controller code in app.js instead of controllers.js, then the application and unit tests start working again. But I cannot include them in the same file - how do I get this to run in the angular scope without breaking the application and unit tests?
You don't need to go that route. You can use the modular approach, but the issue is with your second parameter.
In your app.js you have this:
var app = angular.module('fooBar', []);
Then in your controller, you have this:
var app = angular.module('fooBar', []);
What you're doing there is defining the module twice. If you're simply trying to attach to the app module, you cannot pass in the second parameter (the empty array: []), as this creates a brand new module, overwriting the first.
Here is how I do it (based on this article for architecting large AngularJS apps.
app.js:
angular.module('fooBar',['fooBar.controllers', 'fooBar.services']);
angular.module('fooBar.controllers',[]);
angular.module('fooBar.services', []);
...etc
controllers.js
angular.module('foobar.controllers') // notice the lack of second parameter
.controller('FormViewCtrl', function($scope) {
//controller stuffs
});
Or, for very large projects, the recommendation is NOT to group your top-level modules by type (directives, filters, services, controllers), but instead by features (including all of your partials... the reason for this is total modularity - you can create a new module, with the same name, new partials & code, drop it in to your project as a replacement, and it will simiply work), e.g.
app.js
angular.module('fooBar',['fooBar.formView', 'fooBar.otherView']);
angular.module('fooBar.formView',[]);
angular.module('fooBar.otherView', []);
...etc
and then in a formView folder hanging off web root, you THEN separate out your files based on type, such as:
formView.directives
formView.controllers
formView.services
formView.filters
And then, in each of those files, you open with:
angular.module('formView')
.controller('formViewCtrl', function($scope) {
angular.module('formView')
.factory('Service', function() {
etc etc
HTH
Ok - I finally figured it out. Basically, if you wish to use the module scope and not the global scope, then we need to do the following (if you have a setup like app.js and controllers.js):
In app.js, define the module scope:
var myApp = angular.module(<module_name>, [<dependencies>]);
In controllers.js, do not define myApp again - instead, use it directly like:
myApp.controller(..);
That did the trick - my application and unit tests are now working correctly!
It is best practice to have only one global variable, your app and attach all the needed module functionality to that so your app is initiated with
var app = angular.module('app',[ /* Dependencies */ ]);
in your controller.js you have initiated it again into a new variable, losing all the services and config you had attached to it before, only initiate your app variable once, doing it again is making you lose the service you attached to it
and then to add a service (Factory version)
app.factory('NewLogic',[ /* Dependencies */ , function( /* Dependencies */ ) {
return {
function1: function(){
/* function1 code */
}
}
}]);
for a controller
app.controller('NewController',[ '$scope' /* Dependencies */ , function( $scope /* Dependencies */ ) {
$scope.function1 = function(){
/* function1 code */
};
}
}]);
and for directives and config is similar too where you create your one app module and attach all the needed controllers, directives and services to it but all contained within the parent app module variable.
I have read time and time again that for javascript it is best practice to only ever have one global variable so angularjs architecture really fills that requirement nicely,
Oh and the array wrapper for dependencies is not actually needed but will create a mess of global variables and break app completely if you want to minify your JS so good idea to always stick to the best practice and not do work arounds to get thing to work
In my case, I've defined a new provider, say, xyz
angular.module('test')
.provider('xyz', function () {
....
});
When you were to config the above provider, you've inject it with 'Provider' string appended.
Ex:
angular.module('App', ['test'])
.config(function (xyzProvider) {
// do something with xyzProvider....
});
If you inject the above provider without the 'Provider' string, you'll get the similar error in OP.