I have a challenge with nested routes, in angular js, here is snippet in app.js
...
when('/profile/:id', {
templateUrl: 'partials/profile',
controller: 'profileCtrl'
}).
when('/profile/:id/editTaskList', {
templateUrl: 'partials/editTaskList',
controller: 'checkListCtrl',
activeMenu: 'editTaskList'
}).
...
I want to have to access to the "profile data" in every single route under the "/profile/:id".
"Profile Data" example is :
{
user: "demo",
title: "dev"
}
and it comes through AJAX(JSON) based on the ID in the routs
The way how it is setup now, is having a service to get the User profile and I make the service request for the profile ID in "profileCtrl".
The problem that I have, if someone share the url and goes right away to "/profile/:id/editTaskList", the "profileCtrl" doesn't fire .
I don't think by adding service request in "checkListCtrl" is a good practice , because I'll have more nested routes under the "profile" and it will require to duplicate the code in multiple controllers.
What is the best practice to fix this issue ? For me would work as well if i can fire "profileCtrl" all the time when we are "/profile/:id" or lower
you can get your data before you controllers are initalized
...
when('/profile/:id', {
templateUrl: 'partials/profile',
controller: 'profileCtrl',
resolve: {
profile: ['ProfileService','$route', function (ProfileService,$route) {
return ProfileService.getProfile($route.current.params.id);
}]
},
}).
when('/profile/:id/editTaskList', {
templateUrl: 'partials/editTaskList',
controller: 'checkListCtrl',
resolve: {
profile: ['ProfileService','$route', function (ProfileService,$route) {
return ProfileService.getProfile($route.current.params.id);
}]
},
activeMenu: 'editTaskList'
}).
...
controller will be
function myCtrl($scope,profile){
// now profile is your data
}
Related
Possible Duplicate of $http is getting called multiple times in AngularJS
Sorry I didn't have 50 reputations to comment on the same post.
We are calling the api's using http service and all the services have session id that been passed through cookiestore value. The api call doesn't work for first time as session id is null during first call and it works only for the second call.
Can anyone help us with this. We have multiple api's and all are happening twice which is actually increasing the load. And yes we have researched it.
Router.js
angular.module('adminsuite',['ngFileUpload','ui.router','ngCookies','angular-clipboard','ngAnimate', 'ngSanitize', 'ui.bootstrap','ngMessages']).constant("__env",env).config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
//$locationProvider.html5Mode(true);
$stateProvider
.state('login', {
url: '/',
views:{
header:{
templateUrl: '',
controller: ''
},
pageContent:{
templateUrl: 'Login/login3.html',
controller: 'loginController'
},
footer:{
templateUrl: 'common/footer3.html',
controller: 'footerController'
}
}
})
// HOME STATES AND NESTED VIEWS ========================================
.state('dashboard', {
url: '/dashboard',
views:{
header:{
templateUrl: 'common/header.html',
controller: 'headerController'
},
pageContent:{
templateUrl: 'dashboard/dashboard.html',
controller: 'dashboardController'
},
footer:{
templateUrl: 'common/innerFooter.html',
controller: 'footerController'
}
}
})
//SURVEY STATES
.state('survey', {
url: '/survey',
views:{
header:{
templateUrl: 'common/headerTool.html',
controller: 'headerController'
},
pageContent:{
templateUrl: 'survey/survey.html',
controller: 'surveyController'
},
footer:{
templateUrl: 'common/innerFooter.html',
controller: ''
}
}
});
// ABOUT PAGE AND MULTIPLE NAMED VIEWS =================================
})
LoginAuthenticationService.js
UserService.GetByUsername(requestData)
.then(function (user) {
console.log(user);
if (user.SessionID) {
sessionID = user.SessionID;
userDetails = user.UserProfile;
response = { success: true};
} else {
response = { success: false, message: 'Username or password is incorrect' };
}
callback(response);
});
UserService.js
function GetByUsername(user) {
//console.log(__env.apiUrl);
return $http.post(__env.apiUrl+'/UserAuthentication/login', user, {headers: { 'Content-Type': 'application/json'}}).then(handleSuccess, handleError('Error getting user by username'));
}
Api.js
$http.get(__env.apiUrl+'/UserSurvey/GetAllSurveys', {
headers: { 'Content-Type': 'application/json','SessionID':$rootScope.token}
})
.then(function(response){
console.log(response);
return response.data;
}, function(error){
console.log("error");
console.log(error);
return error;
});
Any help appreciated.
Assuming, you have declared controllers within your application using AngularUI state definitions such as the following:
$stateProvider
.state('index',
{
url : '/',
templateUrl : 'templates/home.html',
controller : 'HomeController'
})
And then, in the home.html view template, you also declare the controller using the ng-controller directive like so:
<div ng-controller="HomeController">
Then you have, without realising it, attached the controller twice (once through the state definition and twice through the ng-controller directive) which causes the code contained within the controller to execute twice as well.
You might be having a digest cycle issue.
When my AJAX module is located in plain Javascript my data renders on the first server query. However, when I place my AJAX module in an AngularJS module I must query the server twice before my data renders. Note the query is invoked from the UI.
After poking around I realized the data had indeed arrived on the first query and was assigned to all designated objects and variables but only within the scope of the AngularJS code. Of course, the data had arrived asynchronously and the UI had already gone through a digest cycle and had no reason to go again because nothing in the UI had changed. When there’s a change in the UI Angular updates everything but does nothing when the change (arriving data) comes from the server.
Subsequently, the second identical query would force a digest cycle and update all the bindings with the data that was already sitting there from the first query.
Thus the objective is to force a digest cycle from Javascript to update your UI bindings. I now force a digest cycle at the end of my callback function.
To force a digest cycle place the Angular method $scope.$apply([exp]) after your data variable assignments are complete. I found helpful details in the Angular docs at: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply and also a great explanation with working examples at: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html and an important detail about using the “controller as” syntax when forcing a digest cycle at: How to call $scope.$apply() using "controller as" syntax and hopefully this fixes the problem of the double HTTP call.
No errors.
I'm able to access a list of teams at /teams and a specific team at /teams/2 where 2 is the id of a team.
$stateProvider.state('app.teams.team', {
url: '/:team',
templateUrl: 'templates/teams/team.html',
controller: 'TeamCtrl',
resolve: {
team: function($stateParams, TeamsService) {
return TeamsService.getTeam($stateParams.team)
}
}
})
I'm not able to access the roster at /teams/2/roster.
$stateProvider.state('app.teams.team.roster', {
url: '/roster',
templateUrl: 'templates/teams/roster.html'
})
The page loads without errors, and shows templates/teams/team.html instead of templates/teams/roster.html
The problem is the way you treat your states inheritance.
With the current configuration:
app.teams.team state expect the url http://app.teams url/:team
and
app.teams.team.roster state expect the url http://app.teams.team url/roster
each child expect the parent state to hold a ui-view directive so the templateUrl can get injected to.
This mean that for the roster state you have the app.teams htmls, contains the team html, contains the roster html.
To load a diffrent html under same level stay in same hirerchial level ->
app.teams.team
app.teams.roster
I ended up making roster its own state, which works as intended.
$stateProvider.state('app.roster', {
abstract: true,
url: '/teams/:team/',
views: {
content: {
template: '<ion-nav-view></ion-nav-view>'
}
},
resolve: {
team: function($stateParams, TeamsService) {
return TeamsService.getTeam($stateParams.team)
}
}
})
$stateProvider.state('app.roster.index', {
url: 'roster',
templateUrl: 'templates/roster/index.html',
controller: 'TeamCtrl',
})
I'm making a single page application (SPA). I made a controller called InitialControler to load the data from the server at this url (local.app/init).
I want this url to be opened before any other url. I'm using ui-router, I did a $state.go('init') in the .run() function but it still load the requested page before the 'init' page
First create state called app
$stateProvider.state('app', {
abstract: true,
templateUrl: "assets/partials/container.html",
controller: 'AppCtrl',
resolve: {
init: function(MyFactory) {
return MyFactory.resolver();
}
}
});
Now, any new state you create should be child state of app state. This is also good because it become sort of your root scope. And state will not process unless your factory resolves.
This is how you create your factory
app.factory('MyFactory', function($http){
var items = [];
return {
resolver: function(){
return $http.get('my/api').success(function(data){
items = data;
})
},
get() {
return items;
}
}
});
Now in any other state
$stateProvider.state('app.items', {
url: '/items',
templateUrl: "assets/partials/items.html",
controller: function($scope, MyFactory){
$scope.items = MyFactory.get();
}
});
More on sate resolve
https://github.com/angular-ui/ui-router/wiki#resolve
If you are using ui-router then you could resolve this using nested states. For example:
$stateProvider
.state("main", {
url: "/",
template: '<div ui-view></div>',
controller: 'InitController'
})
.state("main.landing", {
url: "landing",
templateUrl: "modules/home/views/landing.html",
controller: 'LandingPageController'
})
.state("main.profile", {
url: "profile",
templateUrl: "modules/home/views/profile.html",
controller: 'ProfileController'
});
In this example you have defined 3 routes: "/", "/landing", "/profile"
So, InitController (related to "/" route) gets called always, even if the user enters directly at /landing or /profile
Important: Don't forget to include <div ui-view></div> to enable the child states controller load on this section
One way to do is, in config declare only 'init' state. And in InitialController, after data is loaded(resolve function of service call), configure other states. But in this approach, whenever you refresh the page, the url will change to local.app.init.
To stay in that particular state even after reloading, the solution I found is to have a StartUp app in which I loaded the required data and after that I bootstraped the main app manually by angular.bootstrap.
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.
First of all, apologies I don't have an example to show. The current situation is I am using UI Router and I have 3 views with a different controller each on the same page with the state name 'result'. i.e. for the head, content and sidebar.
However, only 1 of the controllers (content) return true when I call for $state.is('result'). I've also tried $state.includes('result').
When I log the $state only, all 3's state object shows the same correct \result in $state.current.name, however, if I try to log $state.current directly, the name becomes blank for 2 of them.
I've tried swapping the order of the views, but only content is returning true.
I read the doc many times, but I can't find the reason why. Please help!
.state('result', {
url: '/result',
views: {
'headView' : {
templateUrl: 'modules/core/templates/head.html'
},
'contentView' : {
templateUrl: 'modules/core/templates/content.html'
},
'sidebarView' : {
templateUrl: 'modules/core/templates/sidebar.html'
},
}
})
Place your controllers in your state view definitions where your templateUrls are defined.
E.G.
DEMO
.state('result', {
url: '/result',
views: {
'headView' : {
templateUrl: 'modules/core/templates/head.html',
controller: 'HeadViewController',
},
'contentView' : {
templateUrl: 'modules/core/templates/content.html',
controller: 'ContentViewController'
},
'sidebarView' : {
templateUrl: 'modules/core/templates/sidebar.html',
controller: 'SidebarViewController'
},
}
})