Multiple parents views with angular ui-router - angularjs

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.

Related

How to modify url of a state in AngularJS ui-routes

I am on angular 1.6. Trying to override value of a ui-router state.
Initial states as follows :
$stateProvider.state('parent', {
abstract: true,
parent: 'ancestor',
url: '/parent',
data: {
authorities: [A,B]
},
views: {
'content#': {
templateUrl: 'app/scripts/myapp/module/parent.html'
}
}
})
.state('child', {
parent: 'parent',
abstract: true,
views: {
'tabs': {
templateUrl: 'app/scripts/myapp/module/submodule/child.html',
controller: 'ChildController',
controllerAs: 'childController'
}
}
})
.state('grandChild', {
parent: 'child',
url: '/{dataSource:(?:option1|option2|option3)}',
views: {
'body': {
templateUrl: 'app/scripts/myapp/module/submodule/grandchild.html',
controller: 'GrandChildController',
controllerAs: 'grandChildController'
},
},
resolve: {
groups: ['MyService', '$transition$', function (MyService, $transition$) {
var selectedSource = $transition$.params().dataSource ? $transition$.params().dataSource : 'option1';
return MyService.getGroups(selectedSource);
}]
}
})
Then in one of sub-implementation I am trying to override value of url - adding option4 for one of the state.
var states = $stateRegistry.deregister('grandChild');
$stateRegistry.register(states[0]);//There is one more child. For simplicity not showing here.
states[1].url.pattern = '/{dataSource:(?:option1|option2|option3|option4)}';
$stateRegistry.register(states[1]);
when I do following on 'grandChild'
$state.go('grandChild', {dataSource: 'option4'});
I get error as
Param values not valid for state 'grandChild'. Invalid params: [ dataSource ]
when I inspect $state
I see something weird.
$state.$current.params.datasource.type.pattern = /(?:option1|option2|option3)/
$state.$current.url.pattern = "/{dataSource:(?:option1|option2|option3|option4)}"
$state.$current.url._compiled[0] = /((?:option1|option2|option3))/

implement back button functionality using $state

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!

Angular ui-router passing data between subviews of the same state

How do I access other subviews in the same state. I am building a page with a toolbar on top and a sidebar and I want a button on the toolbar to open/close the sidebar and buttons in the sidebar to change the content. easy of it's all in the same controller but what I did is to use ui-router's subview feature like this:
.state('dash', {
url: '/dash/:id',
views: {
nav: {
controller: 'NavCtrl',
controllerAs: 'ctrl',
templateUrl: '/views/navbar.html'
},
sidebar: {
controller: 'SidebarCtrl',
controllerAs: 'ctrl',
templateUrl: '/views/sidebar.html'
},
content: {
controller: 'DashCtrl',
controllerAs: 'ctrl',
templateUrl: '/views/dash.html'
}
}
})
UI looks like this:
Define a resolve and use it as a place to store common data for the activated 'dash' state.
app.config(function($stateProvider) {
$stateProvider.state('dash', {
url: '/',
resolve: {
dashData: function() {
return { input: "default value" };
}
},
views: {
nav: {
controller: function() {
},
controllerAs: 'ctrl',
template: '<h3>This is the Navbar</h3>'
},
sidebar: {
controller: function(dashData) { // Inject reference to resolve object
this.dashData = dashData;
},
controllerAs: 'ctrl',
template: 'content data visible in ' +
'the sidebar: <b>{{ ctrl.dashData.input }}<b>'
},
content: {
controller: function(dashData) { // Inject reference to resolve object
this.dashData = dashData;
},
controllerAs: 'ctrl',
template: '<input type="text" ng-model="ctrl.dashData.input">' +
'This is bound to dashData.input'
}
}
})
});
Inject the shared object into each controller
app.controller('DashCtrl', function(dashData, $scope) {
$scope.dashData = dashData;
});
app.controller('... ....
I put this example in a plunker for you: http://plnkr.co/edit/8M1zXN0W5ybiB8KyxvqW?p=preview
This would be a good example of where an abstract parent state comes in handy:
An abstract state can have child states but can not get activated itself. An 'abstract' state is simply a state that can't be transitioned to. It is activated implicitly when one of its descendants are activated.
https://github.com/angular-ui/ui-router/wiki/Nested-States-and-Nested-Views#abstract-states
And then especially this usecase:
inherit $scope objects down to children
Consider the following abstract parent state and it's child state:
$stateProvider.state('root', {
abstract: true,
url: '/dash',
templateUrl: 'root.html',
controller: 'rootController'
});
$stateProvider.state('dash', {
parent: 'root',
url: '/:id',
views: {
'navbar': {
templateUrl: 'navbar.html',
controller: 'navbarController'
},
'sidebar': {
templateUrl: 'sidebar.html',
controller: 'sidebarController'
},
'content': {
templateUrl: 'content.html',
controller: 'contentController'
}
}
});
Now you can store logic (and data) you need in your childstate in the controller of the abstract parent state:
angular.module('app').controller('rootController', [
'$scope',
function ($scope) {
$scope.sidebar = {
show: true
};
$scope.items = [{
name: 'Alpha'
}, {
name: 'Bravo'
},{
name: 'Charlie'
},{
name: 'Delta'
}];
$scope.selected = $scope.items[0];
$scope.select = function (item) {
$scope.selected = item;
}
}
]);
Example of using this logic/data in a template of the child state, sidebar.html:
<ul class="nav nav-pills nav-stacked">
<li ng-repeat="item in items" role="presentation">
{{item.name}}
</li>
</ul>
Here's a complete example with your requirements, i could post all the code here but i think that would be a bit too much:
http://embed.plnkr.co/2jKJtFM0GWsylyLcAdne/
I'll gladly answer any question you may have, just holler. Good luck!
if you'd name your controllers differently with controller as, you could use the NavCtrl in the sidebarCtrl's template. Maybe use some boolean value that exists on the NavCtrl, that decides what to show in the sidebar? (from the comment)
This should work, haven't tried it though.
.state('dash', {
url: '/dash/:id',
views: {
nav: {
controller: 'NavCtrl',
controllerAs: 'navCtrl',
templateUrl: '/views/navbar.html'
},
sidebar: {
controller: 'SidebarCtrl',
controllerAs: 'sidebarCtrl',
templateUrl: '/views/sidebar.html'
},
content: {
controller: 'DashCtrl',
controllerAs: 'dashCtrl',
templateUrl: '/views/dash.html'
}
}
})
sidebarService:
angular.module('app').value('sidebarService', {show: true});
navCtrl something like this:
function(sidebarService){
var vm = this;
vm.toggleSideBar = function(){sidebarService.show = !sidebarService.show;}//used in navbar.html
}
sidebarCtrl:
function(sidebarService){
var vm = this;
vm.showSideBar= sidebarService;
}
and then in sidebar.html you use the sidebar value service:
<div ng-if="sidebarCtrl.showSideBar.show">
<!--SideBar-->
</div
You can use events to communicate between controllers. Check the AngularJS documentation for $scope.$broadcast and `$scope.$on : https://docs.angularjs.org/api/ng/type/$rootScope.Scope

Angular Js - ui-router with Breadcrumbs

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

ui router - abstract state inside à normal one (3 levels)

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.

Resources