I am trying to set up some states as follows
state('search', {
url: '',
templateUrl: '/modules/core/views/search.html',
controller: 'SearchController'
}).
state('check', {
controller: 'CheckController',
abstract: true,
template: '<ui-view></ui-view>',
params: {
subject: null
}
}).
state('check.status', {
templateUrl: '/modules/core/views/check-status.client.view.html'
}).
state('check.personal', {
templateUrl: '/modules/core/views/check-personal.client.view.html'
});
Search results are passed into the 'check' state with parameterised ui-sref's.
Within each child state of 'check' I want to change some properties on subject and then call $state.go (preferably without passing subject as a parameter) to move along to the next state in the journey.
I'm finding though that
CheckController is reloaded if the parameter is not supplied on $state.go to subsequent states or if I supply it but change it slightly.
If CheckController is reloaded, changes made to the subject object aren't persisted through the transition as if $stateParams contains a copy of the subject object taken at some point earlier by ui-router.
Is there a way to prevent / control either of these behaviours?
Edit:
I found this pattern works out:
state('check.status', {
templateUrl: '/modules/core/views/check-status.client.view.html',
params: {
subject: null
},
controller: function($scope, $stateParams) {
$scope.$parent.subject = $stateParams.subject;
}
}).
Removing the param from the abstract parent, adding it to the first state in the journey and assigning the property on the parent scope with a small controller.
I think in your config for your child states you can tell ui-router to prevent reloading with this:
state('check.status', {
templateUrl: '/modules/core/views/check-status.client.view.html',
reloadOnSearch: false
})
Related
I am re-structuring a state into an abstract state with a child that contains many nested views.
I need to preserve the oldState, and my goal is to have any reference to oldState redirect to 'app.newState.home'.
The current implementation I am using below (using redirectTo, Redirect a state to default substate with UI-Router in AngularJS) works in terms of redirecting to the child state of a different abstract state. HOWEVER - I get this error from Segment analytics:
https://api-iam.intercom.io/ping/events 422 (Unprocessable Entity)
[{"code":"422","message":"Cannot have more than 120 active event names"}]}
.state('app.oldState', {
url: '/oldState',
redirectTo: 'app.newState.home',
})
.state('app.newState', {
url: '/oldState',
// abstract: true,
templateUrl: helper.basepath('anotherTemplate'),
resolve: helper.resolveFor('datatables','easypiechart','ngDialog','angularFileUpload', 'filestyle', 'taginput'),
})
.state('app.newState.home', {
url: '',
views: {
'firstView':{
templateUrl: helper.basepath('templateOne'),
controller: 'ControllerOne'
},
'secondView':{
templateUrl: helper.basepath('templateTwo'),
controller: 'ControllerOne'
},
'thirdView':{
templateUrl: helper.basepath('templateThree'),
controller: 'ControllerOne'
},
'fourthView':{
templateUrl: helper.basepath('templateFour'),
controller: 'ControllerTwo'
},
}
})
Why is this error being thrown and how can I properly resolve this issue???
Intercom permits max 120 event names. This is documented here
https://docs.intercom.com/the-intercom-platform/track-events-in-intercom
under the header "Is there a limit on the number of event types I can send?".
The documentation also explains how to "archive" events, which presumably resolves this situation until one hits the limit again.
I'm trying to route to a url with the selected object's id value within a nested state but it's not working.
ROUTER:
.state('sessions.editor', {
url: '/editor/:id',
templateUrl: 'views/editor.html',
controller: 'EditorCtrl'
})
CONTROLLER (EditorCtrl):
$scope.theSession.$id = $stateParams.id;
//$scope.session object is successfully returned with the $id property.
PREVIOUS CONTROLLER:
//when button is clicked
$state.go('sessions.editor');
However, when I try to set it to $stateParams, the id property becomes undefined. I tested this with another property on $scope.session and that property becomes undefined also when I try to set it $stateParams.
I'm thinking the trouble has something to do with the state being nested. Is this correct?
I believe you need to add the params key to the state for UI-Router, example:
.state('sessions.editor', {
url: '/editor/:id',
controller: 'EditorCtrl',
templateUrl: 'views/editor.html',
params: {
id: {
value : '',//Default
squash: false
}
}
})
Then use your existing implementation in the EditorCtrl for $stateParams.
I have three states: one abstract state and two concrete states inheriting from the abstract one. I am transitioning from one state to another and I noticed that the variables that were in the $scope in one state are no longer in $scope after I have transitioned to the other state: see $scope.signupForm.member.email below.
Can someone please advise?
My UI router configuration:
$stateProvider
.state('signup', {
abstract: true,
views: {
'#': {
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.success.html'
}
}
})
Relevant snippet from my controller:
signupService.signup($scope.signupForm)
.success(function () {
//TODO: issue with controller no longer being in scope: signupForm.member.email is not displayed in template
$state.go('signup.success');
});
My email input (from signup.form.html):
<input type="email" name="email"
placeholder="{{'SIGNUP_FORM_EMAIL' | translate}}"
ng-model="signupForm.member.email" ng-required="true"
ng-pattern="EMAIL_PATTERN"
class="form-control"/>
Where I try to display the email (from signup.success.html):
<div class="panel-body">
success!
check your email at: {{signupForm.member.email}}
</div>
edit 1:
If I pull up the controller one level - by putting it into the abstract state i.e. 'signup', then signupFormCtrl - the angular form controller - is undefined!
<form name="signupFormCtrl" ng-submit="signup()" novalidate>
edit 2:
This is what I tried:
.state('signup', {
abstract: true,
views: {
'#': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
templateUrl: 'signup/views/signup.success.html'
}
}
})
Thre is a working plunker
This is feasable with UI-Router built-in features. We will need to introduce controller for our base state:
.state('signup', {
abstract: true,
templateUrl: 'signup/views/signup.html',
controller: 'SignupBaseCtrl',
})
Inside of this controller we would define a Model inside of a $scope:
.controller('SignupBaseCtrl', ['$scope', function ($scope) {
$scope.signupForm = { member : { email : null }};
}])
And now, if we would work with a model like this:
{{signupForm.member.email}}
In any of our child states, we would be accesing the same model, the same reference object singupForm.
And how it is possible? how it is working? All is clearly explained here:
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.
You can also check: Controller from Parent Layout is not access by child views
Data from one scope can not be accessed from a different scope. try using the rootScope for data that is to be used across scopes use $root in templates as in {{$root.signupForm.member.email}} and $rootScope in controllers as in $rootScope.signupForm.member.email
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.
I recently migrated from ngRoute to ui-router. I have a page that has 2 sections.
Right section displays current item details,
Left section shows similar items to current item.
Once user clicks a similar item from left list, right section will reload with clicked item id and left section will stay same.
To keep left section still on user item navigations, i defined left section as an abstract state and right section as it's child state. (you cant view similar items if you arent looking to an item).
Left section (listview) is parent and contains a ui-view in HTML to embed item details.
To load similar items on page open, i need to know which item is being loaded by my child state. But i cant define same url for both abstract state and it's child state.
i tried to resolve $stateParams in abstract state with no chance.
my state configuration is below
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('item', {
//url: '/items/:itemName/:itemID'
url: '/items',
abstract: true,
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
}).state('item.itemDetails', {
url: '/:itemName/:itemID',
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail'
});
how can i access itemID from my abstract state (from ItemListController)?
i solved the problem using angular scope variable. parent contains an ui-view to show child state contents. so child and parent state share same scope therefore i can notify parent about selected item id and parent can update it's view.
parent state has a scope function like this
$scope.notifySelectedItem = function(selectedItemID){};
and child state can call this function from it's scope
$scope.notifySelectedItem($stateParams.itemID);
I would model this as a single state with two named views. The subject of your state is a single item and related data, so your state should probably revolve around that. You have described two views, one which shows the item details, and another which shows other things, but the primary subject is still the one item.
$stateProvider
.state('item', {
url: '/item/:itemID',
views: {
detail: {
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail'
}, related: {
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
}
}
});
In your parent view, add two named ui-views, where the views defined above will plug into:
<div ui-view="related"></div>
<div ui-view="detail"></div>
Now, you can enhance this using resolves to preload the data before the controller is invoked:
$stateProvider
.state('item', {
url: '/item/:itemID',
resolve: {
item: function($http, $stateParams) {
return $http.get('/item/' + $stateParams.itemID);
},
related: function($http, $stateParams) {
return $http.get('/item/' + $stateParams.itemID + "/related");
},
},
views: {
detail: {
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail'
}, related: {
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
}
}
});
app.controller("ItemListController", function($scope, related) { $scope.related = related.data; });
app.controller("ItemDetailController", function($scope, item) { $scope.item = item.data; });