SWEET DEMO
I have a list of profiles, where each profile can be either in a summary or in a detailed view. Only one profile can have a detailed view at any time.
profiles.html
<div ng-repeat="profile in profiles">
<div ui-view="profile-summary"></div>
<div ui-view="profile-details"></div>
</div>
profile-summary.html
<div ng-if="$state.params.profileId !== profile.id">
{{ profile.name }}
<button ng-click="showProfileDetails(profile.id)">More</button>
</div>
profile-summary-controller.js
$scope.showProfileDetails = function(profileId) {
$state.go('profiles.profile', { profileId: profileId });
};
profile-details .html
<div ng-if="$state.params.profileId === profile.id">
Detailed profile of {{ profile.name }}
<button ng-click="hideProfileDetails()">Less</button>
</div>
profile-details-controller.js
$scope.hideProfileDetails = function() {
$state.go('profiles.profile', { profileId: null });
};
ui-router configuration
$stateProvider
.state('profiles', {
url: '/profiles?keywords',
views: {
'profiles': {
templateUrl: 'profiles.html',
controller: 'ProfilesCtrl'
},
'profile-summary@profiles': {
templateUrl: 'profile-summary.html',
controller: 'ProfileSummaryCtrl'
}
}
})
.state('profiles.profile', {
url: '/:profileId',
views: {
'profile-details': {
templateUrl: 'profile-details.html',
controller: 'ProfileDetailsCtrl'
}
}
});
Questions I have:
- When the More button is clicked,
ProfileDetailsCtrl
is instantiated 3 times. How could I instantiate it only for profile that is extended?
- Am I utilizing the ui-router flexibility properly, or there is a better way to implement this? (Note: When profile is expanded, it should be reflected in the URL (to make it bookmarkable))
PLAYGROUND HERE
Extend with detail Replace
After discussion in comments below, there is a different solution for the basic (required) concept:
Inside a list - How to replace a row (on-click), with more details - using ui-router
?
I.e. let's start with this:
- More 1 basic info1
- More 2 basic info2 // click more here
- More 3 basic info2
getting this when More 2 is clicked (ang getting back if Less 2 is clicked next)
- More 1 basic info1
- Less 2 very detailed information loaded with ui-router state machine...
- More 3 basic info2
This would be the solution:
1) The list view template, would have ng-if
, checking if there is some detail
info, or not:
<div ng-repeat="profile in profiles">
<div ng-if="!profile.detail">
<button ui-sref=".profile({profileId:profile.id})">More</button>
{{ profile.name }}
</div>
<div ng-if="profile.detail">
<button ui-sref=".">Less</button>
{{ profile.detail | json }}
</div>
</div>
A few fancy parts to mention: instead of some ng-click
we just do use the built in ui-sref
with a relative path def ui-sref=".profile({profileId:profile.id})"
- That will call the child state profile.
Once child is loaded, we can just get back by re-calling the parent ui-sref="."
(wow...)
2) Our detail
state will be doing two things
- load the detail by ID // GetById called on a Server
cleanup on leave // Restoring the list as it was
// find a profile from parent collection
var profile = _.find($scope.profiles, {id : $stateParams.profileId});
$http
.get("detail.json") // getById
.then(function(response){
// would contain just a detail for id... here we filter
var detail = _.find(response.data, {id : $stateParams.profileId});
// assign the detail
profile.detail = detail;
});
// cleanup - remove the details
var cleanup = function(){
delete profile.detail;
}
$scope.$on("$destroy", cleanup);
A few things to mention: we hook on the $scope
event "destroy". This will be fired, once we go back to the parent. And that's the place where we clean all the foot prints made during the ui-router
detail state round-trip...
3) the detail view
There is NONE. None, becuase we do not need a template. Well in fact we still need the view anchor, where is the detail state placed... and the DetailController
is called!
.state('profiles.profile', {
url: '/:profileId',
views: {
'profile-details': { // NO template
controller: 'ProfileDetailsCtrl'
}
}
});
so there must be the view anchor somewhere in the parent:
<div ui-view="profile-details"></div>
Working code example:
Take a look a that solution here... it should be clear
(below is the original part of the answer why multiple times fired controller)
Original part of the answer
The controller is instantiated as many times, as many times is its view injected into the page. And you do inject the view 3 times.
Here is the source of the profiles
$scope.profiles = [
{ id: '100', name: 'Misko Hevery' },
{ id: '101', name: 'Igor Minar' },
{ id: '102', name: 'Vojta Jina' },
];
Here we do create anchors/targets with the same ui-view
name:
<div ng-repeat="profile in profiles"> // for each profile in our array
<div ui-view="profile-summary"></div>
<div ui-view="profile-details"></div> // we inject this view named profile-details
</div>
And finally, we ask to inject our view into these (3) parent/child view-targets:
.state('profiles.profile', {
url: '/:profileId',
views: {
'profile-details': { // the parent view-target is there 3 times
templateUrl: 'profile-details.html',
controller: 'ProfileDetailsCtrl'
}
}
});
Solution: this should not happen. We should not use one ui-view="viewName"
moret than once. It is working. but it is not what we can correctly manage... simply move the targets from repeater...
EXTEND here I updated the plunker, I made the profiles.html like this
// NO repeater here
<div ui-view="profile-summary"></div>
<div ui-view="profile-details"></div>
And I do iterate inside fo the summary:
<div ng-repeat="profile in profiles">
{{ profile.name }}
<button ng-click="showProfileDetails(profile.id)">More</button>
</div>
So now, each ui-view
is there only once... see that in action here