Angular UI-router and using dynamic templates - angularjs

I'm trying to load a template file using a rootscope value as for it's name.
I have a init controller which sets the $rootScope.template to "whatever.html", then I have my route like this:
$stateProvider.state('/', {
url: '/',
access: 'public',
views: {
page: {
controller: 'HomeCtrl',
templateProvider: function($templateFactory, $rootScope) {
return $templateFactory.fromUrl('/templates/' + $rootScope.template);
}
}
}
});
But this doesn't work. It actually freezes the whole chrome so that I have to kill the process in order to stop it... I've also tried this with templateUrl but with no results.
So how could I use my dynamic template file with UI-router?

Similiar to your other question (in order I found them): Angular and UI-Router, how to set a dynamic templateUrl, I also created a working plunker to show how to. How it would work?
So, if this would be state call:
#/parent/child/1
#/parent/child/2
And these would be states:
$stateProvider
.state('parent', {
url: '/parent',
//abstract: true,
templateUrl: 'views.parentview.html',
controller: function($scope) {},
});
$stateProvider
.state('parent.child', {
url: '/child/:someSwitch',
views: {
// see more below
Then we can use this templateProvider definiton:
templateProvider: function($http, $stateParams, GetName) {
// async service to get template name from DB
return GetName
.get($stateParams.someSwitch)
// now we have a name
.then(function(obj){
return $http
// let's ask for a template
.get(obj.templateName)
.then(function(tpl){
// haleluja... return template
return tpl.data;
});
})
},
What we can see is chaining of async results:
// first return of promise
return asyncstuff
.then(function(x){
// second return of a promise once done first
return asyncstuff
.then(function(y){
// again
return asyncstuff
.then(function(z){
return ... it
}
}
}
And that's what the magical templateProvider can do for us... wait until all promises are resolved and continue execution with known template name and even its content. Check the example here. More about template provider: Angular UI Router: decide child state template on the basis of parent resolved object

Related

Http calls are happening twice in angular.js

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.

Is it possible to load a template via AJAX request for UI-Router in Angular?

I know this might get an answer here, however that goes more for lazy loading implementation itself.
So, this is a typical UI-Router config block:
app.config(function($stateProvider, $urlRouterProvider, $injector) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'view_home.html', // Actually SHOULD BE result of a XHR request ....
controller: 'HomeCtrl'
});
});
But what if I want to load such templateUrl when it's requested ($stageChangeStart) and it would be AJAX request based.
How should it be implemented? How are LARGE angular applications dealing with this?
We should (in this case) use TemplateProvider. Small cite from doc:
Templates
TemplateUrl
... templateUrl can also be a function that returns a url. It takes one preset parameter, stateParams, which is NOT injected.
TemplateProvider
Or you can use a template provider function which can be injected, has access to locals, and must return template HTML, like this:
$stateProvider.state('contacts', {
templateProvider: function ($timeout, $stateParams) {
return $timeout(function () {
return '<h1>' + $stateParams.contactId + '</h1>'
}, 100);
}
})
And there is even more.
We can use cooperation with really powerful
$templateRequest
In this Q & A (Angular UI-Router dynamic routing based on slug from API Ajax Call. Load view based on slug) we can see so simple templateProvider defintion
.state('hybrid', {
// /john-smith
url: '/:slug',
templateProvider: ['type', '$templateRequest',
function(type, templateRequest)
{
var tplName = "tpl.partial-" + type + ".html";
return templateRequest(tplName);
}
],
and the result is also chached...
There are some other links to similar Q & A, including working examples
Angular UI Router: decide child state template on the basis of parent resolved object
Angular and UI-Router, how to set a dynamic templateUrl

Preventing parent state from reloading on child state change

I am trying to implement a tabbed interface akin to this: http://odetocode.com/blogs/scott/archive/2014/04/14/deep-linking-a-tabbed-ui-with-angularjs.aspx
However, on my state change, the controller of the parent state seems to be reinitialized (or a new $scope is created?)
There are two major differences between the example plunkr and my project.
I use a parameter in my url
I resolve different data on the state change for each tab (removing this does nothing).
I am not using ui-bootstrap for the tabs but am triggering a $state.go on ng-click of the tab.
I experimented with the above plunkr and added a dropdown to the parent state; however the parent dropdown values seem to persist when the child states change. I am not too concerned with the child states and will probably end up using sticky states anyways.
I am using wondering if I am doing something fundamentally wrong before I try and add another package to my project.
here is a rough plunkr of what I am trying to do: http://plnkr.co/edit/TmRQN5K8OEc8vHG84G5z?p=preview
here is my config:
app.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.when('/main',
function ($state) {
$state.go('parent.tab1', { main_id: '00008' });
});
$stateProvider
//Handle States Here
.state('parent', {
abstract: true,
url: '/parent?main_id',
templateUrl: "main.html",
controller: 'Main_Controller',
resolve: {
//Calls to API
}
})
.state('parent.tab1', {
url: "/applications",
templateUrl: "tab1.html",
controller:'Tab1Ctrl',
resolve: {
//Get some different data from an API
},
})
.state('parent.tab2', {
url: "/phasing",
templateUrl: "tab2.html",
controller: 'Tab2Ctrl',
resolve: {
//More API Data
}
});
});
I've made your plunker working here
$urlRouterProvider
//.when('/main',
.when('',
function ($state) {
$state.go('parent.tab1', { main_id: '00008' })
});
Also there is a change in main.html, which does not use ng-controller any more. We just have to pass the proper Controller name
$stateProvider
//Handle States Here
.state('parent', {
abstract: true,
url: '/parent?main_id',
templateUrl: "main.html",
controller: 'MainController',
resolve: {
//Calls to API
}
})
...
// MainController
// these two names should fit
app.controller("MainController", function($rootScope, $scope, $state) {
So now, it is working, and let's discuss
I use a parameter in my url
I resolve different data on the state change for each tab (removing this does nothing).
I am not using ui-bootstrap for the tabs but am triggering a $state.go on ng-click of the tab.
Quick answers:
parameter in url exists, e.g. #/parent/tab1?main_id=8000
resolve is trigerred for each controller if controller is reinstantiated. That happens when we navigate to that state (among tabs)
no need to use $state.go, I used:
a snippet:
<a ui-sref="parent.tab1({main_id:'00008'})"> go to tab1 with main_id '00008'</a><br />
<a ui-sref="parent.tab2({main_id:'00008'})"> go to tab2 with main_id '00008'</a><br />
<a ui-sref="parent.tab3({main_id:'00008'})"> go to tab3 with main_id '00008'</a><br />
Check it here

AngularJS - load data before loading any controller

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.

AngularJS UI Router - change url without reloading state

Currently our project is using default $routeProvider, and I am using this "hack", to change url without reloading page:
services.service('$locationEx', ['$location', '$route', '$rootScope', function($location, $route, $rootScope) {
$location.skipReload = function () {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
return $location;
};
return $location;
}]);
and in controller
$locationEx.skipReload().path("/category/" + $scope.model.id).replace();
I am thinking of replacing routeProvider with ui-router for nesting routes, but cant find this in ui-router.
Is it possible - do the same with angular-ui-router?
Why do I need this?
Let me explain with an example :
Route for creating new category is /category/new
after clicking on SAVE I show success-alert and I want to change route /category/new to /caterogy/23 (23 - is id of new item stored in db)
Simply you can use $state.transitionTo instead of $state.go . $state.go calls $state.transitionTo internally but automatically sets options to { location: true, inherit: true, relative: $state.$current, notify: true } . You can call $state.transitionTo and set notify: false . For example:
$state.go('.detail', {id: newId})
can be replaced by
$state.transitionTo('.detail', {id: newId}, {
location: true,
inherit: true,
relative: $state.$current,
notify: false
})
Edit: As suggested by fracz it can simply be:
$state.go('.detail', {id: newId}, {notify: false})
Ok, solved :)
Angular UI Router has this new method, $urlRouterProvider.deferIntercept()
https://github.com/angular-ui/ui-router/issues/64
basically it comes down to this:
angular.module('myApp', [ui.router])
.config(['$urlRouterProvider', function ($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}])
// then define the interception
.run(['$rootScope', '$urlRouter', '$location', '$state', function ($rootScope, $urlRouter, $location, $state) {
$rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) {
// Prevent $urlRouter's default handler from firing
e.preventDefault();
/**
* provide conditions on when to
* sync change in $location.path() with state reload.
* I use $location and $state as examples, but
* You can do any logic
* before syncing OR stop syncing all together.
*/
if ($state.current.name !== 'main.exampleState' || newUrl === 'http://some.url' || oldUrl !=='https://another.url') {
// your stuff
$urlRouter.sync();
} else {
// don't sync
}
});
// Configures $urlRouter's listener *after* your custom listener
$urlRouter.listen();
}]);
I think this method is currently only included in the master version of angular ui router, the one with optional parameters (which are nice too, btw). It needs to be cloned and built from source with
grunt build
The docs are accessible from the source as well, through
grunt ngdocs
(they get built into the /site directory) // more info in README.MD
There seems to be another way to do this, by dynamic parameters (which I haven't used).
Many credits to nateabele.
As a sidenote, here are optional parameters in Angular UI Router's $stateProvider, which I used in combination with the above:
angular.module('myApp').config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('main.doorsList', {
url: 'doors',
controller: DoorsListCtrl,
resolve: DoorsListCtrl.resolve,
templateUrl: '/modules/doors/doors-list.html'
})
.state('main.doorsSingle', {
url: 'doors/:doorsSingle/:doorsDetail',
params: {
// as of today, it was unclear how to define a required parameter (more below)
doorsSingle: {value: null},
doorsDetail: {value: null}
},
controller: DoorsSingleCtrl,
resolve: DoorsSingleCtrl.resolve,
templateUrl: '/modules/doors/doors-single.html'
});
}]);
what that does is it allows to resolve a state, even if one of the params is missing.
SEO is one purpose, readability another.
In the example above, I wanted doorsSingle to be a required parameter. It is not clear how to define those. It works ok with multiple optional parameters though, so not really a problem. The discussion is here https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090
After spending a lot of time with this issue, Here is what I got working
$state.go('stateName',params,{
// prevent the events onStart and onSuccess from firing
notify:false,
// prevent reload of the current state
reload:false,
// replace the last record when changing the params so you don't hit the back button and get old params
location:'replace',
// inherit the current params on the url
inherit:true
});
Calling
$state.go($state.current, {myParam: newValue}, {notify: false});
will still reload the controller, meaning you will lose state data.
To avoid it, simply declare the parameter as dynamic:
$stateProvider.state({
name: 'myState',
url: '/my_state?myParam',
params: {
myParam: {
dynamic: true, // <----------
}
},
...
});
Then you don't even need the notify, just calling
$state.go($state.current, {myParam: newValue})
suffices. Neato!
From the documentation:
When dynamic is true, changes to the parameter value will
not cause the state to be entered/exited. The resolves will not be
re-fetched, nor will views be reloaded.
This can be useful to build
UI where the component updates itself when the param values change.
This setup solved following issues for me:
The training controller is not called twice when updating the url from .../ to .../123
The training controller is not getting invoked again when navigating to another state
State configuration
state('training', {
abstract: true,
url: '/training',
templateUrl: 'partials/training.html',
controller: 'TrainingController'
}).
state('training.edit', {
url: '/:trainingId'
}).
state('training.new', {
url: '/{trainingId}',
// Optional Parameter
params: {
trainingId: null
}
})
Invoking the states (from any other controller)
$scope.editTraining = function (training) {
$state.go('training.edit', { trainingId: training.id });
};
$scope.newTraining = function () {
$state.go('training.new', { });
};
Training Controller
var newTraining;
if (!!!$state.params.trainingId) {
// new
newTraining = // create new training ...
// Update the URL without reloading the controller
$state.go('training.edit',
{
trainingId : newTraining.id
},
{
location: 'replace', // update url and replace
inherit: false,
notify: false
});
} else {
// edit
// load existing training ...
}
If you need only change url but prevent change state:
Change location with (add .replace if you want to replace in history):
this.$location.path([Your path]).replace();
Prevent redirect to your state:
$transitions.onBefore({}, function($transition$) {
if ($transition$.$to().name === '[state name]') {
return false;
}
});
i did this but long ago in version: v0.2.10 of UI-router like something like this::
$stateProvider
.state(
'home', {
url: '/home',
views: {
'': {
templateUrl: Url.resolveTemplateUrl('shared/partial/main.html'),
controller: 'mainCtrl'
},
}
})
.state('home.login', {
url: '/login',
templateUrl: Url.resolveTemplateUrl('authentication/partial/login.html'),
controller: 'authenticationCtrl'
})
.state('home.logout', {
url: '/logout/:state',
controller: 'authenticationCtrl'
})
.state('home.reservationChart', {
url: '/reservations/?vw',
views: {
'': {
templateUrl: Url.resolveTemplateUrl('reservationChart/partial/reservationChartContainer.html'),
controller: 'reservationChartCtrl',
reloadOnSearch: false
},
'viewVoucher#home.reservationChart': {
templateUrl: Url.resolveTemplateUrl('voucher/partial/viewVoucherContainer.html'),
controller: 'viewVoucherCtrl',
reloadOnSearch: false
},
'addEditVoucher#home.reservationChart': {
templateUrl: Url.resolveTemplateUrl('voucher/partial/voucherContainer.html'),
controller: 'voucherCtrl',
reloadOnSearch: false
}
},
reloadOnSearch: false
})
Try something like this
$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})
In Angular 2, the accepted answer from RezKesh translates to the following:
this.uiRouter.stateService.go(
"home.myRouteState",
{
"param1": this.myParam1,
"param2": this.myParam2
},
{ notify: false }
);
Assuming you have injected UIRouter into your component's constructor as follows:
constructor(
private uiRouter: UIRouter
) { }
I don't think you need ui-router at all for this. The documentation available for the $location service says in the first paragraph, "...changes to $location are reflected into the browser address bar." It continues on later to say, "What does it not do? It does not cause a full page reload when the browser URL is changed."
So, with that in mind, why not simply change the $location.path (as the method is both a getter and setter) with something like the following:
var newPath = IdFromService;
$location.path(newPath);
The documentation notes that the path should always begin with a forward slash, but this will add it if it's missing.

Resources