That's a bad title. I'm aware. That's because I'm not entirely sure how to ask this question. I've got two essentially identical classes, behaving just a little bit differently, a corresponding controllerAs: 'vm' in the state config for each of them also behaving differently and a perplexing "this method can be static" warning from Webstorm in one of them and not the other.
index.html:
<div ui-view="main"></div>
<div ui-view="portfolio"></div>
app.js
// this file contains only the module setter with all the
// dependencies, as well as the $stateProvider config and
// any actions that occur when the app runs
'use strict';
angular.module('necApp', ['dep1', 'dep2', 'etc'])
.config(['$urlRouterProvider', '$locationProvider', '$animateProvider', Config])
.run(['$rootScope', '$state', Run]);
function Config(params) { /* do stuff */ }
function Run(params) { /* do stuff */ }
main.js
use strict';
import { MainController } from './main.controller';
angular.module('myApp')
.controller('MainController', MainController)
.config(['$stateProvider', Config]);
function Config($stateProvider)
{
$stateProvider
.state('main',
{
url: '/',
views: {
'main': {
templateUrl: 'app/main/main.html',
// OF NOTE: I have access to the controller as 'vm' in the view
// regardless of whether I include the next two lines
controller: MainController,
controllerAs: 'vm'
}
}
});
}
main.html
<!--
again, this expression is evaluated whether or not I include
the `controller` and `controllerAs` properties in my $state config
-->
<h1> {{ vm.result }} </h1>
main.controller.js
// OF NOTE: when I DO include the `controller` property in the $state config
// for the main route, this controller is registered and instantiated twice
'use strict';
export class MainController
{
constructor($http)
{
/* #ngInject */
angular.extend(this, {
$http: $http,
result: ''
});
this.Init();
}
Init()
{
this.$http.get('/endpoint').then(res =>
{
this.result = res.data;
});
}
}
portfolio.js
use strict';
import { PortfolioController } from './portfolio.controller';
angular.module('necApp')
.controller('PortfolioController', PortfolioController)
.config(['$stateProvider', Config]);
function Config($stateProvider)
{
$stateProvider
.state('portfolio',
{
url: '/portfolio',
views: {
'portfolio': {
templateUrl: 'app/portfolio/views/portfolio.html',
// OF NOTE: I do NOT have access to the controller as 'vm'
// in the view in this case without the next two lines
controller: PortfolioController,
controllerAs: 'vm'
}
}
});
}
portfolio.html
<!-- this is NOT evaluated without the `controller` and `controllerAs` properties in the $state config -->
<h1> {{ someExpression }} </h1>
portfolio.controller.js
'use strict';
export class PortfolioController
{
constructor()
{
angular.extend(this, {
someExpression: 'Testing . . .'
});
this.Init();
}
// OF NOTE: Webstorm warns me that this method can be static, even though
// 1) that breaks it and 2) I do NOT get that warning in MainController
Init()
{
// works as expected
console.log('initializing PortfolioController.');
}
}
As always, I very much look forward to your thoughts and comments.
Okay, before anybody else wastes their own valuable time with this, it turns out I'm just dumb. Or forgetful. I found a forgotten and unused directive I wrote that was for some reason using the MainController as 'vm'. Geez.
Although: I still have Webstorm warning me that PortfolioController.Init() can be static, while I do not get that warning on MainController.Init(). So that's still a mystery, I guess.
Related
how to write different modules with their own routing?
i have an angular app and it has different modules.i am going to write for each of them, specific route file, but i got this error
Uncaught Error: [$injector:unpr] http://errors.angularjs.org/1.6.4/$injector/unpr?p0=routeServiceProvider%20%3C-%20routeService
it is my code :
sample.module.js
angular.module('app.sample', []);
sample.route.js
angular
.module('app.sample')
.run(appRun);
/* #ngInject */
function appRun (routeService) {
routeService.configureRoutes(getRoutes());
}
function getRoutes () {
return [ {
url: '/sample',
config: {
templateUrl: 'sample.html'
}
}
];
}
i already add ngRoute and inject these files in index.html file
To achieve such project structure, ui-router is the best way to go. It is a separate library so you have to include into your project as a dependency.
Here are the snippets that will be useful for your case
dashboard.module.js
angular.module('app.dashboard', ['ui.router']);
dashboard.router.js
angular.module('app.dashboard')
.config(routerConfig);
routerConfig.$inject = ['$stateProvider'];
function routerConfig($stateProvider) {
$stateProvider
.state('state1', {
url: '/state1',
templateUrl: 'url/to/state1.html',
controller: function () {
// controller code here
}
})
.state('state2', {
url: '/state2',
templateUrl: 'url/to/state2.html',
controller: function () {
// controller code here
}
});
}
sample.module.js
angular.module('app.sample', ['ui.router']);
sample.router.js
angular.module('app.sample')
.config(routerConfig);
routerConfig.$inject = ['$stateProvider'];
function routerConfig($stateProvider) {
$stateProvider
.state('state3', {
url: '/state3',
templateUrl: 'url/to/state3.html',
controller: function () {
// controller code here
}
})
.state('state4', {
url: '/state4',
templateUrl: 'url/to/state4.html',
controller: function () {
// controller code here
}
});
}
Lastly, app.module that connects all these modules
app.module.js
angular.module('app', [
/*
* sub-modules
*/
'app.dashboard',
'app.sample'
]);
To sum up, you have two independent sub-modules (app.dashboard and app.sample) with their own routing logic and one module (app) that wraps them into one angular application.
$stateProvider, service provided by ui.router, is used for registering states.
Additional info
Since your application is modular, you are probably going to need nested routing which is greatly supported by ui.router. Read docs to get more information on nested states.
Update
However, if you still want to stick with ngRoute, this and this clearly explain how to achieve the same result.
I have just created a simple app. Route for main controller is working but not for another one.
This is the part of the code of route file
$routeProvider
.when('/', {
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'main'
})
.when('/signatures', {
templateUrl: 'app/components/signature/signature.html',
controller: 'SignatureController',
controllerAs: 'signature',
resolve: {
signatureLists: function(SignatureService){
return SignatureService.getSignatures();
}
}
})
.otherwise({
redirectTo: '/'
});
and below is the controller
(function() {
'use strict';
angular
.module('demoapp')
.controller('SignatureController', SignatureController);
/** #ngInject */
function SignatureController(signatureLists) {
var vm = this;
vm.signatures = signatureLists;
}
})
I have defined the module in another file:
(function() {
'use strict';
angular
.module('demoapp', ['ngRoute', 'toastr']);
})();
when I try to visit /signatures page, I get this error:
Error: [ng:areq] Argument 'SignatureController' is not a function, got undefined
Maybe its just a silly error due to a typo or something else but still I can't figure it out
You forgot to self invoke the controller closure..do a () at the end
(function() {
'use strict';
angular
.module('demoapp')
.controller('SignatureController', SignatureController);
/** #ngInject */
function SignatureController(signatureLists) {
var vm = this;
vm.signatures = signatureLists;
}
})()
Please consider the this code where the routeProvider is needed to inject page(n).html in ng-view.
In addition to the console error:
unknown provider: $routeProviderProvider <- $routeProvider
The argument to function routeIt is the name of the page to navigate to, How can I mix a conditional switch with routeProvider.when in order to call the page matching the argument in the most efficient manner? Thanks
(function () {
'use strict';
angular
.module('appModule')
.controller('MainMenuCtrl', ['$scope', '$http', 'TogglerFactory', '$routeProvider', MainMenuCtrl]);
function MainMenuCtrl($scope, $http, Toggler, routeProvider) {
$http.get('models/mainMenu.json').then(
function (response) {
$scope.menuItems = response.data;
},
function (error) {
alert("http error");
}
)
function routeIt (page) {
routeProvider
.when('/page1', {
url: "/page1",
templateUrl: 'views/page1.html',
controller: 'Page1Ctrl'
})
.when('/page2', {
url: "/page2",
templateUrl: 'views/page2.html',
controller: 'Page2Ctrl'
})
}
$scope.itemClicked = function (item){
Toggler.menuToggle();
routeIt(item);
}
}
})();
Service providers aren't available for injection in run phase (i.e. anywhere but provider services and config blocks). It is possible to make route provider injectable and configure it after config phase,
app.config(function ($provide, $routeProvider) {
$provide.value('$routeProvider', $routeProvider);
});
And controllers aren't the best places to implement any logic (e.g. route hot-plug).
Ok you're using your routeProvider wrong you need to configure your routes inside .config blocks
angular.module('myApp', [])
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/page1', {
url: "/page1",
templateUrl: 'views/page1.html',
controller: 'Page1Ctrl'
}
})
If you want to change the url from your controller use $location.path('/page1'); inside your controller.
I want to resolve some value before I load the first page of my application, but it kept telling me
Unknown provider: programClassSummaryProvider <- programClassSummary <- HomeCtrl
I pretty sure I did it correctly, because I did the same thing for any other controller and routing. but it is not working for my homepage controller.
It seems like it load the controller first, before it is resolved in the routing. Anything wrong with my code?
In routing.js
$stateProvider
.state('home', {
url: '/home',
controller: 'HomeCtrl',
controllerAs: 'vm',
templateUrl: 'index_main.html',
resolve: {
programClassSummary: ['GroupDataFactory', function (groupDf) {
return groupDf.getProgramClassSummary();
}]
},
ncyBreadcrumb: {
skip: true
}
});
in controller.js
angular
.module('issMccApp')
.controller('HomeCtrl', homeCtrl);
homeCtrl.$inject = ['$scope', '$location', '$state', '$auth', 'programClassSummary'];
/* #ngInject */
function homeCtrl($scope, $location, $state, $auth, programClassSummary) {
var vm = this;
vm.isAuthenticated = isAuthenticated;
vm.programClassSummary = programClassSummary;
if (!$auth.isAuthenticated()) {
$state.go('login');
return;
}
function isAuthenticated() {
return $auth.isAuthenticated();
}
}
in factory.js
function getProgramClassSummary(showAll) {
var query = "";
if (showAll)
query = APIConfigObj.base_url + '/api/group/infor/programclasssummary?all=1';
else
query = APIConfigObj.base_url + '/api/group/infor/programclasssummary';
return $http.get(query)
.success(function (result) {
return result;
})
.error(function (err) {
return err;
})
}
I'd say, we really have to distinguish the UI-Router state world, and angular itself. Reason why is clearly defined here (extracted $resolve from UI-Router API documentation):
$resolve
resolve(invocables, locals, parent, self)
Resolves a set of invocables. An invocable is a function to be invoked via $injector.invoke(), and can have an arbitrary number of dependencies. An invocable can either return a value directly, or a $q promise. If a promise is returned it will be resolved and the resulting value will be used instead. Dependencies of invocables are resolved (in this order of precedence)
from the specified locals
from another invocable that is part of this $resolve call
from an invocable that is inherited from a parent call to $resolve (or recursively
from any ancestor $resolve of that parent).
There is a wroking plunker, which uses this index.html
<body ng-controller="RootCtrl">
a summary for a root:
<pre>{{summary}}</pre>
<ul>
<li>home
<li>other
</ul>
<div ui-view=""></div>
So, here we use some RootCtrl, which won't go through state machine UI-Router, it is angular basic stuff
The root controller must be defined as
.controller('RootCtrl', ['$scope', 'GroupDataFactory', function ($scope, groupDf) {
$scope.summary = groupDf.getProgramClassSummary();
}])
For a state home, we can use different approach, in fact the same as above (simplifed version below)
.state('home', {
url: "/home",
templateUrl: 'tpl.home.html',
resolve: {
programClassSummary: ['GroupDataFactory', function (groupDf) {
return groupDf.getProgramClassSummary();
}]
},
controller: 'HomeCtrl',
})
And its controller is now able to consume the locals
.controller('HomeCtrl', ['$scope', 'programClassSummary', function ($scope, summary) {
$scope.summaryForHome = summary;
}])
Check it in action here
I'd like to implement a setup where i can define a "root state" in the main module, and then add child states in other modules. This, because i need the root state to resolve before i can go to the child state.
Apparently, this should be possible according to this FAQ:
How to: Configure ui-router from multiple modules
For me it doesn't work:
Error Uncaught Error: No such state 'app' from ngBoilerplate.foo
Here is what i have:
app.js
angular.module( 'ngBoilerplate', [
'templates-app',
'templates-common',
'ui.state',
'ui.route',
'ui.bootstrap',
'ngBoilerplate.library'
])
.config( function myAppConfig ( $stateProvider, $urlRouterProvider ) {
$stateProvider
.state('app', {
views:{
"main":{
controller:"AppCtrl"
}
},
resolve:{
Auth:function(Auth){
return new Auth();
}
}
});
$urlRouterProvider.when('/foo','/foo/tile');
$urlRouterProvider.otherwise( '/foo' );
})
.factory('Auth', ['$timeout','$q', function ($timeout,$q) {
return function () {
var deferred = $q.defer();
console.log('before resolve');
$timeout(function () {
console.log('at resolve');
deferred.resolve();
}, 2000);
return deferred.promise;
};
}])
.run(function run( $rootScope, $state, $stateParams ) {
console.log('greetings from run');
$state.transitionTo('app');
})
.controller( 'AppCtrl', function AppCtrl ( $scope, Auth ) {
console.log('greetings from AppCtrl');
});
foo.js
angular.module( 'ngBoilerplate.foo', ['ui.state'])
.config(function config( $stateProvider ) {
$stateProvider
.state( 'app.foo', {
url: '/foo/:type',
views: {
"main": {
controller:'FooCtrl',
templateUrl: function(stateParams) { /* stuff is going on in here*/ }
}
}
});
})
.controller( 'FooCtrl', function FooCtrl( $scope ) {
console.log('deferred foo');
});
How do i make this work or what other approaches could i take to have something global resolved before every state (without defining a resolve on each state)?
I finally chose this approach which does the job for me:
// add all your dependencies here and configure a root state e.g. "app"
angular.module( 'ngBoilerplate', ['ui.router','templates-app',
'templates-common','etc','etc']);
// configure your child states in here, such as app.foo, app.bar etc.
angular.module( 'ngBoilerplate.foo', ['ngBoilerplate']);
angular.module( 'ngBoilerplate.bar', ['ngBoilerplate']);
// tie everything together so you have a static module name
// that can be used with ng-app. this module doesn't do anything more than that.
angular.module( 'app', ['ngBoilerplate.foo','ngBoilerplate.bar']);
and then in your app index.html
<html ng-app="app">
In the documentation the feature1 module depends on the application module. Try
angular.module( 'ngBoilerplate.foo', ['ngBoilerplate'])
I would of just commented but i do not have the rep. I know this is old but i had the same problem and came across this. One thing i am confused about is in app.js you do not import "ngBoilerplate.foo" but ngBoilerplate.library instead. I had the same problem and my solution was to inject sub modules into the top module instead of their parent.
My structure was module('ngBoilerplate'), module('ngBoilerplate.foo') and module('ngBoilerplate.foo.bar')'. I was injecting ngBoilerplate.foo.bar into ngBoilerplate.foo and the $stateProvider was failing. I needed to inject ngBoilerplate.foo.bar into top level ngBoilerplate.
I thought i would put this here in case anyone else sees this. The error i had was Uncaught TypeError: Cannot read property 'navigable' of undefined from ngBoilerplate.foo