AngularJS calling a controller from a directive - javascript

I have a model called 'user', 'user' has a controller called 'login' and a directive called 'userMenu', what I'm trying to achieve is that the userMenu directive uses the controller 'login' that's available in the module. Maybe I don't understand well how the modules and directives should work but I'm doing the following:
First, I define my controller like this:
angular.module('user', []).
controller('login', ['$scope', '$http', function($scope, $http){
$scope.logIn = function(){
//Do something...
}
}
Then, in my directive...
angular.module('user', []).
directive('userMenu', function(){
return {
priority: 0,
templateUrl: 'app/includes/user/menu.html',
replace: true,
restrict: 'A',
controller: 'login',
}
});
But the I get this:
Error: Argument 'login' is not a function, got undefined
Would you please guide me on the use of directives and controllers within modules?

The problem is that you are re-defining the user directive while preparing your directive. I presume that you wanted to register both a controller and a directive on the same module. If so you can register your directive like this:
angular.module('user').directive('userMenu', function(){
return {
...
}
});
Notice that there is no second argument ([]) to the call of angular.module which means that you want to retrieve an instance of already defined module instead of defining a new one.

Related

Using $watch on an AngularJS service function defined as an ES6 class

I'm using ES6 with AngularJS and am following this guide. It recommends defining services as classes.
In a service definition I'm injecting the $cookieStore service and attaching it to the class like this:
export default class Authentication {
constructor($cookieStore) {
this.$cookieStore = $cookieStore;
}
setAuthenticatedAccount(account) {
this.$cookieStore.put(JSON.stringify(account), 'authenticatedAccount');
}
isAuthenticated() {
return !!this.$cookieStore.get('authenticatedAccount');
}
}
I want to update my navbar to reflect whether or not the user is logged in, which I'm doing with $watch:
var NavbarController = function($location, $scope, Authentication) {
$scope.$watch(Authentication.isAuthenticated, () => {
$scope.isAuthenticated = Authentication.isAuthenticated();
$scope.currentUser = Authentication.getAuthenticatedAccount();
});
}
This gives the following error:
Cannot read property '$cookieStore' of undefined.
This error usually occurs when you don't inject proper dependencies in your controller.
Just make sure that $cookieStore is propertly injected in your angular controller
myApp.controller('myCtrl', ['$scope', '$cookieStore', function ($scope, $cookieStore) {
...
...
point to note
The $cookieStore service is deprecated. Please use the $cookies service instead.

How to load controllers in AngularJS dynamically?

I have many controllers in app and I don't want to load them upfront as the combined JS becomes very heavy.
So I have tried following:
In my main app.js, I have route defined as below:
$routeProvider.when('/myaccount', {
templateUrl: 'my-account.html',
title: 'My Account'
})
The my-account-controller.js has controller defined like:
angular.module('myApp',[]).controller('my-account', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
}]);
And finally in my-account.html, I have a script tag which should load the controller syncronously before using it. I am attaching that contorller with ng-controller directive:
script src='/assets/my-account-controller.js'
<div class="container" ng-controller='my-account'>
...
</div>
The problem is somehow, Angular can not identify my-account as controller and it throws following error:
Error: [ng:areq] Argument 'my-account' is not a function, got undefined
Can someone tell me what is the missing piece?
You should use UpperCamelCase in your controller name, and i don't know if you already have defined myApp, if yes, you should remove the second argument from the module method since it will reset the dependencies:
so this -> angular.module('myApp',[]) should be -> angular.module('myApp')
angular.module('myApp').controller('MyAccount', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
}]);
And in your view:
<script src='/assets/my-account-controller.js'
<div class="container" ng-controller='MyAccount'>
...
</div>
And the route:
$routeProvider.when('/myaccount', {
templateUrl: 'my-account.html',
title: 'My Account',
controller: 'MyAccount'
});
You can read more about angular best practices here
This is not the proper way to do it. You have to declare your controller in the route you are using such as
$routeProvider.when(''/myaccount'', {
templateUrl: 'my-account.html',
controller: 'my-account',
title: 'My Account'
})

How to inject dependencies to a named angular directive controller?

If I have code similar to this question on injecting another controller to a directive:
angular.module('paramApp', []);
angular.module('paramApp').controller('ParamCtrl', ['$scope', '$http', function($scope, $http) {
.
.
.
}]);
angular.module('deviceApp', ['paramApp']);
angular.module('deviceApp').directive('DeviceDirective', function () {
return {
.
.
.
controller: 'ParamCtrl'
};
});
When I minify js, the injected dependencies of $scope and $http break, how can I explicitly define the dependencies of ParamCtrl when creating DeviceDirective to prevent uncaught injector issues with bundled js?
I am very late to this question but I'll give it a shot.
The syntax is based on John Papa's Angular style guide
First you need a way to make your controller reusable. Convert it from an anonymous function to a named function and pass it to your angular app like so:
// Named controller function
ParamCtrl function($scope, $http) {
this.doStuff
}
// Bind it to your app
angular.module('paramApp').controller('ParamCtrl', ['$scope', '$http', ParamCtrl );
// While we are at it, do the same with your directive
DeviceDirective function (controlerParam) {
return {
...
controller: controlerParam
}
}
// Bind it to your app
angular.module('deviceApp', ['ParamCtrl', DeviceDirective]);
However, if you meant to pass the controller's scope to your directive refer to fiznool's post

How to test function in $scope from different controller?

I'm using Jasmine for testing my AngularJS application. I have an Authenticaiton controller, which calls a function which I define in the scope from the Application controller. So:
1. AppController
$scope.setUser = function() {}
2. AuthenticationController
$scope.setUser(User);
I am testing the AuthenticationController, and setUser() is not inside the scope of AuthenticationController. How do I inject the scope/function from the AppController?
The error message:
TypeError: $scope.setUser is not a function
Should I mock the whole function? Is the structure smelly? What's the best way to do this?
EDIT:
In the real app, AuthenticationController gets injected into my dashboard-app.
AuthenticationController:
angular
.module( 'authentication', [])
.controller('AuthenticationController', AuthenticationController);
AuthenticationController.$inject = ['$scope', '$rootScope', 'AUTH_EVENTS', 'AuthenticationService'];
Dashboard:
angular
.module('dashboard', [
'authentication'
])
.run(run);
Info: Names are changed in my question to make it easier to understand.
inject $rootScope into your controllers.
AppController :
$rootScope.setUser = function() {}
AuthenticationController
$rootScope.setUser();

Using ui-router's resolve on a controller defined with inline array annotation and fluent API

I'm used to defining angular controllers with the inline array notation, e.g.:
angular
.controller('SomeCtrl', [ '$scope', function($scope) {
...
}])
I also prefer the fluent API for angular modules, i.e. there is only a single angular.module call at the top of my file defining the controllers, and everything else is chained to this call.
I would now like to use ui-router's resolve feature, and keep the controller dependency with the controller, not in the router (see the recommendations on routing resolves).
Is there a way to continue using my preferred approaches (inline array notation, fluent API) together with the recommendations for ui-router's resolve? My current understanding is that angular.controller defines the constructor for a controller, so in order to have an additional resolve() method on the controller I would have to pass in an object that already has this method plus a constructor.
Update: adding a code sample.
Route:
function config ($routeProvider) {
$routeProvider
.when('/myroute', {
templateUrl: 'views/some.html',
controller: 'SomeCtrl'
resolve: SomeCtrl.resolve
});
}
Controller:
angular
.controller('SomeCtrl', [ '$scope', 'SomeService', function($scope, SomeService) {
var data = SomeService.data;
...
}])
Where SomeService.data() is the method call that returns the promise I need resolved. How do I define the resolve using my preferred approaches?
SomeCtrl.resolve = {
data: function(SomeService) {
return SomeService.data();
}
};
You can keep using the inline annotations. You will just need an annotation for the name of the model that you are resolving.
So if your resolve has a method named getPromise, your inline controller DI annotation should contain the string 'getPromise'.
UPDATE
So with your code examples you should make the following changes.
Add 'resolve annotations' to your resolve object:
SomeCtrl.resolve = {
'SomeService': SomeService,
someData: ['SomeService', function(SomeService) {
return SomeService.data();
}]
};
Add an annotation to the resolve method. I have changed it from 'data' in your example to 'someData' here:
angular
.controller('SomeCtrl', [ '$scope', 'someData', function($scope, someData) {
var data = someData;
...
}])
Another UPDATE
Ok. I think I understand the question now.
You do not need to define the uiRouter resolve with SomeCtrl.resolve.
You can define the resolve inline in the module config section:
function config ($routeProvider) {
$routeProvider
.when('/myroute', {
templateUrl: 'views/some.html',
controller: 'SomeCtrl'
resolve: {
'SomeService': SomeService,
someData: ['SomeService', function(SomeService) {
return SomeService.data();
}]
}
});
}
Or if you want to get fancy, you can create a resolve object with keys for each of your state and then do some thing like:
...
resolve: myResolveObj.someState
...

Categories