can't get nested views to work with ui-router - angular-ui-router

I have been able to switch out my angular router code with ui-router and it works fine with a basic setup. But on my page I have a partial that I want to reuse in multiple views and I can't get that working. Can anyone see an issue with my code below.
routes....
$stateProvider
.state( 'jobs', {
url: '/jobs',
templateUrl: templates.jobs,
views: {
'jobs': {
templateUrl: templates.jobList,
controller: 'JobListController'
},
'alert': {
templateUrl: templates.alert,
controller: 'AlertController'
}
}
})
templates.jobs file...
<div ui-view="jobs"></div>
<div ui-view="alert"></div>
and then the templates.jobList and templates.alert are just regular html blocks.
I have a main page with just a where my app initially loads. It's within this ui-view that I want to load the templates.jobs view along with it's nested views.
I've found that if I remove the templateUrl: templates.jobs from my jobs state and then move the two ui-views from that jobs file to my main html file it works. However, my main file needs to be able to load many potential views - the ui-view on the main html is just a placeholder where the rest of the application lives. So I can't have those two ui-views in my main file. Is there a way to make this work? Thanks.

Also set the parent view of jobs state in the views object and use absolute targeting:
$stateProvider.state('jobs', {
url: '/jobs',
views: {
'#': {
template: '<div ui-view="jobs"></div><div ui-view="alert"></div>',
controller: function () {}
},
'jobs#jobs': {
template: '<h2>Joblist</h2>',
controller: function () {}
},
'alert#jobs': {
template: '<h2>alert</h2>',
controller: function () {}
}
}
});
Working example on Plunker: http://plnkr.co/edit/AR8RK1Ik1xeGL0xUBV6e?p=preview
Reference: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views#view-names---relative-vs-absolute-names

Related

implementing angular-ui-router 'otherwise' state

If a user types myURL/ or myURL/#/ or even myURL/#/foo they get to my index page.
But if they type myURL/foo, they get a 404. This is terrible. They should instead be redirected to /.
I am trying to implement this and am not having a lot of luck.
(function() {
'use strict';
angular
.module('myApp')
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('index', {
name: 'index',
url: '/',
templateUrl: 'js/views/page1.html',
controllerAs: 'page1Controller',
data: { pageTitle: 'Main' }
})
.state('page2', {
name:'page2',
url: '/page2/:id',
templateUrl: 'js/views/page2.html',
controllerAs: 'page2Controller',
data: { pageTitle: 'page2' }
})
$urlRouterProvider.otherwise('/');
}]);
})();
I have looked at dozens of articles, and nowhere do I seem to be able find this simple case handled.
On the official docs it is mentioned that you can pass $injector and $location to the function otherwise.
Their example looks like this:
app.config(function($urlRouterProvider){
// if the path doesn't match any of the urls you configured
// otherwise will take care of routing the user to the specified url
$urlRouterProvider.otherwise('/index');
// Example of using function rule as param
$urlRouterProvider.otherwise(function($injector, $location){
... some advanced code...
});
})
What you can do to achieve your goal is to create a state, and whenever something it's not matched and enters otherwise fct, send it to that state.
$urlRouterProvider.otherwise(function($injector, $location){
$injector.get('$state').go('404');
});
I have not tested this but should work.

Vue 2 and child component events with multiple components

I've read this question / answer, and whilst that works when the parent contains a specific component, in my scenario the parent contains this:
<component :is="currentView"></component>
and the value of currentView determines which component is 'loaded' at any particular time. So, using v-on:event="handler" in the component tag here means all child components must use the same event name :(. Is it possible in the created() function of the parent to set up specific handlers which will be called regardless of which component is currently 'in view', and when they each might send different events? For example, I have a login component which sends a 'login' event, and a data view component which sends an 'update' event. In the parent, I want something like this:
this.$on('login', doLogin)
this.$on('update', doUpdate)
However, that's listening to events from itself, not its children. I also tried giving the component a ref:
<component ref="main" :is="currentView"></component>
and using this.$refs.main.$on('login', doLogin), but the $refs object isn't created until the initial render, so that can't go in the mounted() method (too early), and in the updated() method it will be repeatedly called which I'm sure isn't a good idea...
you could set a global event and then pass the name of the action as a part of the payload, i.e
const Login = {
template: `<h1>Login</h1>`,
mounted() {
this.$emit('global-event', {
action: 'login',
data: {
user: 'foo',
},
})
},
}
const Update = {
template: `<h1>Update</h1>`,
mounted() {
this.$emit('global-event', {
action: 'update',
data: {
user: 'foo bar',
},
})
},
}
new Vue({
el: '#app',
data: {
called: {
update: 0,
login: 0,
},
currentView: 'login',
},
components: {
Login,
Update,
},
methods: {
// within doSomething you would process the various events based on their payload.action
doSomething (payload) {
this.called[payload.action]++
},
toggleView () {
this.currentView = this.currentView === 'login' ? 'update' : 'login'
},
},
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<component #global-event="doSomething" :is="currentView"></component>
<button #click="toggleView">toggle view</button>
<pre>{{ called }}</pre>
</div>
Gah! After spending ages searching, finding nothing, then posting here, I of course immediately stumble on the answer.
But this only applies if you have control over the child components (I'm building all of them). In the child, you simply:
this.$parent.$emit('login', {some: 'data'})
And then set up the event listeners with this.$on('login', doLogin) in the parent as normal in the created() method.
I'm sure there will be a scenario where I'm using a third party component that simply calls this.$emit(), and at the moment, I can't see any alternative to v-on:eventName="handler" in the parent.

Angular ui-router with spa using sticky states not preserving scroll position on first back button

I'm creating an Angular mobile SPA app and part of the requirements have a substantial amount of list >> details pages. After they filter to populate the list, they can click on the item to go to a details page for it (with crud options if allowed, which rules out just using a modal). I'm saving the relevant data and things in a service, but this does not help maintain the scroll position and ui-router-extras sticky state seems like it would be ideal. I'm using requirejs as well, in case that's relevant. Also, I am using a Kendo ListView bound with a kendo.dataSource if that is relevant.
I've implemented it, but I'm not sure I've done it correctly. I scroll down and click on a list item and it goes to the details. When I click the back button (a directive that uses $window.history.back();, although I tried using $state.go and it did the same thing), it will go back and used the "cached" version, but will be at the top of the list. If I scroll down and click on another item and then go back, it will maintain the scroll position like I expect.
I turned on $stickyStateProvider.enableDebug(true); and the output looks identical from the first to the subsequent calls.
Here's my states config:
return app.config(function ($stateProvider, $stickyStateProvider, $urlRouterProvider) {
$stickyStateProvider.enableDebug(true);
$urlRouterProvider.otherwise(function ($injector, $location) {
var $state = $injector.get("$state");
$state.go("home");
});
$stateProvider.state('home', {
url: '/home',
templateUrl: 'app/views/home.html',
controller: 'homeController',
controllerAs: "vm"
})
.state('cmmenu', {
url: '/cmmenu',
templateUrl: 'app/views/cmmenu.html',
controller: 'cmmenuController',
controllerAs: "vm"
})
.state('areainquiry', {
//sticky: true,
abstract: true,
url: '/areainquiry',
templateUrl: 'app/views/areainquiry.html'
})
.state('areainquiry.list', {
url: '/areainquirylist',
views: {
'areainquirylist#areainquiry': {
templateUrl: 'app/views/areainquirylist.html',
controller: 'areainquirylistController',
controllerAs: "vm"
}
},
sticky: true,
deepStateRedirect: true
})
.state('areainquiry.details', {
url: '/areainquirydetails/:areaInquiryId',
views: {
'areainquirydetails#areainquiry': {
templateUrl: 'app/views/areainquirydetails.html',
controller: 'areainquirydetailsController',
controllerAs: "vm"
}
}
})
.state('login', {
url: '/login',
templateUrl: 'app/views/login.html',
controller: 'loginController',
controllerAs: "vm"
});
})
Here is the areainquiry.html that is the abstract parent view:
<div ui-view="areainquirylist" ng-show="$state.includes('areainquiry.list')"></div>
<div ui-view="areainquirydetails" ng-show="$state.includes('areainquiry.details')"></div>
Here is the code that is opening this screen. I do not have a named view in my index.html and all views up to areainquiry.html are using the root un-named view (I gave it a name "body" and changed all of them to use it, but it worked the same):
<div class="col-xs-6 col-md-4">
<a class="btn btn-default fullwidth" ui-sref="areainquiry.list">
<span class="{{ vm.areaInquiryButtonIcon }}"></span>
<br />
{{ vm.areaInquiryButtonText }}
</a>
</div>
I was trying to figure out a way to do it without the abstract state and just through a parent areainquiry and child areainquiry.detail, but couldn't get it working that way, so went this route.
Never mind. It is working. I'm not sure how I fixed it. I was working through another issue (had to do with a factory service), so I'm assuming that fixed it.

ui-router nested states with parameters throw extention

I'm trying to make some nested states with dynamic options.
This kind of states works fine: app, app.process, app.process.step2
But my situation is little different because I want to pass some data in URL.
Here is my states
.state('app.process/:type', {
url: "/process/:type",
views: {
'menuContent1': {
templateUrl: "templates/intro.html",
controller: 'IntroCtrl',
}
}
})
.state('step/:type/:step', {
url: "/process/:type/:step",
parent: 'app.process',
views: {
'proiew': {
templateUrl: "templates/processes/increase.html",
controller: "increaseCtrl",
}
}
})
While trying to run this
$state.go('step/:type/:step', {type:$stateParams.type, step:2});
I get an error
Error: Could not resolve 'new/:type/:step' from state 'app.process/:type'
at Object.transitionTo (http://localhost:8100/lib/ionic/js/ionic.bundle.js:33979:39)
at Object.go (http://localhost:8100/lib/ionic/js/ionic.bundle.js:33862:19)
at Scope.$scope.goNext (http://localhost:8100/js/controllers/IntroCtrl.js:11:18)
at http://localhost:8100/lib/ionic/js/ionic.bundle.js:18471:21
at http://localhost:8100/lib/ionic/js/ionic.bundle.js:43026:9
at Scope.$eval (http://localhost:8100/lib/ionic/js/ionic.bundle.js:20326:28)
at Scope.$apply (http://localhost:8100/lib/ionic/js/ionic.bundle.js:20424:23)
at HTMLButtonElement.<anonymous> (http://localhost:8100/lib/ionic/js/ionic.bundle.js:43025:13)
at http://localhost:8100/lib/ionic/js/ionic.bundle.js:10478:10
at forEach (http://localhost:8100/lib/ionic/js/ionic.bundle.js:7950:18)
any suggestions?
there are many different ways to pass parameters to a state, first of all you can simply set the parameters on the click of the link and then take them from the scope.
but if we are talking on something more reliable and readable i suggest this post, and specifically i like using Resolve. i'll put here an example of resolve, but i encourage you to read and find out what works best for you:
$stateProvider
.state("customers", {
url : "/customers",
templateUrl: 'customers.html',
resolve: {
// A string value resolves to a service
customersResource: 'customersResource',
// A function value resolves to the return
// value of the function
customers: function(customersResource){
return customersResource.query();
}
},
controller : 'customersCtrl'
});

ui-router Abstract State Combination

I created two json files where I'm getting the data through a controller homeCtrl and articleCtrl. Than I have a state to display all my data places and articles
.state('overview', {
abstract: true,
url: '/overview',
templateUrl: '_/partial/overview/overview.html',
controller: 'homeCtrl',
resolve: {
places: ['$http', function($http) {
return $http.get('_/api/place.json').then(function(response) {
return response.data;
})
}]
}
})
.state('overview.all', {
url: '/all',
templateUrl: '_/partial/overview/overview-all.html',
controller: 'homeCtrl',
resolve: {
places: ['$http', function($http) {
return $http.get('_/api/place.json').then(function(response) {
return response.data;
})
}]
}
})
.state('overview.articles', {
url: '/articles',
templateUrl: '_/partial/overview/overview-articles.html',
controller: 'articleCtrl',
resolve: {
articles: ['$http', function($http) {
return $http.get('_/api/article.json').then(function(response) {
return response.data;
})
}]
}
})
Afterwards I created two states to get the data by id
.state('overview.detail-place', {
url: '/:id/place',
templateUrl: '_/partial/details/detail-place.html',
controller: function($scope, $stateParams){
$scope.place = $scope.places[$stateParams.id];
}
})
.state('overview.detail-article', {
url: '/:id/article',
templateUrl: '_/partial/details/detail-article.html',
controller: function($scope, $stateParams){
$scope.article = $scope.articles[$stateParams.id];
}
})
I have no problem going to the detail page of a place but when I want to go to an article I get this error, 99 being the id of the article.
TypeError: Cannot read property '99' of undefined
at new $stateProvider.state.state.state.state.state.state.state.state.controller (http://localhost:8888/%203.0v/_/js/app.js:86:53)
at d (http://localhost:8888/%203.0v/_/lib/angular.min.js:34:265)
at Object.instantiate (http://localhost:8888/%203.0v/_/lib/angular.min.js:34:394)
at http://localhost:8888/%203.0v/_/lib/angular.min.js:66:112
at http://localhost:8888/%203.0v/_/lib/angular-ui-router.min.js:7:15323
at J (http://localhost:8888/%203.0v/_/lib/angular.min.js:53:345)
at f (http://localhost:8888/%203.0v/_/lib/angular.min.js:46:399)
at http://localhost:8888/%203.0v/_/lib/angular.min.js:46:67
at j (http://localhost:8888/%203.0v/_/lib/angular-ui-router.min.js:7:14566)
at http://localhost:8888/%203.0v/_/lib/angular-ui-router.min.js:7:14835 <div class="main-container overview ng-scope" ui-view="">
The issue here is, that state 'overview.articles' does initiate the articles collection
.state('overview.articles', {
...
controller: 'articleCtrl',
resolve: {
articles: ....
So most- likely the 'articleCtrl' does place the articles into the $scope.articles.
BUT, this state: 'overview.detail-article' in fact does not have access to that collection:
.state('overview.detail-article', {
...
controller: function($scope, $stateParams){
$scope.article = $scope.articles[$stateParams.id];
}
And why? because the 'overview.detail-article' is not child state of the 'overview.articles'
But this still does not have to be enough with ui-router, check this:
Scope Inheritance by View Hierarchy Only (small cite:)
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.
So we need the detail state to be a child of articles and also nested in the parent view:
.state('overview.articles.detail-article', { ...
A plunker with working example...

Resources