I'm new to Angular 1 and have to implement a new feature on an existing webapp. The app uses jhipster to generate some parts of the backend and frontend (Angular 1 and uirouter).
So I tried to use my own route and state like this which is mostly copy and pasted from existing components of the webapp:
(function() {
'use strict';
angular
.module('artemisApp')
.config(stateConfig);
stateConfig.$inject = ['$stateProvider'];
function stateConfig($stateProvider) {
$stateProvider
.state('model-comparison-exercise-for-course', {
parent: 'entity',
url: '/course/{courseid}/model-comparison-exercise',
data: {
authorities: ['ROLE_ADMIN', 'ROLE_TA'],
pageTitle: 'artemisApp.modelComparisonExercise.home.title'
},
views: {
'content#': {
templateUrl: 'app/entities/model-comparison-exercise/model-comparison-exercise.html',
controller: 'ModelComparisonExerciseController',
controllerAs: 'vm'
}
},
resolve: {
translatePartialLoader: ['$translate', '$translatePartialLoader', function ($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('modelComparisonExercise');
$translatePartialLoader.addPart('exercise');
$translatePartialLoader.addPart('global');
return $translate.refresh();
}],
courseEntity: ['$stateParams', 'Course', function ($stateParams, Course) {
return Course.get({id: $stateParams.courseid}).$promise;
}]
}
});
}
})();
Then I try to open this route with the following code:
<a ui-sref="model-comparison-exercise-for-course({courseid:course.id})"
data-translate="artemisApp.course.modelComparisonExercises"></a>
By clicking on that link a http get request is fired which returns a http status code 404: http://localhost:8080/app/entities/model-comparison-exercise/model-comparison-exercise.html
Actually, the url that should be opened is http://localhost:8080/#/course/1/model-comparison-exercise
Any idea what I could have configured wrong?
Please try changing 'content#' to 'content#artemisApp'.
As explained here:
The symbol before the # is the name of the view you want to match, and the symbol after the # is a reference to the state in which the template the ui-view directive should exist in.
And the <a> tag is not being closed:
<a ui-sref="model-comparison-exercise-for-course({courseid:course.id})"
data-translate="artemisApp.course.modelComparisonExercises"></a>
Searching through the code, I found that model-comparison-exercise.html does not exist in folder model-comparison-exercise. Besides model-comparison-exercises.html exist.
Related
I have setup a parent view with multiple child views, and it seems to be working with no problem in the application. The problem is if you refresh the page or try to go directly to the child view with the appropriate url.
Here are my states that I am loading. This the parent state:
{name: "myTeam", url: "/myTeam", template: "<my-team></my-team>"}
Here's one of the children:
{name: "myTeam.info", url: "/info", template: "<my-team.info></my-team.info>"}
Here's another:
{name: "myTeam.checkin", url: "/checkin/{book}/{club}/{team}/{bus}", template: "<my-team.checkin></my-team.checkin>"}
So, if I visit the Info view, it loads the template correctly and sets the url to "/myTeam/info". If try to refresh the page or just start out at the url "/myTeam/info", it won't load the state/template.
I added code to the angular-ui-router.js (version 0.1.2) to try to debug. I added the console.log() line at line 912.
UrlMatcher.prototype.exec = function (path, searchParams) {
var m = this.regexp.exec(path);
console.log(" --> " + this.regexp + " -- " + path + " = " + m);
if (!m) return null;
searchParams = searchParams || {};
Here's the output:
--> /^\/myTeam\/?$/ -- /myTeam/info = null
--> /^\/info(.*)?\/?$/ -- /myTeam/info = null
--> /^\/checkin\/([^\/]*)?\/([^\/]*)?\/([^\/]*)?\/([^\/]*)?(.*)?\/?$/ -- /myTeam/info = null
So, it's not matching on the parent or the child.
I was following the example on the Nested States and Nested Views. Their urls looked the same to me.
I don't see how it makes a difference, but I am using the UI extras to load these as future states and using the ocLazyLoad.
Any thoughts on how I can get the URL to work? Or, how it is supposed to work?
I finally determined that the problem was with the UI Extras and future state loading.
I ended out taking the easy road for time's sake. I changed my project to not use any child states, and that takes care of my problem.
From the best I can tell, it is not loading all of the children states. The future state loader is only matching the parent and loading the parent.
I think my problem is similar to this problem that I found here:
UI Router Extras Issue #63
I attempted to write code in my future state loader that would load the parent state and all of it's children. That forced me to have to modify the UI Router Extras to accept an array of promises, and I had to modify too many things to make it work.
Hopefully, at some point I can try to build a more complete fix outside my project and contribute it, but for now, my solution is to avoid child states.
weird, my project have children states, and some children have more children states too, and it's working fine without problems, i will show you how i setup my url:
dashboardState.js
the state with url: '' is the home page, and it is loaded when you navigate to url /home, but their children home loads inside dashboard.
(function () {
'use strict';
angular
.module('myapp')
.config(dashboardStates);
function dashboardStates($stateProvider) {
var dashboard = {
name: 'dashboard',
abstract: true,
url: '/home', // view inicial abaixo
templateUrl: 'app/components/Dashboard/view/dashboard.html',
controller: 'DashboardController',
controllerAs: 'dashboard'
};
var dashboardHome = {
name: 'dashboard.home',
url: '',
parent: 'dashboard',
templateUrl: 'app/components/Dashboard/view/dashboard.home.html',
controller: 'DashboardController',
controllerAs: 'home',
ncyBreadcrumb: {
label: 'Home'
}
};
// states
$stateProvider
.state(dashboard)
.state(dashboardHome);
}
}());
and the state below loads inside the dashboard view:
profileState.js
(function () {
'use strict';
angular
.module('myapp')
.config(profileStates);
function profileStates($stateProvider) {
var profile = {
name: 'dashboard.profile',
url: '/profile',
templateUrl: 'app/components/Profile/view/profile.html',
controller: 'ProfileController',
controllerAs: 'profile'
};
var profileEdit = {
name: 'dashboard.profile.edit',
url: '/edit',
templateUrl: 'app/components/Profile/view/profile.edit.html',
controller: 'ProfileController',
controllerAs: 'profile'
};
$stateProvider
.state(profile)
.state(profileEdit);
}
}());
more info: https://github.com/angular-ui/ui-router/wiki/Nested-States-and-Nested-Views
I don't know why this first state works but the second one doesn't:
Working state:
.state('app.pages.invoice', {
url: '/invoice',
templateUrl: "assets/views/pages_invoice.html",
title: 'Invoice',
resolve: {
"currentAuth": ["Auth", function(Auth) {
return Auth.$requireSignIn();
}]
}
})
Not working state, throwing the Error: [ng:areq]:
validationCtrl&p1=not%20aNaNunction%2C%20got%20undefined
.state('app.form.validation', {
controller: "validationCtrl",
url: '/validation',
templateUrl: "assets/views/form_validation.html",
title: 'Form Validate',
resolve: {
"currentAuth": ["Auth", function(Auth) {
return Auth.$requireSignIn();
}]
}
})
This second one works only when the controller is injected via this:
resolve: loadSequence('validationCtrl')
that is (controller is moved into the resolve and there is not currentAuth anymore):
.state('app.form.validation', {
url: '/validation',
templateUrl: "assets/views/form_validation.html",
title: 'Form Validate',
resolve: loadSequence('validationCtrl')
})
and then I don't know how to integrate my currentAuth element into the resolve again. How can I inject the validationCtrl.js via resolve and add the currentAuth element also into resolve?
here is the loadsequence function:
// Generates a resolve object previously configured in constant.JS_REQUIRES (config.constant.js)
function loadSequence() {
var _args = arguments;
return {
deps: ['$ocLazyLoad', '$q',
function ($ocLL, $q) {
var promise = $q.when(1);
for (var i = 0, len = _args.length; i < len; i++) {
promise = promiseThen(_args[i]);
}
return promise;
function promiseThen(_arg) {
if (typeof _arg == 'function')
return promise.then(_arg);
else
return promise.then(function () {
var nowLoad = requiredData(_arg);
if (!nowLoad)
return $.error('Route resolve: Bad resource name [' + _arg + ']');
return $ocLL.load(nowLoad);
});
}
function requiredData(name) {
if (jsRequires.modules)
for (var m in jsRequires.modules)
if (jsRequires.modules[m].name && jsRequires.modules[m].name === name)
return jsRequires.modules[m];
return jsRequires.scripts && jsRequires.scripts[name];
}
}]
};
}
My first state doesn't have any controller, so I am fine resolving the currentAuth alone. But when the view has a controller, add the currentAuth causes the controller not to work anymore.
note:
my currentAuth is taken from here.
update:
herer is the validationCtrl.js:
app.controller('ValidationCtrl', ["$scope", "$state", "$timeout", "SweetAlert", "$location",
function ($scope, $state, $timeout, SweetAlert, $location) {
...
update 2:
basically the question is to allow only currently signed in users to view the pages which are children of app.; so my parent view is like this: so basically I am looking to inject the currentAuth factory into the main parent view and the children should inherit this. They cannot be viewed unless the currentAuth in the parent is resolved.
$stateProvider.state('app', {
url: "/app",
templateUrl: "assets/views/app.html",
resolve: loadSequence('modernizr', 'moment', 'angularMoment', 'uiSwitch', 'perfect-scrollbar-plugin', 'toaster', 'ngAside', 'vAccordion', 'sweet-alert', 'chartjs', 'tc.chartjs', 'oitozero.ngSweetAlert', 'chatCtrl'),
abstract: true
})
edit 1:
I have put the question in other words as well here and trying to find an answer to multiple resolve states.
edit 2:
here is the main.js: pastebin url
and the validationCtrl.js pastebin url.
actually, the validationCtrl is just an example controller among other controllers I have.
Question is how to block view permission for child views unless the parent currentAuth is resolved? given that I don't know how to handle multiple resolve with loadsequence and a singleton factory.
Assuming you are using ui router as a routing framework for your SPA app.
Error: [ng:areq]
the error you get:
validationCtrl&p1=not%20aNaNunction%2C%20got%20undefined
is due to the declaration of the controller within the state, the name of the controller function is not resolved because "ValidationCtrl" is not equal to "validationCtrl" then correct state is:
.state('app.form.validation', {
controller: "ValidationCtrl",
url: '/validation',
templateUrl: "assets/views/form_validation.html",
title: 'Form Validate',
resolve: {
"currentAuth": ["Auth", function(Auth) {
return Auth.$requireSignIn();
}]
}
})
Abstract States - Nested States
to answer the second question, a useful example for your case may be this:
$stateProvider.state('app', {
url: "/app",
templateUrl: "assets/views/app.html",
resolve: {
scripts: loadSequence('modernizr', 'moment', 'angularMoment', 'uiSwitch', 'perfect-scrollbar-plugin', 'toaster', 'ngAside', 'vAccordion', 'sweet-alert', 'chartjs', 'tc.chartjs', 'oitozero.ngSweetAlert', 'chatCtrl').deps,
currentAuth: function(Auth){ return Auth.$requireSignIn();}
},
abstract: true
})
.state('app.pages.invoice', {
// url will become '/app/invoice'
url: '/invoice',
templateUrl: "assets/views/pages_invoice.html",
title: 'Invoice'
})
.state('app.form.validation', {
controller: "ValidationCtrl",
// url will become '/app/validation'
url: '/validation',
templateUrl: "assets/views/form_validation.html",
title: 'Form Validate'
})
As you can see from the example in the resolve of the abstract state you can define different factory functions, ui router will wait until all dependencies are resolved before resolving the children states.
Resolve property explanation :
The resolve property is a map object. The map object contains key/value pairs of:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}:
If string, then it is an alias for a service.
Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before the controller is instantiated and its value is injected into the controller.
for more details I refer you to ui router doc.
As said in my comment i suggest you to try the following :
.state('app.form.validation', {
url: '/validation',
templateUrl: "assets/views/form_validation.html",
title: 'Form Validate',
controller: "validationCtrl",
resolve:{
"myCtrl": loadSequence('validationCtrl'),
"currentAuth": ["Auth", function(Auth) {
return Auth.$requireSignIn();
}]
}
})
The other part of my comment was about the fact that child states inherits parent's resolve and children can override it.
So you can just do the following :
.state('app', {
// all states require logging by default
resolve:{
"currentAuth": ["Auth", function(Auth) {
return Auth.$requireSignIn();
// i'm guessing we're redirecting toward app.login if not logged
}]
}
})
.state('app.login', {
resolve:{
"currentAuth": ["Auth", function(Auth) {
return true;// just be sure to not do infinite redirections
}]
}
})
Note if you have some trouble because Auth isn't yet loaded with lazy loading, you should be able to load it in a angular.run.
Firstly coming to the error
Error: [ng:areq]:
validationCtrl&p1=not%20aNaNunction%2C%20got%20undefined
controller: "validationCtrl", change it according to the main controller
that is
This means there is no validationCtrl function.
I may be wrong but i think there is a small typo' in this line in your controller controller: "validationCtrl", change it according to the **main controller** controller: "ValidationCtrl"
that is
This error happens due to either defining two angular.modules with the same name in different files containing different arguments as you may be trying to implement the dependancy injection.
It causes the problem as the script loaded inyour main html file won't know which angular.module to be configured.
To resolve this define the angular.modules with different names.
How to block view permission for child views unless the parent currentAuth is resolved
You can install this package angular middlewareThis middleware package contains some pre-defined route functions or you can also create your own functions.Along with this $http documentation using the success and callback functions,you can create your own middleware and the auth service while using a singleton factory
OR
Assuming that you are using node.js as your backend you can use [middleware][3] routing in your server using express and map it to the frontend routes.
Here is a perfect tutorial for middleware authentication in nodejs
I'm currently working on a project using UI Router. My code currently defines states as part of the app config ( example below ) but the code is growing. Is there a good way to modularize this code both for organization and unit testing? For me the best solution would be to define states as an external service.
.state('page', {
url: '/page/{id}',
params: {
id: ['$q', function ($q) {
// Code
return defaultValue;
}],
},
templateUrl: 'page.html',
'controller': 'CatalogDetailsController',
'controllerAs': 'details',
resolve: {
categories: ['$q', function ($q) {
// Code
return promise;
}],
},
I would start by defining the objects separately instead of in-line
( do note that this does make the code less readable)
.state('page', {
url: '/page/{id}',
params: myparamsObj // defined somewhere else.
templateUrl: 'page.html',
'controller': 'CatalogDetailsController',
'controllerAs': 'details',
resolve: myResolveObj, // defined somewhere else.
If your app.config is becoming too big , you could use the approach mentioned in [refactor large AngularJS module config into separate files ] question to split your config part.
My proposal is based on ES6 modules.
Long story short. Each state has a separate folder, for instance contacts/one/edit. In this folder I have the following files:
controller.js, controller.spec.js
state.js, state.spec.js
state.html
state.js holds the state definition object:
import controller from './edit.controller';
import template from './edit.state.html';
// State name is exported so we can use it in the corresponding tests
export const name = 'contacts.one.edit';
export default {
name,
url: '/edit',
template,
controller,
controllerAs: 'ctrl'
};
This configuration could be activated in the module's configuration block:
import oneState from './one/one.state';
export function states($stateProvider) {
'ngInject';
$stateProvider
.state({
parent: 'app',
name: 'contacts',
abstract: true,
url: '/contacts',
template: '<div ui-view autoscroll="true"></div>'
})
.state(oneState)
.state(oneEditState)
// etc...
}
Here you will find the complete example https://github.com/lucassus/angular-webpack-seed/tree/ce4e9b91ce9ed47ca74073d754b0cbacff8cb65f/src/app/contacts/one/edit
Using Angular UI router, I have a route set up as follows
$stateProvider.state('edit', {
url: '/:file/:page',
...
}
if I change the route from /edit/file1/page1 to /edit/file1/page2, the view doesn't refresh. I'm calling $state.go, and I can easily redraw the page, but by not running this through the router things like the back button don't update the route.
Can someone advise how to update the route so the view is updated?
Thanks.
If I understand your intention here, it is to have your templateUrl be based on the :file and :page $stateParams.
Since you haven't shown how you are actually using $state.go, I put together an example that shows a few of the ways you can change between states with and without { reload: true }.
angular.module('exampleApp', ['ui.router'])
.config(function($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/file1/page1');
$stateProvider
.state('edit', {
url: '/:file/:page',
controller: 'FilePageCtrl',
templateUrl: function($stateParams) {
return $stateParams.file + "-" + $stateParams.page + ".html";
}
});
})
.controller('FilePageCtrl', function($scope, $state) {});
Here are some example links that change the state (but will not re-run the controller and template if it's already the current state):
<a ui-sref="edit({ file: 'file1', page: 'page2' })">/file1/page2</a>
Is more or less the same as (whether inline with ng-click or within a controller):
<a href ng-click="$state.go('edit', { file: 'file1', page: 'page2' })">/file1/page2</a>
The following two will force the controller and templateUrl to run, regardless if the current state is the same:
<a ui-sref="edit({ file: 'file1', page: 'page2' })" ui-sref-opts="{ reload: true }">/file1/page2</a>
<a href ng-click="$state.go('edit', { file: 'file1', page: 'page2' }, { reload: true })">/file1/page2</a>
You can read more about using a dynamic templateUrl here: https://github.com/angular-ui/ui-router/wiki#templates
Here is a working example: http://plnkr.co/edit/JUgUB3I675Kh7kKkitgJ?p=preview
For the following state definitions and the url http://localhost/#/foo/bar, I expect to receive the console output
/foo
/foo/bar
However, I only get one /foo. Is my expectation wrong? What can I do to get the parent controller invoked as well?
$stateProvider
.state('foo', {
url: '/foo',
controller: function($scope) {
console.log('/foo')
},
abstract: true
})
.state('bar', {
parent: 'foo',
url: '/bar',
controller: function($scope) {
console.log('/foo/bar')
}
})
Update
Even more strange, when I add
onEnter: function() {
console.log('enter')
}
enter is printed (but not /foo/bar).
Update 2
I'd like to add a resolve attribute to the parent route and have all children wait for a deferral to be resolved until their controllers get instantiated. This is what it follows: https://github.com/angular/angular.js/issues/5854 The scope is used to store the result of the deferral (which is a $http response).
On the abstract parent state, there should be a ui-view directive so the child state knows where to insert itself.
.state('foo', {
url: '/foo',
abstract: true,
controller: function($scope) {
console.log("/foo");
},
template: '<ui-view/>'
})
Here is a working example: http://plnkr.co/edit/OskGCBSQGhEXlxaNLZtD?p=preview
Update 2 response
You should be able to just add a resolve to the abstract parent state:
resolve: {
user: function($q, $timeout) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("a resolved user");
}, 1000);
return deferred.promise;
}
}
Here is a working example of that: http://plnkr.co/edit/cm0xUP8LmruHiVBhccuf?p=preview
Yeah, your expectation is wrong: only one route will be matched. If you want to match the second route, you'll need to set the url to /foo/bar, but there's no way to hit them both with one URL.
I'm not sure what you're trying to do but if you want to access url parameters you can use /foo/:paramName and the $routeParams service.