I have a home page with a classes section. I have a navbar with a classes link. If you are on the page , clicking the link should scroll down to the classes section. If you are on another page, clicking the link should bring you back to the home page and then scroll to the classes section.
I am having issues with the way I handle my state - for now if I am on another page my OnEnter function blocks by saying Cannot 'read property 'top' of undefined'. This being said, I am probably mo writing my states properly.
$stateProvider
.state('home', {
url : '/',
templateUrl:'components/home/home.html',
controller: 'HomeCtrl'
})
.state('classes', {
url: '/',
templateUrl:'components/home/home.html',
controller: 'HomeCtrl',
onEnter: function () {
$('html, body').animate({
scrollTop: $("#classes").offset().top
}, 1000);
}
})
This happens because when you enter the state, the view (HTML) might not yet be loaded and added to the DOM. You may fix the issue with viewContentLoaded event. See:
https://github.com/angular-ui/ui-router/wiki#view-load-events
I think you can also use ui-router's own autoscroll directive to achieve the same effect: https://github.com/angular-ui/ui-router/wiki/Quick-Reference#autoscroll
Related
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'
});
I want to temporarily change the browser url when the ui bootstrap modal is opened ( The page behind should remain as is, only the url changes ). When the modal is closed the url should be reverted back to the original one.
Steps :
User loads the page
url : xyz.com/home
User clicks a link opens a modal
url : xyz.com/detail/123
possible solution : changing url with html5 push state
problem : Angular ui-router tries to run its routes as per the changed url, eventually changing the background page.
User closes the modal
url : xyz.com/home
possible solution : html5 pop state
problem : Reloads the background page, which kills the purpose
Example implementation : Pinterest pins and their pin details popup.
You can use ui-router-extras sticky state to solve your problem. There is simple example with modal by the link. You should create two named views, one for main content (background) and one for modal.
<div ui-view="app"></div>
<div ui-view="modal"></div>
Mark the state, from what you want to access to modal as sticky: true in route definition.
.state('main', {
abstract: true,
url: '/',
templateUrl: '_layout.html'
})
.state('main.index', {
url: '',
sticky: true,
views: {
'app': {
templateUrl: 'index.html'
}
}
})
.state('main.login', {
url: 'login/',
views: {
'modal': {
templateUrl: 'login.html'
}
}
})
Also add an event for stateChangeSuccess:
$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
if ((from.views && !from.views.modal) || !from.views) {
$rootScope.from = from;
$rootScope.fromParams = fromParams;
}
});
so, when you need to close modal, you can just
$state.go($rootScope.from, $rootScope.fromParams);
There is small problem for that solution. If you reload page on the modal state, then the app ui-view will be empty.
This can be achieved by having a nested state and triggering the modal using onEnter callback:
$stateProvider
.state('contacts', {
url: '/home',
templateUrl: 'home.html',
controller: function($scope, MyService){
$scope.contacts = MyService.getContacts();
}
})
.state('contacts.details', {
url: "^/details/:id", // using the absolute url to not have the "/home" prepended
onEnter: function($state, $uibModal) {
var modal = $uibModal.open({
templateUrl: 'details.html',
controller: function($scope, $stateParams, MyService) {
// get data from service by url parameter
$scope.contact = MyService.getContact($stateParams.id);
}
});
modal.result.finally(function() {
$state.go('^'); // activate the parent state when modal is closed or dismissed
});
}
});
This technique is described in the ui-router's FAQ.
Here the plunk. In this example the modal's scope is created as a child of the $rootScope - the default $uibModal's behavior when no scope is passed to it. In this case we should use the service in the modal's controller to obtain the data by url parameter.
To have master and details URLs look like these - xyz.com/home and xyz.com/detail/123 - we should use the absolute URL (^/details/:id) in the child state.
Using this solution you can open the detail URLs directly and still have both, master and detail states, activated properly, so sharing the detail URL is possible.
I think you can achive that with ngSilent module
https://github.com/garakh/ngSilent
using $ngSilentLocation.silent('/new/path/');
(once you open modal and again after closing it)
Managed to implement this using https://github.com/christopherthielen/ui-router-extras/tree/gh-pages/example/stickymodal
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"
I'm having trouble with a simple ui-router sample I have set up. I have a company page, whose default sub-state should show CompanyProfile, but it defaults to nothing until I click profile. Once I clicked employees, I have to click profile twice to get it to show again. Ideally I want ui-sref="company()" and ui-sref="company.profile()" to display the same screens. It seems like I'm missing something small..
Here's the plnkr:
http://plnkr.co/edit/A3LHGqQIuRlK1QdjuzrP?p=preview
HTML:
<a ui-sref="company()">company</a>
| <a ui-sref="company.profile()">profile</a>
| <a ui-sref="company.employees()">employees</a>
JS:
$stateProvider
.state('company', {
url: '/',
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
})
.state('company.profile', {
url: '',
templateUrl: 'profile.html',
controller: 'CompanyProfileCtrl as CompanyProfileCtrl'
})
.state('company.employees', {
url: '/employees',
templateUrl: 'employees.html',
controller: 'CompanyEmployeesCtrl as CompanyEmployeesCtrl'
});
btw, I'm writing everything as components and decided to define the routes in each component, so you'll find the 3 state definitions in the 3 controllers. I'm not entirely sure this is the best approach or not yet.
The default state is entirely dependent on how you call $urlRouterProvider.otherwise(), passing it a url transitions the application to the particular url, wherein ui-router detects and looks for the very first state it sees.
In your main.js configuration, defines the / url as the default url for the application, which is technically the company state's url and is the very first state in the chain of parent states and children states, making it the default state. This in fact, is also the resulting url for the company.profile state that you wanted your application to default to.
To solve this problem, depends on the use cases for your application.
Use case: If your application defines the company state as a non-navigational state, then setting it to an abstract state solves the problem.
DEMO
CompanyCtrl.js
$stateProvider
.state('company', {
abstract: true,
url: '/',
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
});
Use case: If the company state is nagivational, then simply remove the url definition in the company state and change the url defintion for the company.profile state to '/'. The only caveat for this solution would be the loss of the href attribute to be applied for for any anchor tags defined with the ui-sref="company" state which also implies the application of the text cursor. To mitigate this problem you might as well define all anchor tags with ui-sref attribute with a pointer cursor.
DEMO
CompanyCtrl.js
$stateProvider
.state('company', {
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
});
CompanyProfileCtrl.js
$stateProvider
.state('company.profile', {
url: '/',
templateUrl: 'profile.html',
controller: 'CompanyProfileCtrl as CompanyProfileCtrl'
})
style.css
a[ui-sref] {
cursor: pointer;
}
UPDATE:
Use Case: The same with use case #2 but making the company state an abstract state.
DEMO
CompanyCtrl.js
$stateProvider
.state('company', {
abstract: true,
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
});
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 :-)