I'm trying to manage my admin page by bellow states with multi views: admin, admin.header, admin.leftPanel, admin.main, admin.tail. In the header, leftPanel, main and tail, I use $state.go to their sub states respectively to render their contents. I write bellow simple code to demo this problem.
Demo states model:
state1:
state2view
controller: $state.go(state1.state2) <---superseded
state3view
controller: $state.go(state1.state3)
Code (plunker):
<!DOCTYPE html>
<html ng-app="demo">
<head>
<meta charset="utf-8" />
<title>Demo</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.6/angular.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.3/angular-ui-router.js"></script>
<script>
let app = angular.module('demo', ['ui.router']);
app.config(['$urlRouterProvider', '$stateProvider', function ($up, $sp) {
$sp.state('state1', state1);
$sp.state('state1.state2', new SubState('state2view'));
$sp.state('state1.state3', new SubState('state3view'));
$up.otherwise('/');
}]);
let state1 = {
url: '/',
views: {
"state1view1": {
controller: ['$transition$', '$state', function ($tr, $st) {
this.stateName = $st.current.name;
$st.go('state1.state2', {message: 'message from ' + $st.current.name + ' to ' + $st.current.name + '.state2'});
}],
controllerAs: '$ctrl',
template: `<div>
{{$ctrl.stateName}} begin<br>
<ui-view name="state2view"></ui-view>
{{$ctrl.stateName}} end
</div>`
},
"state1view2": {
controller: ['$transition$', '$state', function ($tr, $st) {
this.stateName = $st.current.name;
$st.go('state1.state3', {message: 'message from ' + $st.current.name + ' to ' + $st.current.name + '.state3'});
}],
controllerAs: '$ctrl',
template: `<div>
{{$ctrl.stateName}} begin<br>
<ui-view name="state3view"></ui-view>
{{$ctrl.stateName}} end
</div>`
}
}
};
function SubState(view1Name) {
this.params = {message: ''};
this.views = {};
this.views[view1Name] = {
controller: ['$transition$', '$state', function ($tr, $st) {
this.parentMessage = $tr.params().message;
this.stateName = $st.current.name;
}],
controllerAs: '$ctrl',
template: `<div>
{{$ctrl.stateName}} begin<br>
{{$ctrl.parentMessage}}<br>
{{$ctrl.stateName}} end
</div>`
};
}
app.run(function($transitions) {
$transitions.onStart({}, function($tr) {
console.log("trans begin: " + $tr.from().name + " -> " + $tr.to().name);
}
);
$transitions.onSuccess({}, function($tr) {
console.log("trans done: " + $tr.from().name + " -> " + $tr.to().name);
}
);
});
</script>
<style>
div{border-style: solid;}
</style>
</head>
<body>
<ui-view name="state1view1"></ui-view>
<br>
<ui-view name="state1view2"></ui-view>
</body>
</html>
Expected result:
state1 begin
state1.state2 begin
message from state1 to state1.state2
state1.state2 end
state1.state3 begin
message from state1 to state1.state3
state1.state3 end
state1 end
Actual result:
state1 begin
state1 end
state1 begin
state1.state3 begin
message from state1 to state1.state3
state1.state3 end
state1 end
It turns out that I just hit the wall.
The idea of enclosing a page's contents into UI Router states and layout by visiting these states one by one programmablly is totally wrong. Particularly, you can not show two sibling states' views in one time.
UI Router is designed for routing by mouse clicks. Even though the pieced documents strongly hint we can relay our full page on a state tree to layout all contents, but it is not always the case. As long as the app logic transit to another state which is not the decedent of the from-state, the from-state exits before entering the to-state, and its run-time generated view(s) of the exited state is fully removed.
Bellow code is originally trying to prove my concept bellow (the improvement of the design in the original question, since I realized that there is not break-point/resume design in state) and to solve my problem, but it turns out to be a example of revealing the opposite -- the impossibility under the wrong idea.
The concept
The code (plnkr)
The result (Firefox 57.0.1)
When entering the page:
After click and close the alert:
Above process revealed that the state1.state2 was executed and layed out (but not evaluated/rendered by angular yet), as we can see in the first picture. At that point the exiting was not happened yet because the onExit alert pop blocked the process. After the alert pop closed the state exited and the view was complete removed.
There is a sticky-state developed for the in-page-tab specific purpose, but as I tried it does not work here. It remembers the last visited stick-states but the views of the exited states are always removed.
I'm now trying to use the UI Router as a routing notation facility only. But I have to be very conscious NOT to run into the idea that UI Router can be used as a general tool to layout a page like a extension of angular component. But this can be difficult: I cannot think of a right pattern to use UI Router at the moment. In case of multi views, if any two sibling views both has their own sub states, I must be very careful because visiting one exits another -- they are exclusive. This makes me think it is not worth its complexity.
While removing views on exit is desired in most case during navigation, I would suggest the UI Router to change and give a chance to keep the views to provide more flexibility. It can be more complicated than the first thought, but it should be possible.
It is also desirable to cache all the "last seen" parameters for each states (not just for sticky states) so we can return to them easily. You may argue the use case, but we cannot imagine how people will use a tool and should not limit the possibilities.
It is also desirable to provide facility for full life cycle hooks per state base (now only have onEnter and onExit).
You should use stateHelper module created by@marklagendijk.
Read this article in the link below about nested states for more options if you do not wish to use the aforementioned module
Nested States
plunkr