I'm trying to create a state which acts like a popup, i.e it doesn't clear the current state, it just pops over it without completely destroying the current state (So that the user can gain access to it by dismissing the popup).
Heavily simplified, my applications routes is something like the following:
angular.module('test', ['ui.router'])
.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('login', {
url: '/login',
template: '<button><a ui-sref="authenticated.home">Login</a></button>'
})
.state('authenticated', {
url: '/authenticated',
template: '<p>We are now authenticated</p>' +
'<a ui-sref="authenticated.home">home</a>' +
'<a ui-sref="authenticated.statistics">statistics</a>' +
'<a ui-sref="authenticated.popup">Popup!</a>' +
'<div ui-view></div>' +
'<div ui-view="popup"></div>'
})
.state('authenticated.home', {
url: '^/home',
template: '<p>We are in home. <br><input></p>'
})
.state('authenticated.statistics', {
url: '^/statistics',
template: '<p>We are in statistics. <br><input></p>'
})
.state('authenticated.popup', {
url: '^/popup',
views: {
popup: {
template: '<div id="popup"><p>popup is up</p>' +
'<a ui-sref="authenticated.home">close</a>' +
'</div>'
}
}
});
$urlRouterProvider.otherwise('/login');
}
]);
a {
margin-right: 20px;
text-decoration: none;
}
#popup {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
background: #000;
color: #fff;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"></script>
<div ng-app="test">
<div ui-view>
</div>
</div>
User is presented with a login screen
Once logged in, user is redirected to authenticated.home state. The authenticated parent state holds a navigation menu and <ui-view> to attach subviews
User can use this navigation to navigate around the application to other routes like authenticated.statistics, authenticated.popup.
The problem is that, when I simply move to the popup state, even though I have specified popup view inside it's views object, it clears the other ui-view (makes sense though, because we're no longer in a state that matches it).
One solution I can think of is to use something like ui-router-extras
to go back to previous state, the problem with this is that any changes the user might have been making in the previous states will be lost.
Another solution will be to have the template of popup in the authenticated states template and show/hide it. But the problem with this is that, the popup should be a bookmark-able state, which loads data from server based on state params.
Is there a better approach to create a state that acts like a popup over current state? maybe by changing the template structure or using something like abstract-states that I haven't thought of?
The sticky states add on from ui-router-extras should be what you're looking for. Gives you the ability to create sticky/parallel states so it should allow you to create a popup state without affecting the original state you were in.
I haven't experimented enough with it to know all the details but the main idea is to move all you main states under a root app state and set sticky to true:
$stateProvider.state('app', {
url: '',
views: {
'app': {
templateUrl: 'app.html',
controller: 'mainCtrl',
}
},
sticky: true
});
$stateProvider.state('app.account', {
url: '/account',
templateUrl: 'account.html'
});
$stateProvider.state('app.account.stuff', {
url: '/stuff',
template: "<h3>Here's my stuff:</h3><ul><li>stuff 1</li><li>stuff 2</li><li>stuff 3</li></ul>"
});
After that add your modal state as a sibling (so not as a child of the root app state)
$stateProvider.state('modal', {
url: '/modal',
views: {
'modal': {
templateUrl: 'modal.html'
}
}
});
I took the example provided in the docs and made a few edits to add controllers and simplify it: http://plnkr.co/edit/4JGdIcDUQs0Za4fBaLj1?p=preview
sounds like something abstract states could fix.
changing your "authenticated" state to an abstract parent and attaching a controller to it can allow you to axx data being transformed by child states. Also you could just make a separate controller for your data and drop an ng-controller on the body tag.
If you're still having probs implementing, how about you use LocalStorage to persist the data you want?
Update2 Found a post and changed the plnk on updating ng-model as you type, but i see why OP wants a popup.
Edit
Plnkr Shared State Example
I'll try to find a good example, but here's some pseudo code for now.
.state('authenticated', {
abstract: true,
url: '/authenticated',
controller: 'AuthStateController',
template: '<p>We are now authenticated</p>' +
'<a ui-sref="authenticated.home">home</a>' +
'<a ui-sref="authenticated.statistics">statistics</a>' +
'<a ui-sref="authenticated.popup">Popup!</a>' +
'<div ui-view></div>' +
'<div ui-view="popup"></div>'
})
I believe with or without {abstract: true} your popup state should have access to data inside of 'AuthStateController'. The resolve stuff may be an overkill solution, but might give you an idea of how to use the controllers data more efficiently.. if all else fails .. build a service to handle the data.
.state('authenticated.popup', {
url: '^/popup',
views: {
popup: {
resolve: {
ctrl: 'AuthStateController',
data: function(ctrl) {
return ctrl.data_for_popup;
}
}
template: '<div id="popup"><p>popup is up</p>' +
'<a ui-sref="authenticated.home">close</a>' +
'</div>'
}
}
})
Probably what you are looking for is a different on your main page.
You need to keep 2 ui-view on your index.html or your base template.
And then while loading the states do something like:
$stateProvider.state('stateNAme', {
url: '/name',
views: {
"main": {
controller: '1Controller',
templateUrl: 'tpl1.html'
},
"sidebar#": {
controller: '2Controller',
templateUrl: 'tpl2.html'
}
}
Read here about the multiple named views
Related
Get the context, angular, ui-router, nothing special, a root view built with 3 named ui-views.
so in index.html we have
<body>
<div ui-view='left'>
<div ui-view='center'>
<div ui-view='right'>
</body>
my route looks like
$stateProvider
.state('main', {
url: '/',
views: {
'left': {templateUrl: 'foo.html'},
'center': {templateUrl: 'bar.html'},
'right': {templateUrl: 'xyz.html'}
}
})
.state('main.b', {
url: '/b',
params: { foo: {value: 'bar'} }
views: { 'right#': {templateUrl: '123.html'} } // I wish to update $stateParams in 'left#' view
})
.state('main.c', {
url: '/c',
params: ...
views: { 'left#': ..., 'center#': ..., 'right#': .. }
});
Is there a way in going to b state to update the $stateParams in the 'center' and 'left' view?? I can get it using a service but i need to add a $watch to the variable I need and it looks a little bit hacky to me.
Going into c state I can actually get what I want, but the view is reloaded, and i wish to avoid this behaviour cause i have a canvas in the 'left' view.
You could use the following to go to a specific route without reloading the views:
$state.go('.', {parm1: 1}, {notify: false});
The last object literal represents the options which you can pass along to go. If you set notify to false, this will actually prevent the controllers from being reinitialized. The . at the beginning is the absolute state name or relative state path you wanna go to.
The important thing is the notify though.
I think that using "Dynamic params" is now a better solution:
When dynamic is true, changes to the parameter value will not cause the state to be entered/exited. The resolves will not be re-fetched, nor will views be reloaded.
$stateProvider.state('search', {
url: '/search?city&startDate&endDate',
templateUrl: 'some/url/template.html',
params: {
city: {
value: 'Boston',
dynamic: true
}
}
}
and then:
$state.go('.', {city: 'London'});
https://ui-router.github.io/ng1/docs/latest/interfaces/params.paramdeclaration.html#dynamic
https://github.com/angular-ui/ui-router/issues/2709
Quoting #christopherthielen from https://github.com/angular-ui/ui-router/issues/1758#issuecomment-205060258:
using notify: false is almost never a good idea, and is now
deprecated. Use reloadOnSearch if you must.
You can also try dynamic parameters in the 1.0 version (currently
1.0.0-alpha.3). In your state, configure a parameter as dynamic and implement the uiOnParamsChanged callback :
.state('foo', {
url: '/:fooId',
params: { fooId: { dynamic: true } },
controller: function() {
this.uiOnParamsChanged = function(changedParams, $transition$) {
// do something with the changed params
// you can inspect $transition$ to see the what triggered the dynamic params change.
}
}
});
For a demo, have a look at this plunker: http://plnkr.co/edit/T2scUAq0ljnZhPqkIshB?p=preview
I have a very basic angular ui router module set up for a website I am working on. It is as follows:
var myApp = angular.module('main', ['ui.router']);
myApp.config(function($stateProvider){
$stateProvider
.state('home', {
url: '/home',
templateUrl: './home_screen.htm'
})
.state('nowshowing1', {
url: '/nowshowing1',
templateUrl: './now_showing.htm'
})
.state('nowshowing2', {
url: '/nowshowing2',
templateUrl: './now_showing.htm'
})
.state('comingsoon1', {
url: '/comingsoon1',
templateUrl: './now_showing.htm'
})
.state('comingsoon2', {
url: '/comingsoon2',
templateUrl: './now_showing.htm'
})
.state('concessions', {
url: '/concessions',
templateUrl: './home_screen.htm'
})
.state('freemovies', {
url: '/freemovies',
templateUrl: './FreeMovies.htm'
})
.state('pictures', {
url: '/pictures',
templateUrl: './pictures.htm'
})
.state('contributors', {
url: '/contributors',
templateUrl: './contributors.htm'
})
.state('about', {
url: '/about',
templateUrl: './home_screen.htm'
});
});
I have a bootstrap carousel and my ui-view inside the carousel. Right now, my website works when using my bootstrap navbar to navigate to each page, where each page is an html page, but also a 'slide' in the carousel. I want the user to be able to use the arrow buttons on the side of the screen to switch to the next and previous 'slide' or page.
I am very new to angular and angular ui router, and I have not been able to find useful documentation on how I might not only access the current, next, and or previous states in my stateProvider, but how to call the method that will access those from my html.
I tried a controller like this:
myApp.controller('StateChangeCtrl', function($scope, $state){
$scope.previousState = function(){
console.log('Previous State!', $scope);
}
});
Then tried to call previousState() onclick of the arrow buttons in the carousel, but got a not defined error.
How/where would I access the current, next, and or previous state of the website if my pages go in the order of how I declared them in the stateProvider, and then how would I call the function from html that would do that?
If I could even access the current state somewhere, I can hardcode what the next and previous states will be (I assume I'd use $state.go() to change states) because I know the order of my html pages and there are not a ton of them...
I'd like to set the title based on the state in my angular app. It works nicely, but if there's no title defined on the child state, I'd like to fall back to the parent's title property.
This is what I have so far:
In my app run block:
...
$rootScope.$on('$stateChangeSuccess', function (event, current, previous) {
if (current.hasOwnProperty('title')) {
$rootScope.title = current.title;
}
});
...
In my module's route
.state('main.parent', {
url: '/parent',
controller: 'ParentController',
controllerAs: 'vm',
templateUrl: 'app/parent.html',
title: 'Parent',
})
.state('main.parent.child', {
url: '/child',
controller: 'ChildController',
controllerAs: 'vm',
templateUrl: 'app/child.html'
})
During the code inspection, I found that $stateChangeSuccess is triggered twice as expected, first for the parent and then for the child. I tried to access current.parent when the child was called, but it has no such property. Is there any way to access it?
UPDATE
I saw these questions:
get parent state from ui-router $stateChangeStart,
get parent state from ui-router $stateChangeStart
But none of them had a clear answer to this problem (except the state splitting hack in the latter one but I'd rather avoid that).
The both ways are hacks a bit.
Don't forget to import $state (and $stateParams for 2nd variant) providers in your controller.
You can use:
$state.$current.parent.self.title
or
var tree = current.name.split('.');
tree.splice(tree.length-1, 1);
var parent = tree.join('.');
$state.get(parent, $stateParams);
It seems that you just need the title property to fall back to the parent's title property if there's no title defined on the child state, I will suggest you to use this repository: angular-ui-router-title.
So finally I came up with the following solution:
I store title inside the state's data object, because from $state you can't access the parent's custom properties, only the ones stored in data (which makes sense - not to pollute the state definition with custom properties).
.state('main.parent', {
url: '/parent',
controller: 'ParentController',
controllerAs: 'vm',
templateUrl: 'app/parent.html',
data: {
title: 'Parent'
}
})
.state('main.parent.child', {
url: '/child',
controller: 'ChildController',
controllerAs: 'vm',
templateUrl: 'app/child.html'
})
And I changed the runblock as follows:
...
$rootScope.$on('$stateChangeSuccess', function (event, toState, fromState) {
var current = $state.$current;
if (current.data.hasOwnProperty('title')) {
$rootScope.title = current.data.title;
} else if(current.parent && current.parent.data.hasOwnProperty('title')) {
$rootScope.title = current.parent.data.title;
} else {
$rootScope.title = null;
}
});
...
This is not the perfect solution yet, as in the case of multiple nested states it'd be nice to fall back to the title of the youngest ancestor's title but it does it for now.
Simply below code is working for me
$state.$current.parent
Looking at the UI-Router documentation in the "to" section, you could do something like below:
$state.go('^');
to go back to the parent state.
I want to have a nested view with UI router, which I've done before (see image) with a main section and then a nav which loads sub-sections into the nested UI-View. This I can do, no issues.
My question is: this time I need to have the initial child state not show to the user until a button is clicked, like this:
Can I do this? Or is it better to load the "baseball" view but hide it and the nav with ng-hide?
UPDATE
Someone asked how I would do the simple nested states in a case like this:
(function() {
'use strict';
angular.module('elements').config(function($stateProvider) {
$stateProvider.state('elements', {
url: '/elements',
abstract: true,
templateUrl: 'modules/elements/templates/elements.html',
controller: 'ElementsController as elements'
})
.state('elements.buttons', {
url: '/elements/buttons',
templateUrl: 'modules/elements/templates/elements-buttons.html'
})
.state('elements.accordion', {
url: '/elements/accordion',
templateUrl: 'modules/elements/templates/elements-accordion.html',
controller: 'AccordionController as accordion'
})
.state('elements.colorcharts', {
url: '/elements/colorcharts',
templateUrl: 'modules/elements/templates/elements-colors-charts.html',
controller: 'ChartColorsController as charts'
})
.state('elements.grid', {
url: '/elements/grid',
templateUrl: 'modules/elements/templates/elements-grid.html'
});
});
})();
Yes it is absolutely possible. I usually accomplish this by using programmatically defined states, which looks like it should work for your situation.
If you have a state for baseball then you could control it as such:
state config
.state('baseball', {
url: '/views/baseball',
template: 'imabaseball!'
})
html
<div ui-view="{{state}}">
<button ng-click="state = 'baseball'">Show Baseball</button>
Then the view in question would not be rendered until the user clicked the button
edit: Based on the answer by #actor2019 I want to update my question to better explain the problem:
Using Angular UI-Router(v0.0.2), I've setup the app to properly navigate between main "pages"/state, while inheriting the base state.
Index.html:
<div ui-view></div>
base.html:
<!-- Header -->
<div>
<!-- Header markup -->
<!-- Search View -->
<div ui-view="search"></div>
</div>
<!-- Page Content view -->
<div ui-view></div>
The issue is here in the app.js file. When I add the views parameter to the base state, everything stops working(100% blank page). Without that parameter, the page renders correctly, but I have no search view.
app.js:
$urlRouterProvider.otherwise('/');
//
// Now set up the states
$stateProvider
.state('base', {
abstract: true,
templateUrl: 'views/base.html',
views: {
"search": {
templateUrl: "views/search.html"
}
}
})
.state('base.home', {
url: "/",
templateUrl: "views/home.html"
})
.state('base.page2', {
url: "/page2",
templateUrl: "views/page2.html"
});
How do I add views to this parent 'base' state?
UPDATE:
The problem with #actor2019's answer here is that the search view gets reinitialized when the state changes. I'd like the views off the base level to persist through state changes.
The first obvious mistake:
You can't specify controller and template on the state while your using views. They are mutually exclusive...
This is because when there is no "views" but a controller and template on the state, UI-Router automatically creates the "views" property and pulls those properties to an "empty" view...
.state('base', {
abstract: true,
templateUrl: 'views/base.html', //Can't do this
views: { // when this is there.
"search": {
templateUrl: "views/search.html"
}
}
})
Instead do:
.state('base', {
abstract: true,
views: {
"": {
templateUrl: 'views/base.html'
},
"search": {
templateUrl: "views/search.html"
}
}
})
Second problem:
How views targeting works with nested views etc. is not very logical, it may work well if you restrict your self to one view in one view all the way down, but ones you start working with multiple named views it all gets confusing... Add unnamed views on top and many people gets lost...
The way views work in UI-Router is the worst part of UI-Router...
Given you example I am not even entirely sure of the way to target the search view from your abstract parent state... Might be:
.state('base', {
abstract: true,
views: {
"": {
templateUrl: 'views/base.html'
},
"search#base": {
templateUrl: "views/search.html"
}
}
})
If it can even be made to work... Alternatively you can move the search view out of base.html, but I guess you added it in there for a reason.
The whole view concept is the biggest reason why I ended up writing https://github.com/dotJEM/angular-routing instead.
The Child state should be home.search instead of header.search. In your case, you may want to write some abstract state to hold the layout,
base.html
<div class="row-fluid">
<div class="header">
<div class="span3" ui-view="logo"></div>
<div class="span9" ui-view="menu"></div>
</div>
</div>
<div class="row-fluid">
<div class="content">
<div class="span2" ui-view="sidebar"></div>
<div class="span10" ui-view="entry"></div>
</div>
</div>
in app.js
$stateProvider
.state('base',{
abstract:true,
url:'/',
templateUrl: viewBase+'base.html'
})
.state('base.main',{
url:'',
views:{
"logo":{
templateUrl:viewBase+'main/logo.html'
},
"menu":{
templateUrl:viewBase+'main/menu.html'
},
"sidebar":{
templateUrl:viewBase+'main/sidebar.html'
},
"entry":{
templateUrl: viewBase+'main/entry.html'
}
}})
According to the ui-router documentation, when the application is in a particular state—when a state is "active"—all of its ancestor states are implicitly active as well. So, for example, when the "contacts.list" state is active, the "contacts" state is implicitly active as well, because it's the parent state to "contacts.list". Child states will load their templates into their parent's ui-view. I'd reccomend looking over the section of their documentation entitled Nested States & Views to gain a fuller understanding of how to do this.
In the code you have provided us here, the parent state of the search template is home, while
.state('header.search', {
templateUrl: "views/search.html",
controller: "SearchCtrl"
})
implies that the parent state of the search template should be header in order for the view to get loaded correctly. So, I believe the following changes to your app.js will fix your issue.
app.js
$stateProvider
.state('home', {
url: "/",
views: {
'': {
templateUrl: "views/mainContent.html",
controller: "MainCtrl"
},
'header': {
templateUrl: "views/header.html"
},
'footer': {
templateUrl: "views/footer.html"
},
}
})
.state('home.search', {
views: {
'search': {
templateUrl: "views/search.html",
controller: "SearchCtrl"
}
})
.state('anotherPage', {
url: "/anotherPage",
templateUrl: "views/anotherPage.html"
});
This works for me.
$stateProvider
.state('base', {
abstract: true,
url:'/',
templateUrl: 'views/base.html'
})
.state('base.home', {
url: "",
views: {
"search#base": {
templateUrl: "views/searchOfHome.html"
}
//content#base, contentOfHome.html
}
})
.state('base.page2', {
url: "page2",
views: {
"search#base": {
templateUrl: "views/searchOfPage2.html"
}
//content#base, contentOfPage2.html
});
If 'base' is the root state, you don't need the '#base'