I am creating a vertical navigation panel for my web page (a very basic task). Considering different user roles should have different nav-items against the authorization module on the server, it is desired to create the navigation contents dynamically rather than statically, by getting the data from the server.
I'm trying to use the UI Router to create nested states dynamically (which is really a natural idea called "divide-and-conquer") but got a problem (I described it in another thread but there are only code snippets and cannot demo). I constructed a simple demo here for the problem, in a more general way.
<!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.provider('runtimeStates', ['$stateProvider', function ($stateProvider) {
this.$get = function () {
return {
newState: function (name, param) {
$stateProvider.state(name, param);
return name;
}
};
};
}]);
app.config(['$urlRouterProvider', '$stateProvider', function (up, sp) {
sp.state('state1', state1);
up.otherwise('/state1');
}]);
let state1 = {
url: '/state1',
controller: ['runtimeStates', '$state', function ($rs, $st) {
this.stateName = $st.current.name;
this.createSubState = function(){
$st.go($rs.newState($st.current.name + '.state2', state2), {
message: 'message from ' + $st.current.name + ' to state2'
});
}
}],
controllerAs: '$ctrl',
template: `<div ng-click="$ctrl.createSubState()" style="border-style: solid; cursor: pointer;">
<p>{{$ctrl.stateName}} begin</p>
<ui-view></ui-view>
<p>{{$ctrl.stateName}} end</p>
</div>`
};
let state2 = {
params: {message : ''},
controller: ['runtimeStates', '$transition$', '$state', function ($rs, $tr, $st) {
this.parentMessage = $tr.params().message;
this.stateName = $st.current.name;
this.createSubState = function(){
$st.go($rs.newState($st.current.name + '.state3', state3),{
message: 'message from ' + $st.current.name + ' to state3'
});
};
}],
controllerAs: '$ctrl',
template: `<div ng-click="$ctrl.createSubState()" style="border-style: solid; cursor: pointer;">
<p>{{$ctrl.stateName}} begin</p>
{{$ctrl.parentMessage}}
<ui-view></ui-view>
<p>{{$ctrl.stateName}} end</p>
</div>`
};
let state3 = {
params: {message : ''},
controller: ['runtimeStates', '$transition$', '$state', function ($rs, $tr, $st) {
this.parentMessage = $tr.params().message;
this.stateName = $st.current.name;
}],
controllerAs: '$ctrl',
template: `<div style="border-style: solid;">
<p>{{$ctrl.stateName}} begin</p>
{{$ctrl.parentMessage}}
<p>{{$ctrl.stateName}} end</p>
</div>`
};
</script>
</head>
<body>
<ui-view></ui-view>
</body>
</html>
When the view of state1 populated, I can click on it and generates the view of state2 with the contents expected; but when continuing to click on the view of state2, the generated contents are totally messed. Expected:
state1 begin
state1.state2 begin
message from state1 to state2
state1.state2.state3 begin
message from state1.state2 to state3
state1.state2.state3 end
state1.state2 end
state1 end
Generated:
state1 begin
state1.state2.state3.state2 begin
message from state1.state2.state3 to state2
state1.state2.state3.state2 begin
message from state1.state2.state3 to state2
state1.state2.state3.state2 end
state1.state2.state3.state2 end
state1 end
I cannot explain why and don't know how to fix.
EDIT
Following the idea of @scipper (the first answer) I updated the demo to bellow:
<!DOCTYPE html>
<html ng-app="demo28">
<head>
<meta charset="utf-8" />
<title>Demo28</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('demo28', ['ui.router']);
app.provider('runtimeStates', ['$stateProvider', function ($stateProvider) {
this.$get = function () {
return {
newState: function (name, param) {
$stateProvider.state(name, param);
return name;
}
};
};
}]);
app.config(['$urlRouterProvider', '$stateProvider', function (up, sp) {
sp.state('state1', state1);
up.otherwise('/state1');
}]);
let state1 = {
url: '/state1',
controller: ['runtimeStates', '$state', function ($rs, $st) {
this.stateName = $st.current.name;
this.createSubState = function(){
$st.go($rs.newState($st.current.name + '.state2', state2), {
message: 'message from ' + $st.current.name + ' to state2'
});
}
}],
controllerAs: '$ctrl',
template: `<div style="border-style: solid;">
<p ng-click="$ctrl.createSubState()" style="cursor: pointer; color: blue;">{{$ctrl.stateName}} begin</p>
<ui-view></ui-view>
<p>{{$ctrl.stateName}} end</p>
</div>`
};
let state2 = {
params: {message : ''},
controller: ['runtimeStates', '$transition$', '$state', function ($rs, $tr, $st) {
this.parentMessage = $tr.params().message;
this.stateName = $st.current.name;
this.createSubState = function(){
$st.go($rs.newState($st.current.name + '.state3', state3),{
message: 'message from ' + $st.current.name + ' to state3'
});
};
}],
controllerAs: '$ctrl',
template: `<div style="border-style: solid;">
<p ng-click="$ctrl.createSubState()" style="cursor: pointer; color: blue;">{{$ctrl.stateName}} begin</p>
{{$ctrl.parentMessage}}
<ui-view></ui-view>
<p>{{$ctrl.stateName}} end</p>
</div>`
};
let state3 = {
params: {message : ''},
controller: ['runtimeStates', '$transition$', '$state', function ($rs, $tr, $st) {
this.parentMessage = $tr.params().message;
this.stateName = $st.current.name;
}],
controllerAs: '$ctrl',
template: `<div style="border-style: solid;">
<p>{{$ctrl.stateName}} begin</p>
{{$ctrl.parentMessage}}
<p>{{$ctrl.stateName}} end</p>
</div>`
};
</script>
</head>
<body>
<ui-view></ui-view>
</body>
</html>
and the contents becomes:
state1 begin
state1.state2.state3 begin
message from state1.state2 to state3
state1.state2.state3 begin
message from state1.state2 to state3
state1.state2.state3 end
state1.state2.state3 end
state1 end
It shows that the view of stat2 is effected by state3, which should be a problem of using UI Router. -- The problem is still unsolved.
` element; so the click on state2 should not trigger the `ng-click` for state1 now. --- that's why I got the same result of your EDIT2 in your bellow answer; which is still not the desired because state3 output overwrote state2's view.
– zipper Nov 26 '17 at 13:28