Angular-ui ui-router - using multiple nested views - angularjs

I am trying out the nested views feature of ui-router plugin, but faced the issue I don't know how to solve.
The code that shows the problem can be found at http://jsfiddle.net/3c9h7/1/ :
var app = angular.module('myApp', ['ui.router']);
app.config(function($stateProvider) {
return $stateProvider.state('root', {
template: "<div class='top' ui-view='viewA'></div><div class='middle' ui-view='viewB'></div>"
}).state('root.step1', {
url: '',
views: {
'viewA': {
template: '<h1>View A</h1>'
}
}
}).state('root.step1.step2', {
url: '/step2',
views: {
'viewB': {
template: '<h1>View B</h1>'
}
}
});
});
app.controller('MainCtrl', [
'$scope', '$state', function($scope, $state) {
$state.transitionTo('root.step1.step2');
}
]);
<div ng-app='myApp' ui-view ng-controller='MainCtrl'>
</div>
So, the code activates "root.step1.step2" state by using $state.go method(https://github.com/angular-ui/ui-router/wiki/Quick-Reference#stategoto--toparams--options)
According to ui-router documentation:
When the application is in a particular state—when a state is
"active"—all of its ancestor states are implicitly active as well.
So, I expect that "root.step1" and "root" will be active and it works as expected, but "viewB" is not filled with the template as you can see in jsfiddle sample : the top viewA(root.step1) is OK, but the middle viewB(root.step1.step2) is empty.
What I am doing wrong?

The documentation says:
Child states will load their templates into their parent's ui-view.
So there should be a ui-view='viewB' inside the viewA template, since the parent state of root.step1.step2 is root.step1. Or the viewB should be one of the views of root.step1, and there should be no root.step1.step2.

Related

Angular Ui router multiple named views and nested states

I have a question about the Angular UI router when I am using multiple named views in nested states. Basically I have an abstract state with a template that points to two named views. Those two named views are defined in both the sub states. I want to keep the URL fixed to /test.
When transitioning to either of the sub states, I see the view corresponding to the first sub state only. Why is that? I really hope someone can clarify the concept for me so I can learn
JSFiddle is here: https://jsfiddle.net/adeopura/e2c5n14o/16/
angular.module('myApp', ['ui.state'])
.config(['$stateProvider', '$routeProvider',
function ($stateProvider, $routeProvider) {
$stateProvider
.state('test', {
abstract: true,
url: '/test',
views: {
'main': {
template: '<h1>Hello!!!</h1>' +
'<div ui-view="view1"></div>' +
'<div ui-view="view2"></div>'
}
}
})
.state('test.subs1', {
url: '',
views: {
'view1': {
template: "Im 1View1"
},
'view2': {
template: "Im 1View2"
}
}
})
.state('test.subs2', {
url: '',
views: {
'view1': {
template: "Im 2View1"
},
'view2': {
template: "Im 2View2"
}
}
});
}])
.run(['$rootScope', '$state', '$stateParams', function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$state.transitionTo('test.subs1');//I see the data corresponding to the test.subs1
// Assume here that there are lots of different state transitions in between unrelated to the test1 state
$state.transitionTo('test.subs2');//I still see the data corresponding to the test.subs1, why is that?
}]);
It's actually the order by which you've defined them. Swap the order so that you define test.sub2 before test.sub1 and you'll see it'll start # test.sub2.
I think it's because you don't have a url assigned to either of the sub-states. I would try giving them their own url paths, and seeing if you can then reference the states that way.
One of my colleagues explained what is happening in this case. Explained as below:
state.transitionTo('test.subs2') causes the application to go to state 'test.subs2'
Once in state 'test.subs2', the URL is changed to /test
This causes the Angular UI Router to fire a change to the appropriate state.
The appropriate state is the first one that matches the URL /test, which would be the state 'test.subs1'
Hope this helps someone

UI Router update parent view from child

I'm new to using Angular UI Router and I seem to be having difficulty being able to update a parent view from it's child view.
I have the following HTML structure (restructured for easier reading, obviously views are in separate html files).
<div ui-view="main">
{ main content }
<div ui-view="tab">
{ tabbed content }
</div>
</div>
Inside tab I have the following sref:
<span ui-sref="silverstone.platforms.view({_id: platform._id})">{{platform.name}}</span>
And here are my states: (I'm using webpack, hence require)
$stateProvider
.state('silverstone', {
url: '/silverstone',
views: {
'main': {
controller: 'SilverstoneCtrl',
template: require('./templates/index.html')
}
}
});
$stateProvider
.state('silverstone.platforms', {
url: '/platforms',
views: {
'tab': {
controller: 'SilverstoneCtrl',
template: require('./templates/platforms.html')
}
}
});
$stateProvider
.state('silverstone.platforms.view', {
url: '/:_id',
views: {
'main': {
controller: 'SilverstoneCtrl',
template: require('./templates/platform-view.html')
}
}
});
When the above sref is clicked, the "main" view needs to be updated. The URL is updating but the views aren't...?
I was missing # in my silverstone.platforms.view state to explicitly address the parent view.
$stateProvider
.state('silverstone.platforms.view', {
url: '/:_id',
views: {
'main#': {
controller: 'SilverstoneCtrl',
template: require('./templates/platform-view.html')
}
}
});
Use reload: true parameter in yout ui-sref links like this.
ui-sref="silverstone.platforms.view({_id: platform._id})" ui-sref-opts="{reload: true}"
Edit:
Maybe your problem are caused by nested states combined with flat template structure.
Other solution may be to properly nest your templates, as states.
For example we have states app and app.substate, then we have two templates for both states. Tempalte of app state contains ui-view directive. (that means every state contains new ui-view directive for injecting of substate template). States are nested by default, this would represent appropriately nested templates.

Ionic not reflecting Changes to parent scope

I have done this many time in pure angular.js with ng-router but dont know whats wrong with it here in ionic using ui-router
I want to have some $scope properties/objects on parent scope that i can read and modify from child scopes and they can reflect every where (without factory), so that refreshing between states doent loose data and i have common place to keep it
What i have tried:
I first tried whole app routes to be wrapped inside a div
<div ng-controller="appCtrl">
<div ui-view></div>
</div>
This doesn't helped so i made app state as an abstract state and controller and now all states inherit from it
.state('app', {
url: "/app",
abstract: true,
controller : 'appCtrl',
templateUrl: "templates/app.html"
})
.state('app.login', {
url: "/login",
parent:"app",
//abstract: true,
controller : 'loginCtrl',
templateUrl: "templates/login.html"
})
.state('app.form', {
url: "/form/:selectedForm",
//abstract: true,
//reload:true,
parent:"app",
controller : 'formCtrl',
templateUrl: "templates/selected-form.html"
})
Problem while trying:
Problem is that lets say i have a property $scope.selectedForm=21; on appCtrl , i can access it correctly(shows 21 in all states) but cant modify it ($scope.selectedForm=22;) it doesnt reflects back
Dummy cotrollers
appctrl(parent):
.controller('appCtrl', function ($scope) {
$scope.InspectedForms = [];
$scope.currentForm= {}
$scope.selectedForm=21;
})
formCtrl(child):
.controller('formCtrl', function ($scope, $stateParams, authService) {
$scope.selectedForm=22;
});
The answer was very obvious, how come I forgot about dot rule
Wasted my several hours, want to post it as if anyone else gets stuck to this kind of problem
Changes are only reflected on objects and array and not on primitive types
Parent:
app.controller('Parentctrl',function($scope){
$scope.parentprimitive = "some primitive value"
$scope.parentobj = {};
$scope.parentobj.parentproperty = "somevalue";
});
Child:
app.controller('ctrlChild',function($scope){
$scope.parentprimitive = "this will NOT modify the parent"
$scope.parentobj.parentproperty = "this WILL modify the parent";
});
Here are details about the inheritance

How to get $stateParams in parent view?

I want to make tabs with tab-content.
tab-content has it's own view.
Here is code sample
(function () {
angular
.module('infirma.konfiguracja', ['ui.router'])
.config(routeConfig)
;
routeConfig.$inject = ['$stateProvider'];
function routeConfig($stateProvider) {
$stateProvider
.state('app.konfiguracja', {
url: 'konfiguracja/',
views: {
'page#app': {
templateUrl: 'app/konfiguracja/lista.html',
controller: 'konfiguracjaListaCtrl',
controllerAs: 'vm'
}
},
ncyBreadcrumb: {label: "Ustawienia systemu"}
})
.state('app.konfiguracja.dzial', {
url: '{dzial:.*}/',
views: {
'dzial#app.konfiguracja': {
templateUrl: 'app/konfiguracja/dzial.html',
controller: 'konfiguracjaDzialCtrl',
controllerAs: 'vm'
}
},
ncyBreadcrumb: {label: "{{vm.nazwaDzialu}}"}
})
;
}
})();
I want to mark selected tab which is in parent state (app.konfiguracja).
Problem is that when entering url like /konfiguracja/firmy/ there is no $stateParams.dzial in app.konfiguracja controller
How to fix it?
I created working example for your scenario here. I would say, that there at least two ways.
The first, general way, how we should use the UI-Router and its selected params in parent views (to mark selected tab/link), should be with a directive **ui-sref-active**:
ui-sref-active="cssClassToBeUsedForSelected"
So this could be the usage:
<a ui-sref="app.konfiguracja.dzial({dzial: item.id})"
ui-sref-active="selected"
>{{item.name}}</a>
The second approach (my preferred) would be to use a reference Model, created in parent $scope, and filled in a child:
.controller('konfiguracjaListaCtrl', ['$scope', function ($scope, )
{
$scope.Model = {};
}])
.controller('konfiguracjaDzialCtrl', ['$scope', function ($scope)
{
$scope.Model.dzial = $scope.$stateParams.dzial;
// we should be nice guys and clean after selves
$scope.$on("$destroy", function(){ $scope.Model.dzial = null });
}])
usage could be then like this
<span ng-if="item.id == Model.dzial">This is selected</span>
How is the second approach working? check the DOC:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Check that all in action here

Angular-ui.router: Update URL without view refresh

I have an Angular SPA that presents a variety of recommendation lists, and a Google Map of locations, based on different cuts of some restaurant data (see m.amsterdamfoodie.nl). I want each of these lists to have their own URL. In order for Google to crawl the different lists I use <a> tags for the offcanvas navigation.
At present the <a> tag causes a view refresh, which is very noticeable with the map.
I can prevent this using ng-click and $event.preventDefault() (see code snippets below), but then I need to implement a means of updating the browser URL.
But in trying Angular's $state or the browser's history.pushstate, I end up triggering state changes and the view refresh...!
My question is therefore how can I update a model and the URL, but without refreshing the view? (See also Angular/UI-Router - How Can I Update The URL Without Refreshing Everything?)
I have experimented with a lot of approaches and currently have this html
Budget
In the controller:
this.action = ($event) ->
$event.preventDefault()
params = $event.target.href.match(/criteria\/(.*)\/(.*)$/)
# seems to cause a view refresh
# history.pushState({}, "page 2", "criteria/"+params[1]+"/"+params[2]);
# seems to cause a view refresh
# $state.transitionTo 'criteria', {criteria:params[1], q:params[2]}, {inherit:false}
updateModel(...)
And, what is I think is happening is that I am triggering the $stateProvider code:
angular.module 'afmnewApp'
.config ($stateProvider) ->
$stateProvider
.state 'main',
url: '/'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
.state 'criteria',
url: '/criteria/:criteria/:q'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
One possible clue is that with the code below if I load e.g. http://afmnew.herokuapp.com/criteria/cuisine/italian then the view refreshes as you navigate, whereas if I load http://afmnew.herokuapp.com/ there are no refreshes, but no URL updates instead. I don't understand why that is happening at all.
This is an example of the way to go if I understand correctly:
$state.go('my.state', {id:data.id}, {notify:false, reload:false});
//And to remove the id from the url:
$state.go('my.state', {id:undefined}, {notify:false, reload:false});
From user l-liava-l in the issue https://github.com/angular-ui/ui-router/issues/64
You can check the $state API here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state
Based on our previous discussions, I want to give you some idea, how to use UI-Router here. I believe, I understand your challenge properly... There is a working example. If this not fully suites, please take it as some inspiration
DISCLAIMER: With a plunker, I was not able to achieve this: http://m.amsterdamfoodie.nl/, but the principle should be in that example similar
So, there is a state definition (we have only two states)
$stateProvider
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
},
'right#main' : { templateUrl: 'tpl.right.html',},
'map#main' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list#main' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'map' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
}];
This would be our main tpl.layout.html
<div>
<section class="main">
<section class="map">
<div ui-view="map"></div>
</section>
<section class="list">
<div ui-view="list"></div>
</section>
</section>
<section class="right">
<div ui-view="right"></div>
</section>
</div>
As we can see, the main state does target these nested views of the main state: 'viewName#main', e.g. 'right#main'
Also the subview, main.criteria does inject into layout views.
Its url starts with a sign ^ (url : '^/criteria/:criteria/:value'), which allows to have / slash for main and not doubled slash for child
And also there are controllers, they are here a bit naive, but they should show, that on the background could be real data load (based on criteria).
The most important stuff here is, that the PARENT MainCtrl creates the $scope.Model = {}. This property will be (thanks to inheritance) shared among parent and children. That's why this all will work:
app.controller('MainCtrl', function($scope)
{
$scope.Model = {};
$scope.Model.data = ['Rest1', 'Rest2', 'Rest3', 'Rest4', 'Rest5'];
$scope.Model.randOrd = function (){ return (Math.round(Math.random())-0.5); };
})
.controller('ListCtrl', function($scope, $stateParams)
{
$scope.Model.list = []
$scope.Model.data
.sort( $scope.Model.randOrd )
.forEach(function(i) {$scope.Model.list.push(i + " - " + $stateParams.value || "root")})
$scope.Model.selected = $scope.Model.list[0];
$scope.Model.select = function(index){
$scope.Model.selected = $scope.Model.list[index];
}
})
This should get some idea how we can use the features provided for us by UI-Router:
Absolute Routes (^)
Scope Inheritance by View Hierarchy Only
View Names - Relative vs. Absolute Names
Check the above extract here, in the working example
Extend: new plunker here
If we do not want to have map view to be recreated, we can just omit that form the child state def:
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
// 'map' : {
// templateUrl: 'tpl.map.html',
// controller: 'MapCtrl',
//},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
Now our map VIEW will be just recieving changes in the model (could be watched) but view and controller won't be rerendered
ALSO, there is another plunker http://plnkr.co/edit/y0GzHv?p=preview which uses the controllerAs
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
controllerAs: 'main', // here
},
...
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
controllerAs: 'list', // here
},
},
})
and that could be used like this:
<h4>{{main.hello()}}</h4>
<h4>{{list.hello()}}</h4>
The last plunker is here
you can use scope inheritance to update url without refreshing view
$stateProvider
.state('itemList', {
url: '/itemlist',
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
//abstract: true //abstract maybe?
}).state('itemList.itemDetail', {
url: '/:itemName/:itemID',
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail',
resolve: {
'CurrentItemID': ['$stateParams',function ($stateParams) {
return $stateParams['itemID'];
}]
}
})
if child view is inside parent view both controllers share same scope.
so you can place a dummy (or neccessary) ui-view inside parent view which will be populated by child view.
and insert a
$scope.loadChildData = function(itemID){..blabla..};
function in parent controller which will be called by child controller on controller load. so when a user clicks
<a ui-sref="childState({itemID: 12})">bla</a>
only child controller and child view will be refreshed. then you can call parent scope function with necessary parameters.
The short answer ended up being do not put the map inside a view that changes. The accepted answer provides a lot more detail on how to structure a page with sub-views, but the key point is not to make the map part of the view but to connect its behaviour to a view that does change and to use a Controller to update the market icons.

Resources