Problem
My Ionic app lets you choose a Project from the side menu, and then displays two tabs (Tasks, Messages) in the main content area. The tasks and messages tabs are nested states of project.
When you change projects in the side menu, TaskListCtrl gets executed twice. See the live demo and watch the console as you change between projects. I also have a video which shows the issue in detail.
How do I stop TaskListCtrl from executing twice? Is there a better way I could be structuring these nested states?
Code
Full code is on GitHub »
Here's my $stateProvider config:
.state('project', {
url: "/projects/:projectID",
abstract: true,
cache: false,
controller: 'ProjectDetailCtrl',
templateUrl: "templates/project.tabs.html",
resolve: {
project: function($stateParams, Projects) {
return Projects.get($stateParams.projectID);
}
}
})
.state('project.tasks', {
url: '/tasks',
views: {
'tasks-tab': {
templateUrl: 'templates/task.list.html',
controller: 'TaskListCtrl'
}
}
})
And the relevant snippet from controllers.js:
.controller('ProjectDetailCtrl', function($scope, project) {
$scope.project = project;
console.log('=> ProjectDetailCtrl (' + $scope.project.name + ')')
})
.controller('TaskListCtrl', function($scope, $stateParams) {
$scope.tasks = $scope.project.tasks;
console.log('\t=> TaskListCtrl')
console.log('\t\t=> $stateParams: ', $stateParams)
console.log('\t\t=> $scope.tasks[0].title: ', $scope.tasks[0].title)
})
Resources
Live demo (watch the console logs as you change between projects)
Video showing the issue
Code on GitHub
Notes
I am aware there are similar questions on StackOverflow — however, none of the solutions they offer solved my issue.*
I've read this can happen when attaching the controller both in $stateProvider and with ng-controller — however, I've checked and I'm not doing this. I'm only attaching the controller with $stateProvider.
I guess tuckerjt07 is right.
It seems to be an issue with routing and parameters and ionic tabs.
I've spend almost the whole day trying to figure out what is going on.
I thought the problem was with the fact you're using an abstract controller with parameters, but that's not the problem.
I've checked if the side menu was interfering with tabs but, again, the problem is not there.
I've checked the scope trying to eliminate friction using controllerAs and avoiding to reference the $scope object to store the viewmodel but ... nothing.
I've created a simplified version of your application here.
There's not much in there and the navigation is through constants in the header.
As you can see the problem is still there.
Doing a little bit of debugging it seems that the problem sits here.
That line calls the controller twice. You can check it yourself adding a breakpoint at line 48435 in ionic.bundle.js.
The only option you have is to change your project.tabs.html and load the list of tasks without the sub-view. Something like this:
<ion-view view-title="{{ project.name }}: Tasks">
<ion-tabs class="tabs-icon-top tabs-positive">
<ion-tab title="{{ project.name }} Tasks" icon="ion-home">
<ion-nav-view>
<ion-content>
<ion-list>
<ion-item class="item-icon-right" ng-repeat='task in project.tasks'>
{{ task.title }}
<i class="icon ion-chevron-right icon-accessory"></i>
</ion-item>
</ion-list>
</ion-content>
</ion-nav-view>
</ion-tab>
<ion-tab title="About" icon="ion-ios-football" ui-sref="tabs.tab2">
<ion-nav-view name="tabs-tab2"></ion-nav-view>
</ion-tab>
<ion-tab title="Another" icon="ion-help-buoy" ui-sref="tabs.tab3">
<ion-nav-view name="tabs-tab3"></ion-nav-view>
</ion-tab>
</ion-tabs>
</ion-view>
You can check how it works here.
I guess we should open an issue.
Related
I am working with ui-router AngularJS in Ionic Project. I have an abstract state where I nest my children's templates via <ion-nav-view> tag. The question is can I display some default data in the template of the abstract state that will be shown for all the children's templates ?? If no then Why. I tried this simple example
<ion-view view-title="MyView">
<div>
<h1>Welcome</h1>
</div>
<ion-nav-view name="ChildContent"></ion-nav-view>
</ion-view>
But the message Welcome is not shown. The space for the div is there but nothing is displayed.
I think I found the solution. What I ended with to not repeat the same info within all the children templates is to create a custom directive with its own template and just include this directive in every child's template. So, we just have one place to manipulate. It is the section "Template-expanding directive" in Angular documentation. More informations could be found in this link : enter link description here. Hope it will help someone else :) .
You can define a controller for your abstract state and there setear default values that can be displayed in various other views. You can also do this in the function
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.controller('AppCtrl', function($scope){
$scope.title = "Test title";
})
OR
angular.module('started.controllers', [])
.run(function($window, $rootScope) {
$rootScope.title = "Test title";
});
<ion-view view-title="{{title}}">
<div>
<h1>Welcome</h1>
</div>
<ion-nav-view name="ChildContent"></ion-nav-view>
</ion-view>
I am setting the scope with data but all the time getting empty screen.
tried to use some $scope.$apply but it isn't helping.
I can see the scope.items isn't empty...
I can see values only one I click on other tabs.
my code:
.controller('AccountCtrl', function ($scope, $timeout) {
if (localStorage.getItem("itemHistory") !== null) {
$scope.items = localStorage["itemHistory"].split(',');
$scope.$safeApply($scope);
}
});
tab that navigate to template
<!-- Dashboard Tab -->
<ion-tab title="History" icon-off="ion-ios-pulse" icon-on="ion-ios-pulse-strong" href="/tab/account">
<ion-nav-view name="tab-account"></ion-nav-view>
</ion-tab>
my temp:
<ion-nav-bar>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-view view-title="Recent Searches">
<ion-content class="padding">
<ul>
<li ng-repeat="item in items">
{{ item }}</li>
</ul>
</ion-content>
</ion-view>
thanks for helping!
I dont see anywhere in your view where the items is being referred. Can you please provide a jsfiddle where I can see the problem? otherwise just looking at this code, I cant make out anything.
And you should not be using explicit apply in this case. apply is a very dangerous call which has to be used with lot of due diligence. It would trigger a digest cycle that trickles up till the rootscope which is what you may NOT want.
I find the issue, it was simple mistake on app.js didnt set the correct view on the app.js
I am using ionic with firebase, and am trying to redirect upon login to the home page. The $state.go redirect does work, however I have a hidden tab called 'Profile', that I do not show until the user is authenticated. This tab remains hidden on the $state.go redirect, and doesn't show until I click on the 'Home' tab again. How do you load the load the ui on a redirect?
HTML:
tabs HTML:
<ion-tabs class="tabs-icon-top tabs-color-active-positive" ng-controller="LoginCtrl">
<!-- Login Tab -->
<ion-tab title="Login" icon-off="ion-locked" icon-on="ion-locked" href="#/tab/login">
<ion-nav-view name="tab-login"></ion-nav-view>
</ion-tab>
<!-- Dashboard Tab -->
<ion-tab title="New Post" icon-off="ion-compose" icon-on="ion-compose" href="#/tab/new">
<ion-nav-view name="tab-new"></ion-nav-view>
</ion-tab>
<!-- Chats Tab -->
<ion-tab title="Home" icon-off="ion-ios-home" icon-on="ion-ios-home" href="#/tab/home">
<ion-nav-view name="tab-home"></ion-nav-view>
</ion-tab>
<!-- Account Tab -->
<ion-tab class="{{hiddenProfileTab()}}" title="Profile" icon-off="ion-person" icon-on="ion-person" href="#/tab/profile">
<ion-nav-view name="tab-profile"></ion-nav-view>
</ion-tab>
</ion-tabs>
Login Controller
.controller('LoginCtrl', ['$scope', '$rootScope', '$state', '$location', function($scope, $rootScope, $state, $location) {
$scope.hiddenProfileTab = function(){
// return "ng-hide";
if ($rootScope.currentUser){
return "ng-show";
} else {
return "ng-hide";
}
}
//DO SOME THINGS
// $location.path('tab.home')
// $state.go('tab.home')
// $state.go('tab.home'), {}, { reload: true }
$state.go('tab.home',{},{location:'replace'});
};
}])
To reiterate the problem, the Profile tab remains hidden on the $state.go redirect to the 'home' page, until the 'home' page tab is clicked.
Any ideas how to load the UI on the redirect?
Use ng-show instead of using class attribute with {{}} interpolation.
<ion-tab ng-show="currentUser" title="Profile" icon-off="ion-person" icon-on="ion-person" href="#/tab/profile">
<ion-nav-view name="tab-profile"></ion-nav-view>
</ion-tab>
Solved this by setting the cache:false so that the controller is forced to load again.
.state('tab.home', {
cache: false,
url: '/home',
views: {
'tab-home': {
templateUrl: 'templates/tab-home.html',
controller: 'HomeCtrl'
}
}
})
Not sure if it is directly attributable to this specific problem, but when I encountered a similar issue with elements not displaying until the user started interacting with the UI, I found that the issue was due to having multiple elements with the same "id" attribute value.
In my case it was related to a loader animation icon that I was using on multiple views. I always had the loader id set to the same thing ("loading-progress"). When showing and hiding my loader I was experiencing weird behaviors until I made sure to name each loader uniquely.
Ultimately, I think my problem was due to not treating angular as being a single page app but coding as though it were a full site with unique pages and views. It's easy to just concentrate on the view at hand and forget that it's actually sitting in a big set of nested views and html snippets.
Edit: Also, I should have mentioned, another common issue where content does not appear (especially dynamically loaded content) often relates to window/div resizing. In particular, if you begin with a hidden div that you then populate with data through an api call, you often need to conclude it with a call to:
$ionicScrollDelegate.resize()
to force the size of the div to be recalculated. Without this, your data can be loaded but still not visible because the parent div is still hanging out with a 0px height.
Edit again!:
Also, when working with ng-repeat and similar dynamic angular databinding, to force the ng logic to re-sync the html with the data you can use:
$scope.$apply();
I can't seem to figure out how to get a back button to appear on a view that contains a ion-side-menus directive.
Here's the absolute simplest example I've come up with:
http://codepen.io/jsplaine/pen/YPxvXL?editors=101.
Note that the ion-views in state x.emptyView and state x.emptySideMenu are children of state x's ion-nav-view.
Here's a more in-depth example, where there exists a side-menu that is actually populated:
http://codepen.io/jsplaine/pen/ZYJRYW?editors=101
Here's the basic router, for the first codepen:
angular.module('ionicApp', ['ionic'])
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('app', {
url: "/",
templateUrl: "templates/home.html",
controller: 'AppCtrl'
})
.state('x', {
url: "/x",
abstract: true,
template: '<ion-nav-view animation="slide-left-right"/>'
})
.state('x.emptyView', {
url: "/empty-view",
templateUrl: "templates/empty_view_only.html"
})
.state('x.emptySideMenu', {
url: "/empty-side-menu",
templateUrl: "templates/empty_side_menu.html"
})
})
There's a second issue with the more in-depth codepen. Depending on which tab you click first, the 2nd or the 3rd, the corresponding template gets cached for future navigation to BOTH the 2nd and 3rd template. Click the 2nd one first, then the 3rd. Then start over and click the 3rd one first then the 2nd. The fact that this is happening makes me thinking that I'm using ui-router wrong somehow.
I also don't understand why I have to define ion-nav-view in both index.html AND in the abstract state ('x') template. Isn't state 'x' a child of index.html (the empty state)?
Can someone figure out how to modify both codepens so that the back button appears on views containing a ion-side-menus directive, and for the 2nd codepen .. the 2nd and 3rd tab caching issue is resolved?
The solution can be seen here:
http://codepen.io/jsplaine/pen/wBqNmw?editors=101
It seems that ion-side-menus' parent ion-view needs a child ion-nav-bar, and enable-menu-with-back-views must be set to true.
<!-- Side Menu Nav and Burger Defined -->
<script id="templates/side_menu_with_nav.html" type="text/ng-template">
<ion-view view-title="Side Menu with Nav and Burger">
<ion-nav-bar></ion-nav-bar> <!-- HERE -->
<ion-side-menus enable-menu-with-back-views="true">
<ion-side-menu-content>
<ion-nav-bar>
<ion-nav-back-button class="button-icon ion-arrow-left-c"></ion-nav-back-button>
<ion-nav-buttons side="right">
<button class="button button-icon button-clear ion-navicon" menu-toggle="right"></button>
</ion-nav-buttons>
</ion-nav-bar>
....
As the ionic directive/menuToggle docs state: https://github.com/driftyco/ionic/blob/master/js/angular/directive/menuToggle.js#L1
### Button Hidden On Child Views
By default, the menu toggle button will only appear on a root
level side-menu page. Navigating in to child views will hide the menu-
toggle button. They can be made visible on child pages by setting the
enable-menu-with-back-views attribute of the {#link >ionic.directive:ionSideMenus}
directive to true.
I wonder if the authors didn't take your case into account. Their model is a slide-in menu that only slides in partially and can be toggled with a menu icon. So you may be a bit off paradigm but this seemed to work for me albeit a bit lame for duplicating the nav-bar code in the template.
<script id="templates/empty_side_menu.html" type="text/ng-template">
<ion-view view-title="Empty side menu">
<ion-nav-bar align-title="center" class="nav-title-slide-ios7 bar-stable">
<ion-nav-back-button class="button-icon ion-arrow-left-c">
</ion-nav-back-button>
</ion-nav-bar>
<ion-side-menus>
</ion-side-menus>
</ion-view>
</script>
I am building a ionic pacakage, having multiple views. I use the route provider to navigate between different views.
app.js
.config(function($routeProvider,$locationProvider){
$routeProvider
.when('/search',
{
controller : 'MyController',
templateUrl : 'partials/search.html'
})
.when('/not-found/:className',
{
controller : 'MyController',
templateUrl : 'partials/not-found.html'
})
My index.html
<body ng-app="MyApp">
<ng-view></ng-view>
</body>
</html>
The problem is that the back button on my phone does not work.i.e it does not remember the history.
e.g If I go from search.html to not-found.html, when I press the back button on my phone, I expect it to come back to search.html instead it closes my app.
I looked and ionic forum and the suggest way to make back button work is to use ion-nav-view. If I replace ng-view with ion-nav-view, the search/not-found page are not rendering, I even tried adding the ion-view on the search/not-found html page.
1) Could you please suggest a way to get my back button working?
In order to achieve that, you actually need to capture the hardware back button pressed event and perform the navigation accordingly or You can use ion-nav-back-button..
Capture the hardware back button event :
$ionicPlatform.registerBackButtonAction(function () {
if (condition) {
navigator.app.exitApp();
} else {
// handle back action!
}
}, 100);
More Details can be found here
Using ion-nav-back-button
<ion-nav-bar>
<ion-nav-back-button class="button-clear">
<i class="ion-arrow-left-c"></i> Back
</ion-nav-back-button>
</ion-nav-bar>
More Details about this can be found here
registerBackButtonAction is already handled as part of ion-nav-back-button as part of the ng-click attribute within the ion-nav-back-button definition: buttonEle.setAttribute('ng-click', '$ionicGoBack()') , since $ionicGoBack executes $ionicHistory.goBack() which in turn handles the hardware back button. A simple change to use state configuration should work fine as below:
angular
.module('app', ['ionic'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('search', {
url: '/search',
controller : 'MyController',
templateUrl : 'partials/search.html'
})
.state('not-found', {
url: `/not-found/:className',
controller : 'MyController',
templateUrl : 'partials/not-found.html'
});
$urlRouterProvider.otherwise('/search');
});
HTML:
<body ng-app="app">
<ion-nav-bar>
<ion-nav-back-button></ion-nav-back-button>
</ion-nav-bar>
<ion-nav-view></ion-nav-view>
</body>
</html>