Angular UI Router, Sub views get unset after a change of state - angularjs

I debated a while on this but I got a Plunk that reproduce it.
I have a state "Contact" that get loaded by default. with $state.transitionTo
Inside that state I have some views, they all get loaded and everything work.
If I click to change the state to "Home" by default or by "ui-sref" and in the "Home" state/template I have ui-sref="contacts". When we click back to set the state to contacts it should work, but all the sub views are now not being called properly.
It seems that when ui-sref call the state this one behave differently that when it is loaded by default.
Why $state.transitionTo(''); seems to work differently than ui-sref.
<script>
var myapp = angular.module('myapp', ["ui.router"])
myapp.config(function($stateProvider, $urlRouterProvider){
// For any unmatched url, send to /
$urlRouterProvider.otherwise("/")
$stateProvider
.state('home', {
templateUrl: 'home.html',
controller: function($scope){
}
})
.state('contacts', {
templateUrl: 'contacts.html',
controller: function($scope){
}
})
.state('contacts.list', {
views:{
"":{
template: '<h1>Contact.List Working wi no Data defined.</h1>'
},
"stateSubView":{
template: '<h2>StateSubView Working</h2>'
},
"absolute#":{
template: '<h2>Absolute item</h2>'
}
}
});
});
myapp.controller('MainCtrl', function ($state) {
$state.transitionTo('contacts.list');
})
Q2:
Why is the Absolute tag that is under contact work when I add the view in the Index, but is not working when it is inside the contact.html file. Absolute reference work only with the Index and not if called everywhere?
"absolute#":{
template: '<h2>Absolute item</h2>'
}

I saw that in index.html you have an empty ui-view tag. What do you expect to go there? I think you can not do this. The router just doesn't know with which state (home or contacts) it should replace. Apparently it picks the second one (contacts). I'd suggest to put url: '/' in the home state and you'll see the difference.
This is for sure one issue.
Other than that:
You can't simply access views from contacts.list in contacts afaik.

The empty ui-view work as a wild card and can be use to switch across multiple route even if we have nested element. But if we have a nested view contact.list it can only be access if we put the whole path in ui-sref="contacts.list" because the list child of contact cannot be access only by using ui-sref="contacts"

Related

Navigate back to state without reloading template

I have done some research but couldn't find a definitive answer. I have main application area where I load different screens. From one screen I want to open a page that would cover the whole screen. So, navigating to 'viewreport' does exactly that. And when I click on Browser's Back button or have my own Back button on the whole screen page I want to get back to the previous state without reloading its template and controller. Another words, I want to see all selections I have done prior opening the whole screen page. Here is my state configuration:
$stateProvider
.state('body', {
url: '/',
abstract: true,
template: '<div ui-view />'
})
.state('viewreport', {
url: 'viewreport',
templateUrl: 'wholescreen.html',
controller: 'wholescreenController'
});
I am loading different modules into the main 'body' state which might look like this:
function ($stateProvider) {
$stateProvider.state('body.htmlreports', {
templateUrl: function ($stateParams) {
return 'htmlReports.html';
},
controller: 'htmlReportsController',
url: 'htmlreports',
}).state('body.htmlreports.reportarea', {
templateUrl: 'htmlReportParams.html',
controller: 'htmlReportParamsController',
});
I am navigating to viewreport state from htmlReportParamsController controler. The new page then opens into the whole screen. That part works fine. But navigating back to htmlreports when clicking on the Browser's Back button will reload 'body.htmlreports' state. Is there a way of getting back to it without reloading its template?
Update. Why I think it's not a duplicate.
I tried what's suggested in it before posting. This: $state.transitionTo('yourState', params, {notify: false});
still reloads 'yourState'. Also the use case in the provided link is not exactly as mine. Because the OP uses edit mode for already loaded view while I am loading a new view over the the whole screen.
Thanks
Use
$window.history.back();
Add $window in dependency injections of your controller. This will refresh your page and wont reload data we selected.
Please maintain states like this
function ($stateProvider) {
$stateProvider.state('body.htmlreports', {
templateUrl: function ($stateParams) {
return 'htmlReports.html';
},
controller: 'htmlReportsController',
url: 'htmlreports',
}).state('body.htmlreports.reportarea', {
templateUrl: 'htmlReportParams.html',
controller: 'htmlReportParamsController',
}).state('body.htmlreports.reportarea.viewreport', {
url: 'viewreport'
});

Multiple named views with dynamic routing in angularjs

Edit: Here is the complete code at Plunker. Though I can not c anything in execution but same code working at local. However gives a console error though
It all works perfect. But due to :id in /news/:id/, i am getting jquery/angular errors in console which can not be tracked anywhere in my code
I can not c What i am doing wrong.
Edit: Solved plunker https://plnkr.co/edit/FWcuBgGpVdMj3CroFrYJ
First of all you are trying to use ui-router but you're including ngRoute script in your plunker. Change it to
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.1/angular-ui-router.min.js"></script>
Then everything should work fine!
I suggest you a few changes...
1. Use ui-sref instead of href because it's much easier to define
ui-sref="post({id:1})" which turns into href="#/news/1"
If you would like to change url some day, then you will have to just change your route file, not each href.
$stateProvider
.state('post', {
url: "news/:id"
or
$stateProvider
.state('post', {
url: "archive/:id"
or
$stateProvider
.state('post', {
url: "whatever/:id"
2. Use abstract state
In your example it's a way better to define abstract state which holds header, content and footer - it's a typical use case.
ui-router
Abstract States
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.
Some examples of how you might use an abstract state are:
To prepend a url to all child state urls. To insert a template with
its own ui-view(s) that its child states will populate. Optionally
assign a controller to the template. The controller must pair to a
template. Additionally, inherit $scope objects down to children, just
understand that this happens via the view hierarchy, not the state
hierarchy. To provide resolved dependencies via resolve for use by
child states. To provide inherited custom data via data for use by
child states or an event listener. To run an onEnter or onExit
function that may modify the application in someway. Any combination
of the above. Remember: Abstract states still need their own
for their children to plug into. So if you are using an
abstract state just to prepend a url, set resolves/data, or run an
onEnter/Exit function, then you'll additionally need to set template:
"".
Here's a plunker which shows how I would do it.
https://plnkr.co/edit/5FvJaelyxdl5MuALt5VY?p=preview
Hope it helps.
Look at the documentation for ui router named views,
You can use following syntax for using multiple views
$stateProvider
.state('state',{
url: '',
views: {
'header': {
templateUrl: 'views/header.html',
controller: 'headerCtrl'
},
'content': {
template: '<div ui-view=" "></div>', //<-- child templates loaded to here
},
'footer': {
templateUrl: 'views/footer.html',
controller: 'footerCtrl'
}
}
})
.state('state.post', {
url: 'news/:id/:KeyWords'
templateUrl: 'views/post.html' //<-- This goes into content's ui-view
});
I'm guessing you want to keep the header and footer and change content views.
You can achieve this by making this state as parent to all other states
suppose
.state('main',{
abstract: true,
views: {
'header': ... ,
'content': {
template: '<ui-view></ui-view>',
}
'footer': ...
}
})
then all the child views will load their views in the ,
ex: in main.child etc, your template will load in the content's <ui-view></ui-view> tag
If you need to use a custom template depending on keywords you can do the following:
.config(['$routeProvider',
function($routeProvider, $routeParams) {
$routeProvider
.when('/news/:id/:keyWords', {
template: '<div ng-include="url"></div>',
controller: "exampleController"
})
then in the exampleController
function($routeParams, $scope) {
$scope.url = $routeParams.keyWords;
}

AngularJS - Default view in <ui-view> element

Is there any problem putting default code inside of a <ui-view> element. It appears to work, but I can't find anything saying one way or another if it's okay to use or not.
My current usage is I want the "default" view to be a list of items. Upon clicking one of those items, it switches to an "editor" child state, which replaces the <ui-view> content with the editor child.
Are there any gotchas I should be aware of before continuing with this approach?
Here is an example of what I'm looking at:
routes.js:
.config(($stateProvider) => {
$stateProvider
.state('admin', {
url: '/admin',
templateUrl: 'admin.html'
})
.state('admin.items', {
url: '/admin/items',
templateUrl: 'admin.items.html'
})
});
admin.html:
<ui-view>Default Stuff Here</ui-view>
items.html:
<p ng-repeat="item in items">{{item}}</p>
Now, I know I can do:
.state('admin.default', {
url: '',
templateUrl: 'admin.default.html'
});
And then put that would show in ui-view. However, that needlessly adds a new state and template file, when it seems to work just fine putting the would-be contents of admin.default.html directly into the ui-view of admin.html.
In my case, I'm not talking about a completely stateless option using otherwise(), I'm talking about a defined parent state with a default child state.
We generally do not put anything inside <ui-view></ui-view>, instead we create a default state and use that.
routerApp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home.html'
})
.state('about', {
// we'll get to this in a bit
});
});
Here the default one is /home, like that you can create a default one.
First, As said in the other answer, we never give any data within the ui-view.. But while dealing with the ui-router, you should give all the specific states in .config()
app.config(function($stateProvider,$urlRouterProvider){
$stateProvider
.state('home',{
url : '/home',
controller: 'homeCtrl',
templateUrl : 'home.html'
}).state('login',{
url : '/login',
controller: 'loginCtrl',
templateUrl : 'login.html'
});
//AND HERE YOU PROVIDE THE DEFAULT PLACE WHERE THE USER IS TO BE REDIRECTED
//IN CASE OF IMPROPER URL
$urlRouterProvider.otherwise('/login');
});
Secondly, If you want to use the <ui-view></ui-view> to hold some data, you certainly can put some data within them.. But make sure that from the state you define, It should not supply any template or templateUrl. So the data within the <ui-view></ui-view> stays as it is.
And third.. Why to keep a default state..
in case you are defining a state admin in your config(), and then in your admin.routes.js file you can define a state as follows..
.state('admin.login',{
url:'',
controller:'loginCtrl',
templateUrl:'admin.login.html'
});
So in case the url is YOURURL/admin It will directly open the login page by default.. so there is no chance of redundancy...

Preventing parent state from reloading on child state change

I am trying to implement a tabbed interface akin to this: http://odetocode.com/blogs/scott/archive/2014/04/14/deep-linking-a-tabbed-ui-with-angularjs.aspx
However, on my state change, the controller of the parent state seems to be reinitialized (or a new $scope is created?)
There are two major differences between the example plunkr and my project.
I use a parameter in my url
I resolve different data on the state change for each tab (removing this does nothing).
I am not using ui-bootstrap for the tabs but am triggering a $state.go on ng-click of the tab.
I experimented with the above plunkr and added a dropdown to the parent state; however the parent dropdown values seem to persist when the child states change. I am not too concerned with the child states and will probably end up using sticky states anyways.
I am using wondering if I am doing something fundamentally wrong before I try and add another package to my project.
here is a rough plunkr of what I am trying to do: http://plnkr.co/edit/TmRQN5K8OEc8vHG84G5z?p=preview
here is my config:
app.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.when('/main',
function ($state) {
$state.go('parent.tab1', { main_id: '00008' });
});
$stateProvider
//Handle States Here
.state('parent', {
abstract: true,
url: '/parent?main_id',
templateUrl: "main.html",
controller: 'Main_Controller',
resolve: {
//Calls to API
}
})
.state('parent.tab1', {
url: "/applications",
templateUrl: "tab1.html",
controller:'Tab1Ctrl',
resolve: {
//Get some different data from an API
},
})
.state('parent.tab2', {
url: "/phasing",
templateUrl: "tab2.html",
controller: 'Tab2Ctrl',
resolve: {
//More API Data
}
});
});
I've made your plunker working here
$urlRouterProvider
//.when('/main',
.when('',
function ($state) {
$state.go('parent.tab1', { main_id: '00008' })
});
Also there is a change in main.html, which does not use ng-controller any more. We just have to pass the proper Controller name
$stateProvider
//Handle States Here
.state('parent', {
abstract: true,
url: '/parent?main_id',
templateUrl: "main.html",
controller: 'MainController',
resolve: {
//Calls to API
}
})
...
// MainController
// these two names should fit
app.controller("MainController", function($rootScope, $scope, $state) {
So now, it is working, and let's discuss
I use a parameter in my url
I resolve different data on the state change for each tab (removing this does nothing).
I am not using ui-bootstrap for the tabs but am triggering a $state.go on ng-click of the tab.
Quick answers:
parameter in url exists, e.g. #/parent/tab1?main_id=8000
resolve is trigerred for each controller if controller is reinstantiated. That happens when we navigate to that state (among tabs)
no need to use $state.go, I used:
a snippet:
<a ui-sref="parent.tab1({main_id:'00008'})"> go to tab1 with main_id '00008'</a><br />
<a ui-sref="parent.tab2({main_id:'00008'})"> go to tab2 with main_id '00008'</a><br />
<a ui-sref="parent.tab3({main_id:'00008'})"> go to tab3 with main_id '00008'</a><br />
Check it here

Back button with nested states in Angular Router

I have an AngularJS application that makes use of the new, state-based ui-router. I have three different views in my application, where one is a top-level views, and the other two are nested ones.
The structure basically is as follows:
/ => Top-level view
/foo => Abstract view, loads a view that contains a ui-view placeholder
/foo/bar => View for the placeholder
/foo/baz => View for the placeholder
The router is set up as following:
app.config(['$urlRouterProvider', '$stateProvider', function ($urlRouterProvider, $stateProvider) {
'use strict';
$urlRouterProvider
.when('/bar', '/foo/bar')
.otherwise('/');
$stateProvider
.state('home', {
url: '/',
views: {
'': {
controller: 'homeController',
templateUrl: '/home/homeLayout.html',
},
'firstHomeView#home': {
templateUrl: '/home/firstHomeView.html'
},
'secondHomeView#home': {
templateUrl: '/homme/secondHomeView.html'
}
}
})
.state('foo', {
abstract: true,
templateUrl: '/foo/fooLayout.html',
controller: 'fooController'
})
.state('foo.bar', {
url: '/foo/bar',
templateUrl: '/foo/barView.html',
controller: 'barController'
})
.state('foo.baz', {
url: '/foo/baz',
templateUrl: '/foo/bazView.html',
controller: 'bazController'
});
The problem is, that basically everything works as expected when you click around or manually type in urls, but that it does not work when using the back / forward buttons of the browser.
E.g., is you go to /foo, you are taken to /foo/bar, as expected. When you then click on a link to go to /foo/baz, everything is fine. Then click a link that takes you to /, and everything is still fine.
If you now hit the back button, you are taken back to /foo/baz (which is correct), but only the /foo/fooLayout.html view is rendered, not its sub-view /foo/bazView.html. The strange thing is now that if you hit the back button again, you are taken to /foo/bar and it renders correctly, including its subview! It seems as if nested views weren't recognized when using the back button, at least, if you enter an abstract view at the same time.
$locationProvider.html5Mode is not enabled, but enabling it doesn't make any difference.
I am using AngularJS 1.0.5 and ui-router 0.0.1-2013-03-20.
Any ideas what might cause this issue, and how I might solve it?
I found the error: In the view fooLayout.html I was using ng-view instead of ui-view. Once I changed that, everything was fine :-)

Resources