My application has multiple states.
The workflow is such that from the landing page I go into Alert view page that has list of alerts in a tabular format. From that list I select an alert which takes the me into the Alert Detail page, that gives detailed information of that particular alert. Within that alert Detail state, there are subviews in the form of navigation tabs that displays various aspect of that alert when clicked on the particular tab.
AlertView.config.js:
$stateProvider.state({
name: "alert-view",
parent: "root",
url: "/alert/view?id&name&criteria&start&end&targets&sort",
data: {
pageTitle: "Alert View"
},
views: {
"main.content#": {
templateUrl: "components/alerts/alert-view/alert-view.html",
controller: "AlertViewsController",
controllerAs: "vm"
}
}
Alert Detail Page.config.js
$stateProvider.state({
name: "alert-details",
parent: "root",
abstract: true,
url: "/alert/details/:id",
params: {
// this will make both links /alert/details & /alert/details/ work
id: {squash: true, value: null}
},
data: {
pageTitle: "Alert Details"
},
views: {
"main.content#": {
templateUrl: "components/alerts/alert-details/alert-details.html",
controller: "AlertDetailsController as vm"
},
"alertViewDetail#alert-details": {
templateUrl: "components/alerts/alert-details/overview/overview.html",
controller: "AlertDetailsOverviewController as vm"
//#todo parent/child controller.
}
},
}).state({
name: "alert-details.overview",
parent: "alert-details",
url: "/overview",
params: {
// this will make both links /alert/details & /alert/details/ work
id: {squash: true, value: null}
},
data: {
pageTitle: "Alert Details"
},
views: {
"alertViewDetail#alert-details": {
templateUrl: "components/alerts/alert-details/overview/overview.html",
controller: "AlertDetailsOverviewController as vm",
controllerAs: "vm"
}
}
}).state({
name: "alert-details.history",
parent: "alert-details",
url: "/history",
params: {
// this will make both links /alert/details & /alert/details/ work
id: {squash: true, value: null}
},
data: {
pageTitle: "Alert Details"
},
views: {
"alertViewDetail#alert-details": {
templateUrl: "components/alerts/alert-details/history/history.html",
controller: "AlertDetailsHistoryController",
controllerAs: "vm"
}
}
}).state({
name: "alert-details.trigger",
parent: "alert-details",
url: "/trigger-events",
params: {
// this will make both links /alert/details & /alert/details/ work
id: {squash: true, value: null}
},
data: {
pageTitle: "Trigger Events"
},
views: {
"alertViewDetail#alert-details": {
templateUrl: "components/alerts/alert-details/trigger-events/trigger-events.html",
controller: "AlertDetailsTriggerEventsController",
controllerAs: "vm"
}
}
}).state({
name: "alert-details.all-fields",
parent: "alert-details",
url: "/all-fields",
params: {
// this will make both links /alert/details & /alert/details/ work
id: {squash: true, value: null}
},
data: {
pageTitle: "All Alert Fields"
},
views: {
"alertViewDetail#alert-details": {
templateUrl: "components/alerts/alert-details/all-fields/all-fields.html",
controller: "AlertDetailsAllAlertFieldsController",
controllerAs: "vm"
}
}
});
alertDetail.html
<button ng-click="vm.goBack()" class="btn btn-default"><i class="icon icon-long-arrow-left"></i><span translate>Back</span></button>
alertDetail.controller.js
vm.goBack = function () {
history.back();
};
My aim is to implement the back button in the alert detail page such that , no matter in whichever subview I am, if clicked on the back button, it directs me to Alert View Page. I tried using history.back() to do so. But it traverses through all the previous state rather than routing to Alert View page directly.
How should I utilise the state information of the Alert View page and Alert Detail page, in order to implement the required scenario. Please help.
You can store all states in a rootscope array.
$rootScope.statesInfo = ['alert-details.overview','alert-details.history',....];
When user press back button then check the current state and go on the previous state.
You can get current state using this: $state.current.name
Simple you will compare and go back in previous state;
angular.forEach($rootScope.statesInfo,function (val,key) {
if ($state.current.name == val) {
$state.go($rootScope.statesInfo[key-1]);
}
});
To solve the problem of saving the current URL and use it later, I created a service that would take in the the query string of the URL as a single object. Then when i need to go to that URL in that same state, I would use that query string object and pass it as a parameter on $state.
so vm.goBack function would look like this:
vm.goBack = function () {
$state.go("alert-view", {name: alertHistory.getHistory().name, criteria: alertHistory.getHistory().criteria, sort: alertHistory.getHistory().sort});
};
the service that i created was:
(function () {
"use strict";
angular
.module("app.alerts")
.service("alertHistory", alertHistory);
alertHistory.$inject = [
"$location",
"logger"];
function alertHistory ($location, logger) {
var historyURL = {
init: null,
saveURL: saveURL,
getHistory: getHistory
};
return historyURL;
function saveURL (urlObj) {
historyURL.currentURL = urlObj;
}
function getHistory () {
return historyURL.currentURL;
}
}
})();
in controller I obtain the query string using $location.search() :
alertHistory.saveURL($location.search());
This might not be the ideal solution. But it works for what i need :) Any suggestions are welcome!
Related
So i have 2 routes set up:
$stateProvider.state('accounts', {
abstract: 'true',
url: "/accounts",
template: '<div ui-view></div>'
}).state('accounts.view', {
url: "/:accountNumber",
views: {
'#': {
templateUrl: 'tpl/account/index.html',
controller: 'AccountController',
controllerAs: 'controller'
}
},
resolve: {
today: ['today', function (today) {
// Return todays date
return today();
}],
tomorrow: ['tomorrow', function (tomorrow) {
// Return tomorrows date
return tomorrow();
}],
lastMonth: ['lastMonth', function (lastMonth) {
// Return lastMonths date
return lastMonth();
}],
account: ['$stateParams', 'AccountService', function ($stateParams, accountService) {
console.log('hi');
return accountService.get($stateParams.accountNumber);
}]
},
data: {
requireLogin: true,
pageTitle: 'Account details'
}
}).state('accounts.create', {
url: '/create',
views: {
'#': {
templateUrl: 'tpl/account/save.html',
controller: 'AccountSaveController',
controllerAs: 'controller'
}
},
resolve: {
account: function () {
return {};
}
},
data: {
requireLogin: true,
pageTitle: 'Create account'
}
}).state('accounts.view.edit', {
url: '/edit',
views: {
'#': {
templateUrl: 'tpl/account/save.html',
controller: 'AccountSaveController',
controllerAs: 'controller'
}
},
data: {
requireLogin: true,
pageTitle: 'Edit account'
}
})
If I click a button decorated with ui-sref='accounts.create' it will navigate to the correct view, but it tries to do the resolve in accounts.view. It seems that the :accountNumber is being confused with /create.
If I refresh my page, it doesn't load the view and still tries to resolve the account.
I have tried a number of ways to solve this.
I would like to have the urls like this:
accounts/:accountNumber - for viewing an account
accounts/create - for creating an account and
accounts/:accountNumber/edit - For editing an account
Is it possible to set up the routes like that?
If your accountNumber is a number indeed, you can define your account.view state as url: "/{accountNumber:int}". That will prevent accounts/create from matching it. Otherwise, you can use some custom regex see docs to achieve the same.
You may also find GitHub issue useful.
I'm building the outline of an app using ui-router. I've defined the states and it seems to work, but I can't help but feel there might be a better way in terms of best practice.
My states:
'main' is an abstract state with a header, footer, and a middle content area.
'main.home' is what comes up by default. The content file 'home.tpl.html' is a sort of splash page with srefs to other areas of the app, like 'main.wizard.step1'.
'main.wizard' is an abstract state representing a multi step info gathering wizard.
'main.wizard.step1', 'main.wizard.step2' are steps in the wizard
What I'm doubting is my use of the 'views' object having values of "#" and "". Does this look reasonable, or would you do something else?
$urlRouterProvider.otherwise('/');
var app = {
name: 'main',
abstract: true,
url: '',
views: {
'header': {
templateUrl: 'header.tpl.html'
},
'footer': {
templateUrl: 'footer.tpl.html'
}
}
};
var home = {
name: 'main.home',
url: '/',
views: {
"#": {
templateUrl: 'home/home.tpl.html'
}
}
};
var wizard = {
name: 'main.wizard',
abstract: true,
url: '/wizard',
views: {
"#": {
templateUrl: 'wizard/wizard.tpl.html'
}
}
};
var step1 = {
name: 'main.wizard.step1',
url: '/step1',
views: {
"": {
templateUrl: 'wizard/step1.tpl.html'
}
}
};
/** repeat similarly for step2, step3 & step 4 **/
$stateProvider.state(app);
$stateProvider.state(home);
$stateProvider.state(wizard).state(step1).state(step2).state(step3).state(step4);
What '#' will mean in the definition of the template of the view will be injected into the ui-view of the root state which is usually your index.html.
If you want the wizard to go into your middle area of your main page you should add something like this to your index.html:
<ui-view name='content'>Optional placeholder text</ui-view>
And in the definition of the view you should do something like
var wizard = {
name: 'main.wizard',
abstract: true,
url: '/wizard',
views: {
"#content": {
templateUrl: 'wizard/wizard.tpl.html'
}
}
};
You can actually drop that # in #content because main is the parent of wizard and you don't have to resolve it in an absolute manner.
In any case if you want to have steps in your wizard(of course) don't put any views in your wizard virtual state. Have your steps sub states have their own view and target wizard#. I don't see that you need a separate view wrapper for your wizard (Maybe if you want to do "step x of y" or a progress bar).
Hope it helps.
You are going good. But you can improve. Here is the code which shows how I had done it,I have taken this approach from a sample app in UI-Router Guide.
var states = [
{ name: 'hello', url: '/loginnn', component: 'loginComponent' },
{ name: 'home', url: '/hommm', component: 'homeComponent' },
{
name: 'home.dumm1',
url: '',
component: 'dummyComponent',
},
{
name: 'home.dumm2',
url: '/dumm1',
component: 'dummyComponent1',
},
{
name : 'home.dashboard',
url: '/dashboard',
component: 'dashboardComponent',
}
]
// Loop over the state definitions and register them
states.forEach(function(state) {
$stateProvider.state(state);
});
});
I have multi pages with One View .I want use Single View for All pages, with Logical structure Angular, In addition Breadcrumbs for navigation, in head of Home page.
config code:
$stateProvider
.state('otherwise', {
url: '*path',
controller: function ($state) {
var lastUrl = sessionStorage.lastUrl
if (lastUrl && lastUrl != 'otherwise')
$state.go(lastUrl);
else if (!lastUrl)
$state.go('Companies');
},
data: {
requireLogin: false
}
})
.state('Create', {
controller: 'myControl',
url: '/',
templateUrl: 'Create.html',
data: {
requireLogin: true
}
})
.state('Jobs', {
controller: 'myControl',
url: '/',
templateUrl: 'JobsList.html',
data: {
requireLogin: true
}
})
.state('Companies', {
controller: 'myControl',
url: '',
templateUrl: 'CompaniesList.html',
data: {
requireLogin: false,
breadcrumbProxy: 'Companies.CarsList'
}
})
.state('Companies.CarsList', {
controller: 'myControl',
params: { id: ':id', companyName: ':companyName' },
url: '',
templateUrl: 'CarsList.html',
data: {
requireLogin: false,
displayName: 'List'
}
})
.state('Companies.CarsInfo', {
controller: 'myControl',
templateUrl: "CarInfo.html",
data: {
requireLogin: false,
displayName: 'Info'
}
})
html:using single VIEW in home page
<div ui-view></div>
You have a Solution for my Config?!
Single view
To handle multiple views, the ui-router provides rules to target views when you have multiple <div ui-view></div> in your templates.
By default, a state takes place in the unique <div ui-view></div> in parent state. So, given your configuration, the Companies.CarsList template will be inserted in the <div ui-view></div> of the Companies state (in CompaniesList.html).
To override that, just wrap templateUrl and controller of your second-level states (Companies.CarsList and Companies.CarsInfo) in a views object the ui-router to place the view in the unique <div ui-view></div> of the root state (in index.html), like this:
.state('Companies.CarsList', {
params: { id: ':id', companyName: ':companyName' },
url: '',
views: {
"#": { // rule for absolutely targetting the unnamed view in root unnamed state.
controller: 'myControl',
templateUrl: 'CarsList.html',
}
},
data: {
requireLogin: false,
displayName: 'List'
}
})
Breadcrumb
Have a look on angular-breadcrumb, it generates a breadcrumb based on the ui-router configuration. All you have to do is to give a name to the states (like you seems to do already with data > displayName).
The module can handle the absolute view targeting I described above. See the docs for details
i'm trying to get work this combination of sub levels routes.
http://plnkr.co/edit/1nBzppJkB45Ljv5SRW4b?p=preview
i took a sample from ui-route on github and added a parent view (contactg).
$stateProvider
.state('contactg', {
url: '/contactg',
templateUrl: 'contactG.html',
onEnter: function () { console.log("enter contactG"); }
})
.state('contactg.contacts', {
abstract: true,
url: '/contacts',
templateUrl: 'contacts.html',
controller: function ($scope) {
$scope.contacts = [{ id: 0, name: "Alice" }, { id: 1, name: "Bob" }];
},
onEnter: function () {
console.log("enter contacts");
}
})
.state('contactg.contacts.list', {
url: '/list',
// loaded into ui-view of parent's template
templateUrl: 'contacts.list.html',
onEnter: function () {
console.log("enter contacts.list");
}
})
.state('contactg.contacts.detail', {
url: '/:id',
// loaded into ui-view of parent's template
templateUrl: 'contacts.detail.html',
controller: function ($scope, $stateParams) {
$scope.person = $scope.contacts[$stateParams.id];
},
onEnter: function () {
console.log("enter contacts.detail");
}
})
My issues is when i click on an item in contactg.contacts.list, the link generated tends to redirect me to /contacts/:id (detail) instead of /contactg/contacts/:id
is there a way to specify that ?
Thanks
Guillaume.
I have a view, which is a child of 2 views.
I want to have different urls to restore it. (ex: #/activities/contact/12, #/search/contact/12 etc)
I haven't find a way to do that without duplicate the state definition.
$stateProvider
.state('main', {
abstract: true,
url: '/',
controller: function() {
},
templateUrl: 'main.abstract.html'
})
.state('activities', {
parent: 'main',
url: 'activities',
template: function() {
return 'ACTIVITY TEMPLATE <br><br><a ui-sref="activities()">activity</a> <br/><a ui-sref="search()">search</a><br><br><a ui-sref="organization()">orga</a>';
},
})
.state('search', {
parent: 'main',
url: 'search',
template: function() {
return 'SEARCH TEMPLATE <br><br><a ui-sref="activities()">activity</a> <br/><a ui-sref="search()">search</a><br><br><a ui-sref="organization()">orga</a>';
},
})
.state('organization', {
parent: 'main',
url: '/contacts/:id',
views: {
"right#main": {
template: function() {
return 'orga loaded';
}
},
}
})
Plunkr available here:
http://plnkr.co/edit/hDZ1FQASKt8zctjNCOuF?p=preview
When the user click on the "orga link", it should load the view in the red bo, but keeping the current parent view in the green one.