I have a one page app which contains the ui.router AngularJS with multiple states links to different parts/code etc. The issue is that inside some of those state html pages I have for example a href="#tip" link that should open a modal that is part of a jQuery code; but what actually happens is that the State ends up changing and going back to my .otherwise("/") page in the state model.
I have noticed that what happens is that the link which is requested is not the correct link with the ui.router url that is currently rendering the page. For example, my app is on example.com/app .. When a state changes the url changes to example.com/app/#/page1 or page2 etc .. When a href="#" link is clicked, the link actually points to example.com/#link instead of what I assume should be example.com/#/page1/#tip or /#/tip considering the way my urls are shown..
How can I fix this to allow the href="#" tags to do and function as they originally did before being placed in states?
Below is my provider setup and a sample sate:
.config(function($stateProvider,$urlRouterProvider,$urlMatcherFactoryProvider){
$urlRouterProvider.otherwise('/app');
$urlMatcherFactoryProvider.strictMode(false)
$stateProvider
.state('flashcards', {
url: "/page1",
views: {
'input-views': {
templateUrl: '/page1.html',
}
}
})
});
Related
I have a requirement that I am trying to implement as follows -
I have an audit log page which has a button to display the logs on a new browser window, while continuing to do other stuff on the application open in the main window.
We use ui-router and angularjs (1.6). Below is my code -
Route to the controller -
.state('auditlog', {
url: '/auditlog',
templateUrl: 'app/system/audit/audit-log.html',
controller: 'AuditLogController',
controllerAs: 'al'
})
On the click event of a button in a different controller, I have the following code -
unDockEventsPanel(event) {
let location = this.$state.href('auditlog');
this.$window.open(location, '', 'scrollbars=no,fullscreen=yes,toolbar=no');
}
and the HTML -
<i class="Icon-MoveOut Icon-Large icon-btn" ng-click="eventsPanelVM.unDockEventsPanel($event)">
Now, when I click on the "MoveOut" button the unDockEventsPanel() method gets called and a new window is opened with the contents of the controller/template in the route definition "auditlog".
The problem I'm facing however is, any operation I perform on the child window is randomly triggering something else on the parent window and vice versa. For eg. When I click on a select button in the child window, the select opens up on the child, but also, on the parent the a text box comes into focus like it would if I had clicked it. Likewise, when I click a select on the parent, a different control on the child gets highlighted.
I can see that a new instance of the application/controller is not created on the child and hence there is an overlap of the controls and events between the two. But I'm not able to figure out how to isolate the two.
Any help is greatly appreciated.
I believe the issue is related to the use of '$state'
let location = this.$state.href('auditlog');
this.$window.open(location, '', 'scrollbars=no,fullscreen=yes,toolbar=no');
from the angular-ui github wiki
When a state is activated, its templates are automatically inserted into the ui-view of its parent state's template.
I would suggest trying to avoid the use of $state by using plain old Javascript to do the new window navigation
window.open("{yourURL}/auditlog", 'scrollbars=no,fullscreen=yes,toolbar=no');
Here's what I wish to achieve:
User is on url example.com/2
User goes to url example.com/2d <- this is not valid url
User is fowarded to example.com/2d/404_error
User clicks browser "back" button and lands on example.com/2
Here's what I have a problem with
User clicks browser "back" button and lands on example.com/2d
User is again automatically forwarded to example.com/2d/404_error
I have created infinite back button loop :(
In my app.config I have
.when('/:gameId/404_error', {
templateUrl: 'views/page_not_found.html'
})
.when('/:gameId', {
controller: 'game',
templateUrl: 'views/gamePage.html'
})
It is called out by app.factory when data is not fetched
}, function(response) {
// something went wrong
$location.path("/" + id + "/404_error");
return $q.reject(response.data);
});
I have tried the "otherwise" method, but using routeparams renders it useless.
You can test it out here:
http://vivule.ee/2 <- working url
http://vivule.ee/2d <- not working url
window.history.go(-2)
This method is pure javascipt you can try to use $window.history.go(-2) in angularjs
Good works!
Fixed it with ng-switch and ng-include
In the controller, when data is not loaded I added:
$scope.page_not_found = true;
And to the template I added:
<div ng-switch on="page_not_found">
<div ng-if="page_not_found" ng-include="'views/page_not_found.html'"></div>
</div>
Everything happens in one .html template. I hide the main content and import the 404 page with ng-include, this solution seems to be faster as well.
I had a problem previously where I wanted to prevent the page from refreshing when programmatically updating the URL.
I have a "home" view, e.g: /books and I have specific views: e.g: /books/123
The home page has a couple of side panels and a main panel with the list of books.
When clicking on a book, I only want the main panel to update, leaving the rest of the page as it is, but I DO want to update the URL to reflect the book that is loaded. All books should be directly linkable, and so the URL reflects this direct link. I update the URL by setting $location.path() when a specific book is loaded, and I use reloadOnSearch:false in my ui-router state to prevent the refresh.
My routes are set up as follows:
.state('books', {
url: '/books',
title: 'Books',
templateUrl: CONFIG.static_url + '/html/book.html',
controller: 'BookCtrl'
})
.state('book', {
url: '/books/:itemId',
title: 'Books',
templateUrl: CONFIG.static_url + '/html/book.html',
controller: 'BookCtrl',
reloadOnSearch: false
})
This works fine, but it has two problems.
1) When transitioning from the home "Books" page, to a specific book, the page IS refreshed. When transitioning from one specific book to another, the model updates without refreshing the page (which is the desired behaviour).
2) When using the browser back/forward controls, the change in URL is not reflected in the model and so the view doesn't update. However, it does work when transitioning to/from the home page to a specific page.
To fix problem 2, I have set a $watch on $location.path() so if it changes and it doesn't match with the model, I load in the correct item. This appears to work fine.
However, problem 1 remains. How can I seamlessly transition from the Home page, to a specific view, without the whole page refreshing while also retaining the browser back/forward functionality? I can probably continue to use the $watch functionality to update based on the URL, but I can't seem to get it to load without the page refresh.
Make book a child of books and change its URL just to /:itemId.
Do not manually set $location.path(), as this will force a refresh. Redirect between states using ui-sref directive or $state.go() function.
Try something like this:
.state('books', {
url: '/books',
// ... (template should contain <div ui-view></div> where sub-state content will be rendered)
})
.state('books.book', {
url: '/:itemId',
// ... (template and controller just for this sub-state; will get rendered into super-state's ui-view)
})
and link like this:
<a ui-sref="books.book({itemId:123})">Book 123</a>
You need to make the book as a child state of the books. By using that you achieve the required behavior as the book html will already be available for the route. I hope that will work.
I'm using the latest Ionic "nighty build" version currently.
One good news with this version is the concept of cached views:
By default views are cached to improve performance. When a view is
navigated away from, its element is left in the DOM, and its scope is
disconnected from the cycle. When navigating to a view which is
already cached, its scope is then reconnected, and the existing
element which was left in the DOM becomes the active view. This also
allows for scroll position of previous views to be maintained.
Interesting, so I tried it and it's really smooth.
However, I come across a severe UX issue:
Basically, my app is composed of 2 tabs.
TabA aims to display a load and list items.
TabB aims to display other items.
Each one has its own navigation: listing and showing specific items.
Obviously, the first time, data are fresh, but then, since cached => stale.
Cached views is really adapted to the "back" button from the "show" to the "list" of a specific tab.
Indeed, scroll is maintain and controllers don't need to reload => very good performance.
However, what a user wants when he clicks on a particular tab is to get a refreshed list.
I really can't find a pretty and efficient way to refresh a specific cached view regarding the clicked element.
I've got those declared states (showing the example for TabA):
.state('tab', {
url: "/tab",
abstract: true,
templateUrl: "/tabs.tpl.html",
controller: 'TabsCtrl'
})
.state('tab.tabAList', {
url: '/items',
views: {
'tab-a': {
templateUrl: '/tabAList.tpl.html',
controller: 'TabACtrl'
}
}
})
.state('tab.tabAShow', {
url: '/tabAList/:itemId',
views: {
'tab-a': {
templateUrl: '/tabAShow.tpl.html',
controller: 'TabAShowCtrl'
}
}
});
So, tab's controller is the parent of tab.tabAList and tab.tabAShow.
tabList contains a function like:
$scope.reloadItems = function() {
//...
}
How to trigger this function when tabA is clicked?
The tricky part is that TabsCtrl code is run just before nested TabAList controller is run.
I tried to involve a $rootScope.$broadcast('reloadTheItems',...) under the on-select attribute of the tab.
But the event is missed since tabAList hasn't run yet at the time of the event sending.
Has anyone experienced it and has a pretty solution?
I repeat, the goal is: "Reload a specific cached view on tab click".
You should listen to the $ionicView.enter event for the view for TabA.
Then do this from the $scope:
$scope.$on( "$ionicView.enter", function( scopes, states ) {
if( states.fromCache && states.stateName == "tab.tabAList" ) {
reloadItems();
}
});
...or something similar...
Currently I have setup a small test application and ran into some trouble lately. After some tries I still don't know where my mistake is.
Here is my pen: http://codepen.io/ins0/pen/wtELa
The problem is that after clicking the side navigation the content normally shows up, navigation updates. When I click on a link inside the content - the url state changes (eg. from #/settings to #/settings/about, xhr gets fired and received but the content doesn't get replaced.
I tried listening to all state events but no error is thrown.
I found the mistake by myself. In order to get the navigation correct you need to set a a viewtarget in your router configuration for childen pages that point to your parent view. Like this:
state('app.settings.about', {
url: "/about",
views: {
'content#app' :{
templateUrl: "about.html"
}
}
});
see'content#app'. this tells the framework to render the about view in the content field defined in route app. I updated the codepen to a working example.