How to annotate dependencies in an inline controller while configuring app routes? - javascript

I'm getting following error if I don't annotate the dependencies for an inline controller function for a route (I'm using strict DI mode and all other codes are annotated so that js-minification doesn't break my code):
https://docs.angularjs.org/error/$injector/strictdi?p0=function(AuthService,%20$state
Here is the logout route code:
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider', '$urlRouterProvider) {
$stateProvider.state('logout', {
url: '/logout',
controller: function(AuthService, $state) {
AuthService.logout();
$state.go('login');
}
}
}]);
Is there any technique to declare inline annotation for the above two dependent services (AuthService, $state) of the inline controller ?
I know the bellow work-around :
.state('logout', {
url: '/logout',
controller: LogoutController
});
function LogoutController (AuthService, $state) {
AuthService.logout();
$state.go('login');
}
LogoutController.$inject = ['AuthService', '$state'];
this works but just wanted to checkout if anyone knows any smart short-cut ?

Try
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider.state('logout', {
url: '/logout',
controller: ['AuthService', '$state', function(AuthService, $state) {
AuthService.logout();
$state.go('login');
}]
}
}]);
Not sure if that will work. Usually we separate our controllers into files for ease of use, rather than writing them inline in the config.route.js file.

Just to add more details here, this is expected for inline controllers.
See https://github.com/olov/ng-annotate/issues/50.
Either not inline them or add apply controller: /* #ngInject */ function(service1){ ... }.
The /* #ngInject */ tells ngannotate to apply annotation here.

Related

Config issues with Factories in app.js & stateProvider

I am trying to set up a factory in my app.js of my ionic app. I have the following code that should work however my state provider is throwing me off and Im not sure where my states should be. I was suggested to use this app.js code to get my factory working however it leaves no room for my routes. I have tried different variations of the following with no lck. Thnk you in advance.
(function() {
'use strict';
angular
.module('starter', []) // In your real application you should put your dependencies.. ['ng...']
//.run(runFunction) // Just commenting to make it WORK HERE
.controller('MainCtrl', MainCtrl)
.factory('myService', myService);
// Just commenting to make it work here..
/*function runFunction($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.Keyboard.disableScroll(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleDefault();
}
})
}*/
MainCtrl.$inject = ['$scope', 'myService'];
function MainCtrl($scope, myService) {
function getSuccess(response) {
$scope.items = response.data;
}
function getError(response) {
console.log('Of course an error since the url is a invalid, but it should work with a valid url!');
}
myService.getAll()
.then(getSuccess)
.catch(getError);
}
function myService($http) {
var factory = {
getAll: getAll
};
return factory;
function getAll() {
return $http.get("url"); // it -> should be your URL
}
}
})();
// dont know where the config goes?
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
// setup an abstract state for the tabs directive
.state('tab', {
url: '/tab',
abstract: true,
templateUrl: 'templates/tabs.html'
})
.state('login', {
url: "/login",
cache: false,
controller: 'AccountCtrl',
templateUrl: "templates/login.html"
})
.state('list', {
url: "/list",
cache: false,
controller: 'ListCtrl',
templateUrl: "templates/list.html"
})
$urlRouterProvider.otherwise('/tab/dash');
});
I don't see you have included ui.router in your app. To work with $stateProvider you should place ui.router module in your depenencies, and should include angular-ui-router.js files in your project.
angular
.module('starter', ['ui.router']) // In your real application you should put your dependencies.. ['ng...']
//.run(runFunction) // Just commenting to make it WORK HERE
.controller('MainCtrl', MainCtrl)
.factory('myService', myService);
ui.router is the module that provides you $stateProvider.
After this .config can go along with module like .controller and .factory. Config is also defined on you app:
angular.module('starter', ['ui.router'])
.config(configFn) //configFn is the function you have in your .config
.controller('MainCtrl', MainCtrl)
.factory('myService', myService);

AngularJS and RequireJS add controller to view

I have a problem with adding a Angular controller to my HTML view. The angular way of doing this is: ng-controller="<controller>". But because I am using RequireJs I have to do this in a different way. I have to add a sub page to every controller and view:
define(['app', 'login/LoginController'], function (app, LoginController) {
app.config(function ($routeProvider, $locationProvider) {
$routeProvider.when('/', {
templateUrl: "modules/" + 'login/login.html',
controller: LoginController
});
});
app.controller('LoginController', LoginController);
});
This way I can define my where my controller is and where my view is.
Problem
Now I have a header.html in which I want to include a menu.html. this can be done via: ng-include="'modules/menu/menu.html'". This works fine. But how can I add a controller to this menu.html?
I have tried: ng-controller="MenuController" but then I get the error: 'Error: [ng:areq] Argument 'MenuController' is not a function, got undefined'. So I do not know how I should add a controller to my menu.html with RequireJS.
MenuController
my MenuController looks as follows:
define(['$'], function ($) {
'use strict';
var MenuController = function ($location, menu, $scope) {
$scope.info="testing123";
};
return MenuController;
});
Anyone knows how I should do this?
You can for example use multiple views in the same controllerwith $stateProvider:
app.config(function ($stateProvider, $locationProvider) {
$stateProvider
.state('login', {
url: '/',
views: {
'menu': {
templateUrl: 'modules/menu/menu.html',
controller: MenuController
},
'login': {
templateUrl: 'modules/login/login.html'
controller: LoginController
}
}
});
});
Then in your template to call them you just need to do something like:
<div ui-view="menu"></div>
<div ui-view="login"></div>
You can see more info on github ui-router.

AngularJS app initiating twice (tried the usual solutions..)

SOLVED:
Resolution here:
https://stackoverflow.com/a/29662815/4004456
Batarang Chrome plugin was firing all ng-click actions twice. Nothing wrong with my code.. Batarang owes me an afternoon...
NOTE: I have attempted all fixes in: Combating AngularJS executing controller twice
EDIT: something puzzling:
It seems all controllers are initialised twice, as it doesn't matter which view calls which function, it its fired twice, this is what lead me to believe it is a problem within the app.js file..
EDIT: resolutions I've attempted:
I've tried removing bower dependencies one by one to rule out a dependency causing it.
I've scoured my code for double controller init (both in index.html and then again in a div / bootstrap init. )
I've removed my custom directives one by one to ensure the "restrict:" / controller duplication isn't occurring.
I've removed all ng-if statements from the application.
Scoured all html for a duplicate ng-view / ui-view etc.
Tried all combinations of routes + links having and not having trailing slashes.
My issue however is nearly identical to the above question, my controllers are firing functions twice on button click.
I've tried to set up a plunkr but the code is too complex for me to get it working properly.. I've stripped any bloat out of the below code.
index.html
<section ng-show="!stateIsLoading"> <div ng-view></div></section>
<section class="colorful" ng-hide="!stateIsLoading">
<three-bounce-spinner></three-bounce-spinner>
</section>
app.js
'use strict';
/**
* #ngdoc overview
* #name smcmsApp
* #description
* # smcmsApp
*
* Main module of the application.
*/
angular
.module('smcmsApp', [
'ngAnimate',
'ngAria',
'ngCookies',
'ngMessages',
'ngResource',
'ngRoute',
'ngSanitize',
'ngTouch',
'ui.bootstrap',
'angular-spinkit',
'restangular'
])
.config(function ($routeProvider, RestangularProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.when('/about', {
templateUrl: 'views/about.html',
controller: 'AboutCtrl'
})
.when('/sales', {
templateUrl: 'views/sales.html',
controller: 'SalesCtrl',
resolve: {
mailers: function (API) {
return API.mailers.getList();
},
totals: function (Restangular) {
return Restangular.all('profit').customGET();
}
}
})
.when('/design', {
templateUrl: 'views/design.html',
controller: 'DesignCtrl',
resolve: {
design: function (API) {
return API.design.getList();
}
}
})
.otherwise({
redirectTo: '/'
});
RestangularProvider.setBaseUrl('http://localhost/SMCMS_Angular/api/Slim/');
})
.run(function ($rootScope, $log) {
$rootScope.stateIsLoading = false;
$rootScope.$on('$routeChangeStart', function () {
console.log("route change start");
$rootScope.stateIsLoading = true;
});
$rootScope.$on('$routeChangeSuccess', function () {
console.log("route change success");
$rootScope.stateIsLoading = false;
});
$rootScope.$on('$routeChangeError', function () {
//catch error
console.log("Route changing animation error");
});
});
DesignCtrl.js
angular.module('smcmsApp')
.controller('DesignCtrl', function ($scope, $log, design, Notifications, Data) {
/* ------------------------- Order Interactions --------------- */
$scope.saveOrder = function (order) {
Notifications.addAlert('error', 'called showModal() from design.js');
$log.debug("called showModal() from design.js");
};
});
views/design.html
<label ng-click="saveOrder(order)" class="btn btn-default">Save Details <span class="glyphicon glyphicon-floppy-open"></span>
I believe the problem is in the way the app handles during initialization. If you navigate to your application http://example.com/ then your url will be an empty string and trigger the route change. After it fails to resolve it will then hit the otherwise and cause the double route change/initialization.
Basically if you fire up the route yourself upon load you shouldn't see the double change. You can just inject the $location service and direct there yourself inside of the run assuming you want to start at any one given point. (see the plunk: http://plnkr.co/edit/lTOSYX?p=preview )
.run(function ($rootScope, $log, $location) {
$location.path('/');//Add this
// continue code here
});
If your controllers are double loading then you probably have other complications such as the double controller declaration/execution issue mentioned in the note (Combating AngularJS executing controller twice).

Using ocLazyLoad to lazy load a controller with $stateProvider

I'm having issues using oclazyload with $stateProvider.
I have specified that the controller .js should be loaded in the router config, and it does,' but it's not available to use as an ng-controller attribute in the file loaded in templateURL.
ui-route config:
core
.run(
[ '$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]
)
.config(
[ '$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
console.info('Routing ...');
$urlRouterProvider
.otherwise('/app/dashboard');
$stateProvider
.state('app', {
abstract: true,
url: '/app',
templateUrl: 'templates/app.html',
})
.state('app.orders', {
abstract: true,
url: '/orders',
templateUrl: 'templates/orders/orders.html',
})
.state('app.orders.index', {
url: '/index',
templateUrl: 'templates/orders/index.html',
resolve: {
deps: ['$ocLazyLoad',
function( $ocLazyLoad ){
console.info('Path ot order controller in route config',Momento.paths.js + 'controllers/orders/index.js');
return $ocLazyLoad.load([
Momento.paths.js + 'controllers/orders/index.js'
])
}
]
}
})
}
]
)
;
And my templateURL file starts:
<div class="" id="" ng-controller="OrdersIndexController">...</div>
But when it loads, console throws the error:
<info>orders/index controller loaded controllers/orders/index.js:3
<info>Now I've finished loading the controller/order/index.js config/ui-router.js:69
<info>orders template loaded VM30437:1 (<-- this is the app.orders abstract template with ui-view directive ready for app.orders.index view)
<error>Error: [ng:areq] Argument 'OrdersIndexController' is not a function, got undefined
... <trace>
So the file is loaded correctly by lazyload, confirmed by console output above and network tab in developer tools, but it's not available in the templateURL to use as controller? Does it need to be aliased either in router config using controller:'' key or in template? Does it need to be specifically attached to the (only) module in this app?
What am I missing?
PS: confirming that the name of the controller is in fact OrdersIndexController:
core
.controller('OrdersIndexController', [
'Model', '$scope', '$window',
function( Model, $scope, $window){
console.info("OrdersIndexController fired");
}
]);
You have to register your controller with
angular.module("myApp").controller
Working
angular.module("myApp").controller('HomePageController', ['$scope', function ($scope) {
console.log("HomePageController loaded");
}]);
Not working
var myApp = angular.module("myApp")
myApp.controller('HomePageController', ['$scope', function ($scope) {
console.log("HomePageController loaded");
}]);
Inside the function function($ocLazyLoad){} you must to declare the name of module that contains the controller and the name of file "to lazy load"
function( $ocLazyLoad ){
return $ocLazyLoad.load(
{
name: 'module.name',
files: ['files']
}
);
}
If you use the current documented way for ocLazyLoad 1.0 -> With your router
...
resolve: { // Any property in resolve should return a promise and is executed before the view is loaded
loadMyCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
// you can lazy load files for an existing module
return $ocLazyLoad.load('js/AppCtrl.js');
}]
}
then in js/AppCtrl.js
You have something like this:
angular.module("myApp").controller('DynamicNew1Ctrl', ['$scope', function($scope) {
$scope.name = "Scoped variable";
console.log("Controller Initialized");
}]);
Note that with angular.module("myApp") you are attaching the new controller to an existing module, in this case the mainApp, so any of new dynamic controllers can use the app dependencies.
but you can define a new module an inject your dependencies, as described here, the later is used commonly when you estructure your app with a plugin architecture and you want to isolate the dynamic modules so they only have access to some especific dependencies

AngularJS dynamic routing

I currently have an AngularJS application with routing built in.
It works and everything is ok.
My app.js file looks like this:
angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', { templateUrl: '/pages/home.html', controller: HomeController });
$routeProvider.when('/about', { templateUrl: '/pages/about.html', controller: AboutController });
$routeProvider.when('/privacy', { templateUrl: '/pages/privacy.html', controller: AboutController });
$routeProvider.when('/terms', { templateUrl: '/pages/terms.html', controller: AboutController });
$routeProvider.otherwise({ redirectTo: '/' });
}]);
My app has a CMS built in where you can copy and add new html files within the /pages directory.
I would like to still go through the routing provider though even for the new dynamically added files.
In an ideal world the routing pattern would be:
$routeProvider.when('/pagename', { templateUrl: '/pages/pagename.html', controller: CMSController });
So if my new page name was "contact.html" I would like angular to pick up "/contact" and redirect to "/pages/contact.html".
Is this even possible?! and if so how?!
Update
I now have this in my routing config:
$routeProvider.when('/page/:name', { templateUrl: '/pages/home.html', controller: CMSController })
and in my CMSController:
function CMSController($scope, $route, $routeParams) {
$route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
alert($route.current.templateUrl);
}
CMSController.$inject = ['$scope', '$route', '$routeParams'];
This sets the current templateUrl to the right value.
However I would now like to change the ng-view with the new templateUrl value. How is this accomplished?
angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/page/:name*', {
templateUrl: function(urlattr){
return '/pages/' + urlattr.name + '.html';
},
controller: 'CMSController'
});
}
]);
Adding * let you work with multiple levels of directories dynamically.
Example: /page/cars/selling/list will be catch on this provider
From the docs (1.3.0):
"If templateUrl is a function, it will be called with the following
parameters:
{Array.} - route parameters extracted from the current
$location.path() by applying the current route"
Also
when(path, route) : Method
path can contain named groups starting with a colon and ending with a star: e.g.:name*. All characters are eagerly stored in $routeParams under the given name when the route matches.
Ok solved it.
Added the solution to GitHub - http://gregorypratt.github.com/AngularDynamicRouting
In my app.js routing config:
$routeProvider.when('/pages/:name', {
templateUrl: '/pages/home.html',
controller: CMSController
});
Then in my CMS controller:
function CMSController($scope, $route, $routeParams) {
$route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
$.get($route.current.templateUrl, function (data) {
$scope.$apply(function () {
$('#views').html($compile(data)($scope));
});
});
...
}
CMSController.$inject = ['$scope', '$route', '$routeParams'];
With #views being my <div id="views" ng-view></div>
So now it works with standard routing and dynamic routing.
To test it I copied about.html called it portfolio.html, changed some of it's contents and entered /#/pages/portfolio into my browser and hey presto portfolio.html was displayed....
Updated
Added $apply and $compile to the html so that dynamic content can be injected.
I think the easiest way to do such thing is to resolve the routes later, you could ask the routes via json, for example. Check out that I make a factory out of the $routeProvider during config phase, via $provide, so I can keep using the $routeProvider object in the run phase, and even in controllers.
'use strict';
angular.module('myapp', []).config(function($provide, $routeProvider) {
$provide.factory('$routeProvider', function () {
return $routeProvider;
});
}).run(function($routeProvider, $http) {
$routeProvider.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
}).otherwise({
redirectTo: '/'
});
$http.get('/dynamic-routes.json').success(function(data) {
$routeProvider.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
});
// you might need to call $route.reload() if the route changed
$route.reload();
});
});
In the $routeProvider URI patters, you can specify variable parameters, like so: $routeProvider.when('/page/:pageNumber' ... , and access it in your controller via $routeParams.
There is a good example at the end of the $route page: http://docs.angularjs.org/api/ng.$route
EDIT (for the edited question):
The routing system is unfortunately very limited - there is a lot of discussion on this topic, and some solutions have been proposed, namely via creating multiple named views, etc.. But right now, the ngView directive serves only ONE view per route, on a one-to-one basis. You can go about this in multiple ways - the simpler one would be to use the view's template as a loader, with a <ng-include src="myTemplateUrl"></ng-include> tag in it ($scope.myTemplateUrl would be created in the controller).
I use a more complex (but cleaner, for larger and more complicated problems) solution, basically skipping the $route service altogether, that is detailed here:
http://www.bennadel.com/blog/2420-Mapping-AngularJS-Routes-Onto-URL-Parameters-And-Client-Side-Events.htm
Not sure why this works but dynamic (or wildcard if you prefer) routes are possible in angular 1.2.0-rc.2...
http://code.angularjs.org/1.2.0-rc.2/angular.min.js
http://code.angularjs.org/1.2.0-rc.2/angular-route.min.js
angular.module('yadda', [
'ngRoute'
]).
config(function ($routeProvider, $locationProvider) {
$routeProvider.
when('/:a', {
template: '<div ng-include="templateUrl">Loading...</div>',
controller: 'DynamicController'
}).
controller('DynamicController', function ($scope, $routeParams) {
console.log($routeParams);
$scope.templateUrl = 'partials/' + $routeParams.a;
}).
example.com/foo -> loads "foo" partial
example.com/bar-> loads "bar" partial
No need for any adjustments in the ng-view. The '/:a' case is the only variable I have found that will acheive this.. '/:foo' does not work unless your partials are all foo1, foo2, etc... '/:a' works with any partial name.
All values fire the dynamic controller - so there is no "otherwise" but, I think it is what you're looking for in a dynamic or wildcard routing scenario..
As of AngularJS 1.1.3, you can now do exactly what you want using the new catch-all parameter.
https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5
From the commit:
This allows routeProvider to accept parameters that matches
substrings even when they contain slashes if they are prefixed
with an asterisk instead of a colon.
For example, routes like edit/color/:color/largecode/*largecode
will match with something like this
http://appdomain.com/edit/color/brown/largecode/code/with/slashs.
I have tested it out myself (using 1.1.5) and it works great. Just keep in mind that each new URL will reload your controller, so to keep any kind of state, you may need to use a custom service.
Here is another solution that works good.
(function() {
'use strict';
angular.module('cms').config(route);
route.$inject = ['$routeProvider'];
function route($routeProvider) {
$routeProvider
.when('/:section', {
templateUrl: buildPath
})
.when('/:section/:page', {
templateUrl: buildPath
})
.when('/:section/:page/:task', {
templateUrl: buildPath
});
}
function buildPath(path) {
var layout = 'layout';
angular.forEach(path, function(value) {
value = value.charAt(0).toUpperCase() + value.substring(1);
layout += value;
});
layout += '.tpl';
return 'client/app/layouts/' + layout;
}
})();

Categories