angularJS nested abstract views - angularjs

I am using StateProvider library to create nested views in my AngularJS app.
I had an abstract view defined at the root and now need to define another abstract view as 2nd level child to the previously created abstract view.
Facing issues with this, Not sure if I can have nested abstract views or not. Any idea.
greatly appreciate your help.
Thanks

There could be more abstract-nested states in hierarchy. This example shows it in action, definition of these states could be like this:
$stateProvider
.state('main', {
url: "",
abstract: true,
templateUrl: 'tpl.main.html',
})
.state('main.middle', {
url: "",
abstract: true,
templateUrl: 'tpl.middle.html',
})
.state('main.middle.alpha', {
url: "/alpha",
templateUrl: 'tpl.leaf.html',
controller: function ($scope, $state){
$scope.state = $state.current;
},
})
Check the plunker. As we can see, the root (main) and its child (middle) do not use url at all... but they could..

Related

ui router - nested views with shared controller not working

I have an abstract parent view that is meant to share a controller with its nested views, before I have a main controller that is the principal of my app
.state('main', {
abstract: true,
url: '/',
templateUrl: 'app/templates/main.html',
controller: 'mainController'
})
.state('main.edit', {
abstract: true,
url: '/edit',
templateUrl: 'app/templates/edit.html',
controller: 'editController'
})
.state('main.edit.details', {
url: '/details',
templateUrl: 'app/templates/editDetailsView.html',
controller: 'editDetailsController'
})
.state('main.edit.info', {
url: '/info',
templateUrl: 'app/templates/editInfoView.html',
controller: 'editInfoController'
})
The routing works as expected, before I set de controller, I have the parent controller of the module that would be edit controller and every controller of each view
The error:
"Error: [ng:areq] Argument 'editController' is not a function, got undefined
http://errors.angularjs.org/1.5.7/ng/areq?p0=editController&p1=not%20aNaNunction%2C%20got%20undefined
minErr/<#http://localhost:3000/js/libs.min.js:5:4923
assertArg#http://localhost:3000/js/libs.min.js:5:19120
assertArgFn#http://localhost:3000/js/libs.min.js:5:19322
$ControllerProvider/this.$get</<#http://localhost:3000/js/libs.min.js:7:31336
z/<.compile/<#http://localhost:3000/js/libs.min.js:15:2556
bind/<#http://localhost:3000/js/libs.min.js:5:12865
invokeLinkFn#http://localhost:3000/js/libs.min.js:7:22121
nodeLinkFn#http://localhost:3000/js/libs.min.js:7:4193
compositeLinkFn#http://localhost:3000/js/libs.min.js:6:26125
compile/<#http://localhost:3000/js/libs.min.js:6:24834
compilationGenerator/<#http://localhost:3000/js/libs.min.js:6:31172
l#http://localhost:3000/js/libs.min.js:15:1755
y/l.compile/<#http://localhost:3000/js/libs.min.js:15:2183
bind/<#http://localhost:3000/js/libs.min.js:5:12865
invokeLinkFn#http://localhost:3000/js/libs.min.js:7:22121
nodeLinkFn#http://localhost:3000/js/libs.min.js:7:4193
compositeLinkFn#http://localhost:3000/js/libs.min.js:6:26125
compile/<#http://localhost:3000/js/libs.min.js:6:24834
z/<.compile/<#http://localhost:3000/js/libs.min.js:15:2764
bind/<#http://localhost:3000/js/libs.min.js:5:12865
invokeLinkFn#http://localhost:3000/js/libs.min.js:7:22121
nodeLinkFn#http://localhost:3000/js/libs.min.js:7:4193
compositeLinkFn#http://localhost:3000/js/libs.min.js:6:26125
compile/<#http://localhost:3000/js/libs.min.js:6:24834
compilationGenerator/<#http://localhost:3000/js/libs.min.js:6:31172
l#http://localhost:3000/js/libs.min.js:15:1755
y/l.compile/</<#http://localhost:3000/js/libs.min.js:15:2175
$RootScopeProvider/this.$get</Scope.prototype.$broadcast#http://localhost:3000/js/libs.min.js:9:24515
v/y.transitionTo/y.transition<#http://localhost:3000/js/libs.min.js:14:29498
processQueue#http://localhost:3000/js/libs.min.js:9:8733
scheduleProcessQueue/<#http://localhost:3000/js/libs.min.js:9:9000
$RootScopeProvider/this.$get</Scope.prototype.$eval#http://localhost:3000/js/libs.min.js:9:22223
$RootScopeProvider/this.$get</Scope.prototype.$digest#http://localhost:3000/js/libs.min.js:9:19908
$RootScopeProvider/this.$get</Scope.prototype.$apply#http://localhost:3000/js/libs.min.js:9:22650
done#http://localhost:3000/js/libs.min.js:8:7950
completeRequest#http://localhost:3000/js/libs.min.js:8:11757
createHttpBackend/</xhr.onload#http://localhost:3000/js/libs.min.js:8:12689
"
If I erase the editController, it works well, but what can I do if I need the shared controller of my views, other way it works if i set a the parent controller like this
.state('main.edit', {
abstract: true,
url: '/edit',
templateUrl: 'app/templates/edit.html',
controller: function($scope){
console.log('edit parent controller');
}
not the idea.
I spent last 24 hours trying to find this. I was about to post a question/add to yours but I finally found my issue.
In my case, after refactoring to the recommended format (separate file for module definition, controller definition, service definition etc) I left [] in some controller definition files like this in a few files:
(function () {
'use strict';
angular
.module('app.landing', [])
.controller('LandingController', LandingController);
That was overwriting app.landing module that has already been created. After reviewing and cleaning up all module definitions and component definitions that use those module references, it all works now. Phew.
(function () {
'use strict';
angular
.module('app.landing')
.controller('LandingController', LandingController);
Interestingly enough I read S/O answers with this very issue, i.e. Error: [ng:areq] from angular controller but I couldn't spot those crazy brackets in 2 files. It makes me question best practices endorsed by angular team a bit as if i had it all in one file, this would not be an issue. When I have 50 files to look over and I've been staring at the screen for hours, this is pretty challenging. I wish there was a check for multiple module definitions.

Nested states or views for layout with leftbar in ui-router?

I have the following layout:
Sidebar and Headerbar will always be present though their content is context-specific.
I think there are two options here: nested states (sidenav > Headerbar > Content) or with views (if I understand that correctly). I'm still struggling to get my head wrapped around ui-router regardless of how many videos and articles I've read.
Clicking on Sidenav would load a state (or view) into Content and Headerbar would adjust its content based on whatever is loaded into Content.
My sense is that nested states seem like the simplest direct approach, particularly when thinking about inheritance.
Looking at it from another point of view, these seem like they could be siblings (although inheritance issues probably make me wrong). My inkling is that views would allow me more flexibility in the future with sub-items and such.
And of course ng-include and directives could play into this.
Being new to ui-router could someone slap me in the right direction? Where I'm stuck is loading the home view. I want my users to see their dashboard in the Content section once they log in. And then, how do I load new elements into Content as the user navigates from the Sidebar?
One way how to design scenario with 1) side bar, 2) action section and 3) main area could be like in this working example
Firstly the root state. Here is root state named 'index'. It is abstract and could do some resolve for us. It does not effect child state naming and does not extend the url (because is undefined)
$stateProvider
.state('index', {
abstract: true,
//url: '/',
views: {
'#' : {
templateUrl: 'layout.html',
controller: 'IndexCtrl'
},
'top#index' : { templateUrl: 'tpl.top.html',},
'left#index' : { templateUrl: 'tpl.left.html',},
'main#index' : { templateUrl: 'tpl.main.html',},
},
})
The first real state is list, and it inherits from parent but with an attribute parent: 'index', so the parent name is not effecting the state name.
Advantage is, that it could inherit lot of resolved stuff. Also, the root state could be loaded once, for all other parent states
.state('list', {
parent: 'index',
url: '/list',
templateUrl: 'list.html',
controller: 'ListCtrl'
})
This is the real power of UI-Router, because now we can see that child is injecting stuff into two places - 1) action section and 2) main area
.state('list.detail', {
url: '/:id',
views: {
'detail#index' : {
templateUrl: 'detail.html',
controller: 'DetailCtrl'
},
'actions#index' : {
templateUrl: 'actions.html',
controller: 'ActionCtrl'
},
},
})
This way, we can use named views and multi views in real world scenario. Please, never forget how the scope definition goes:
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
I just would like to share my experience. There is
similar Q & A: Angular UI Router - Nested States with multiple layouts
and a link to working plunker
The snippet of the state def:
$stateProvider
.state('index', {
url: '/',
views: {
'#' : {
templateUrl: 'layout.html',
controller: 'IndexCtrl'
},
'top#index' : { templateUrl: 'tpl.top.html',},
'left#index' : { templateUrl: 'tpl.left.html',},
'main#index' : { templateUrl: 'tpl.main.html',},
},
})
.state('index.list', {
url: '/list',
templateUrl: 'list.html',
controller: 'ListCtrl'
})
.state('index.list.detail', {
url: '/:id',
views: {
'detail#index' : {
templateUrl: 'detail.html',
controller: 'DetailCtrl'
},
}
In a nutshell, I do use the nesting approach.
It is similar to the "core example" available here http://angular-ui.github.io/ui-router/sample/#/. It is hierarchical (entity list / detail)
And what's more, I use the hidden supper root state:
check the details here Updating resolved objects in ui.router parent states
the examle link
which is handling security related stuff - once, and shared among all child states:
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view></div>',
resolve: {objectX : function() { return {x : 'x', y : 'y'};}},
controller: 'rootController',
})
.state('home', {
parent: "root",
url: '/home',
templateUrl: 'tpl.example.html',
})
.state('search', {
parent: "root",
url: '/search',
templateUrl: 'tpl.example.html',
})
Hope it does enlighten this a bit, because the power of UI-Router I see in multiviews, view nesting, scope inheritance and the logical state machine behind

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.

AngularJS ui-router with different URL parameters

While using ui-view in AngularJS, I'm trying to use URL parameters inside of nested views.
For entities of a given type, I want to be able to show all of the entities when navigating to the url /entities, but I would also like to see only the entities of a given type if I go to entities/ofcategory/categoryName. Lastly, I also want to be able to navigate to /entities/details/entityName to see the details of one entity.
Is this structure possible?
This is my ui-routercode:
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('entities', {
url: '/entities',
templateUrl: 'app/entities/views/entities.html'
})
.state('entities.ofcategory', {
url: '/ofcategory/:categoryName',
templateUrl: 'app/entities/views/entities.ofcategory.html'
}
.state('entities.details', {
url: '/details/:entityName',
templateUrl: 'app/entities/views/entities.details.html'
});
}]);
If I'm navigating to entities/ofcategory/aname or /entities/details/aname I enter the regular entities controller instead of the category or detailsController
One option is to add an abstract state, which serves as a parent to all your entities states. In that case all your urls become relative to this parent state. Please note that you have to define a ui-view in the template of the abstract state, so it could be used for loading the child templates.
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('entities', {
url: '/entities',
abstract: true,
templateUrl: 'app/entities/views/entities.html'
})
.state('entities.all', {
url: '/',
templateUrl: 'app/entities/views/entities.all.html'
})
.state('entities.ofcategory', {
url: '/:categoryName',
templateUrl: 'app/entities/views/entities.ofcategory.html'
}
.state('entities.details', {
url: '/details/:entityName',
templateUrl: 'app/entities/views/entities.details.html'
});
}]);
app/entities/views/entities.html:
<div>
<h1>Entities<h1>
<div ui-view></div>
</div>

AngularJS transition to abstract state

I'm working on an application built with AngularJS that has two different states:
App intro wizard
App pages (with a Navigation template and nested views)
I want to default to State 1 the first time someone access the app, once done the wizard, continue on to the App pages, State 2.
My "app" state (State 2) is an abstract state because it has nested views. So I cannot transition to this state.
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('intro', {
url: '/',
templateUrl: 'templates/intro.html',
controller: 'IntroCtrl'
})
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('app.playlists', {
url: "/playlists",
views: {
'menuContent' :{
templateUrl: "templates/playlists.html",
controller: 'PlaylistsCtrl'
}
}
});
$urlRouterProvider.otherwise("/");
})
I've got the same problem as you, here is a post about abstract state: angularjs ui-router default child state and ui-sref
It explains why you can't directly access abstract state
Hope it helps.
I have added $location service to the controller of the "intro" page and use the following to transit to app state which works for me.
$location.path('app/playlists');
The menu.html is loaded and navigated to playlists.
In a service:
$location.path(
$state.href('app.some.abstract.state', { some: 'params' })
);
Or in a template ($state must be available in the local or global $scope):
<a ng-href="{{$state.href('app.some.abstract.state', { some: 'params' })}}">...</a>

Resources