Dynamically change function of ng-click based on route - angularjs

I have an app with many views. On most views I have a toggle-able drawer on the left hand side for navigation. However, on a few views I want the menu to be a back button instead.
I am trying to use ng-click and databinding.
md-button ng-click="{{$scope.current.navBarFunction}}"
to dynamically inject the name, from an attribute navBarFunction in my routes, of the function for ng-click However this doesn't work and I'm unsure how to continue.
.when('/articles/:articleId', {
title: 'articles.title',
icon: 'arrow_back',
navBarFunction: 'backButton()',
templateUrl: 'views/articles/:articleid.html',
controller: 'ArticlesArticleidCtrl',
resolve: {
loggedin: checkLoggedin
}
})
Furthermore, is there anyway to make an if statement in app.js using the current route? That would simplify things.
EDIT 1:
here's more code in our controller:
function navBack(pageID) {
$location.path( '/' + pageID );
}
function buildToggler(navID) {
var debounceFn = $mdUtil.debounce(function(){
$mdSidenav(navID)
.toggle()
.then(function () {
$log.debug('toggle ' + navID + ' is done');
});
},300);
return debounceFn;
}
$scope.toggleLeft = buildToggler('left');
$scope.navBackUsers = navBack('users');
$scope.navBackArticles = navBack('articles');
$scope.navBackClassrooms = navBack('classrooms');

Without seeing more of your code it should likely be:
ng-click="current.navBarFunction()"
Not sure why you have () in string value in router or how you are setting this up in directive or controller. Seeing more code would help

Related

ui-router 1.x.x change $transition$.params() during resolve

Trying to migrate an angularjs application to use the new version of angular-ui-router 1.0.14 and stumbled upon a problem when trying to change $stateParams in the resolve of a state.
For example, previously (when using angular-ui-router 0.3.2) modifying $stateParams worked like this:
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
resolve: {
availableDates: ['CalendarService', '$stateParams', function(CalendarService, $stateParams) {
return CalendarService.getAvailableDates().then(function(response){
$stateParams.firstAvailableDate = response[0];
return response;
});
}]
}
})
The problem is firstAvailableDate is populated after a resolve and I do not know how to update $transition$.params() during a resolve when usign the new version of angular-ui-router 1.0.14.
I have tried, and managed to update the url parameter with
firing a $state.go('myState', {firstAvailableDate : response[0]}) but this reloads the state, so the screen flickers
modified $transition$.treeChanges().to[$transition$.treeChanges().length-1].paramValues.firstAvailableDate = response[0]; to actually override the parameters. I have done this after looking through the implementation on params() for $transition$.
Although both those options work, they seem to be hacks rather than by the book implementations.
What is the correct approach to use when trying to modify parameters inside a resolve?
Approach with dynamic parameter:
Take a look at this document: params.paramdeclaration#dynamic. Maybe thats what you are looking for: ...a transition still occurs....
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.
Normally, if a parameter value changes, the state which declared that the parameter will be reloaded (entered/exited). When a parameter is dynamic, a transition still occurs, but it does not cause the state to exit/enter.
This can be useful to build UI where the component updates itself when the param values change. A common scenario where this is useful is searching/paging/sorting.
Note that you are not be able to put such logic into your resolve inside your $stateProvider.state. I would do this by using dynamic parameters to prevent the state reload. Unfortunally, the dynamic rules doesn't work when you try to update your state (e.g. by using $stage.go()) inside the resolve part. So I moved that logic into the controller to make it work nice - DEMO PLNKR.
Since userId is a dynamic param the view does not get entered/exited again when it was changed.
Define your dynamic param:
$stateProvider.state('userlist.detail', {
url: '/:userId',
controller: 'userDetail',
controllerAs: '$ctrl',
params: {
userId: {
value: '',
dynamic: true
}
},
template: `
<h3>User {{ $ctrl.user.id }}</h3>
<h2>{{ $ctrl.user.name }} {{ !$ctrl.user.active ? "(Deactivated)" : "" }}</h2>
<table>
<tr><td>Address</td><td>{{ $ctrl.user.address }}</td></tr>
<tr><td>Phone</td><td>{{ $ctrl.user.phone }}</td></tr>
<tr><td>Email</td><td>{{ $ctrl.user.email }}</td></tr>
<tr><td>Company</td><td>{{ $ctrl.user.company }}</td></tr>
<tr><td>Age</td><td>{{ $ctrl.user.age }}</td></tr>
</table>
`
});
Your controller:
app.controller('userDetail', function ($transition$, $state, UserService, users) {
let $ctrl = this;
this.uiOnParamsChanged = (newParams) => {
console.log(newParams);
if (newParams.userId !== '') {
$ctrl.user = users.find(user => user.id == newParams.userId);
}
};
this.$onInit = function () {
console.log($transition$.params());
if ($transition$.params().userId === '') {
UserService.list().then(function (result) {
$state.go('userlist.detail', {userId: result[0].id});
});
}
}
});
Handle new params by using $transition.on* hooks on route change start:
An other approach would be to setup the right state param before you change into your state. But you already said, this is something you don't want. If I would face the same problem: I would try to setup the right state param before changing the view.
app.run(function (
$transitions,
$state,
CalendarService
) {
$transitions.onStart({}, function(transition) {
if (transition.to().name === 'mySate' && transition.params().firstAvailableDate === '') {
// please check this, I don't know if a "abort" is necessary
transition.abort();
return CalendarService.getAvailableDates().then(function(response){
// Since firstAvailableDate is dynamic
// it should be handled as descript in the documents.
return $state.target('mySate', {firstAvailableDate : response[0]});
});
}
});
});
Handle new params by using $transition.on* hooks on route change start via redirectTo
Note: redirectTo is processed as an onStart hook, before LAZY resolves.
This does the same thing as provided above near the headline "Handle new params by using $transition.on* hooks on route change start" since redirectTo is also a onStart hook with automated handling.
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
redirectTo: (trans) => {
if (trans.params().firstAvailableDate === '') {
var CalendarService = trans.injector().get('CalendarService');
return CalendarService.getAvailableDates().then(function(response){
return { state: 'myState', params: { firstAvailableDate: response[0] }};
});
}
}
});

Angular UI Router Reload Controller on Back Button Press

I have a route that can have numerous optional query parameters:
$stateProvider.state("directory.search", {
url: '/directory/search?name&email',
templateUrl: 'view.html',
controller: 'controller'
When the user fills the form to search the directory a function in the $scope changes the state causing the controller to reload:
$scope.searchDirectory = function () {
$state.go('directory.search', {
name: $scope.Model.Query.name,
email: $scope.Model.Query.email
}, { reload: true });
};
In the controller I have a conditional: if($state.params){return data} dictating whether or not my service will be queried.
This works great except if the user clicks the brower's forward and/or back buttons. In both these cases the state (route) changes the query parameters correctly but does not reload the controller.
From what I've read the controller will be reloaded only if the actual route changes. Is there anyway to make this example work only using query parameters or must I use a changing route?
You should listen to the event for succesful page changes, $locationChangeSuccess. Checkout the docs for it https://docs.angularjs.org/api/ng/service/$location.
There is also a similar question answered on so here How to detect browser back button click event using angular?.
When that event fires you could put whatever logic you run on pageload that you need to run when the controller initializes.
Something like:
$rootScope.$on('$locationChangeSuccess', function() {
$scope.searchDirectory()
});
Or better setup like:
var searchDirectory = function () {
$state.go('directory.search', {
name: $scope.Model.Query.name,
email: $scope.Model.Query.email
}, { reload: true });
$scope.searchDirectory = searchDirectory;
$rootScope.$on('$locationChangeSuccess', function() {
searchDirectory();
});
Using the above, I was able to come up with a solution to my issue:
controller (code snippet):
...var searchDirectory = function (searchParams) {
if (searchParams) {
$scope.Model.Query.name = searchParams.name;
$scope.Model.Query.email = searchParams.email;
}
$state.go('directory.search', {
name: $scope.Model.Query.name,
email: $scope.Model.Query.email,
}, { reload: true });
};...
$rootScope.$on('$locationChangeSuccess', function () {
//used $location.absUrl() to keep track of query string
//could have used $location.path() if just interested in the portion of the route before query string params
$rootScope.actualLocation = $location.absUrl();
});
$rootScope.$watch(function () { return $location.absUrl(); }, function (newLocation, oldLocation) {
//event fires too often?
//before complex conditional was used the state was being changed too many times causing a saturation of my service
if ($rootScope.actualLocation && $rootScope.actualLocation !== oldLocation && oldLocation !== newLocation) {
searchDirectory($location.search());
}
});
$scope.searchDirectory = searchDirectory;
if ($state.params && Object.keys($state.params).length !== 0)
{..call to service getting data...}
This solution feels more like a traditional framework such as .net web forms where the dev has to perform certain actions based on the state of the page. I think it's worth the compromise of having readable query params in the URL.

Accesing javascript variable in angularjs

I am trying to accessing javascript variable and then change it in controller and then use it in route.. but it is showing the same old one.
var userEditid = 0;
app.controller("cn_newuser", function ($scope,$window) {
editUser = function (this_) {
$window.userEditid = this_.firstElementChild.value;
alert(userEditid);
//window.location.path("#/edit_user");
//$window.location.href = "#/edit_user";
}
route
.when("/edit_user", {
templateUrl: "/master/edituser/" + userEditid //userEditid should be 3 or some thing else but is showing 0
})
in abobe route userEditid should be 3 or some thing else but is showing 0
This is because the .when() is evaluated on app instantiation, when this global var (which is really not a great idea anyways) is set to 0, rather than when your controller is run. If you are trying to say, "load the template, but the template name should vary based on a particular variable," then the way you are doing it isn't going to work.
I would do 2 things here:
Save your variable in a service, so you don't play with global vars
Have a master template, which uses an ng-include, which has the template determined by that var
Variable in a service:
app.controller("cn_newuser", function ($scope,UserIdService) {
$scope.editUser = function (this_) {
UserIdService.userEditid = this_.firstElementChild.value;
$location.path('/edit_user');
}
});
also note that I changed it to use $location.path()
Master template:
.when("/edit_user", {
templateUrl: "/master/edituser.html", controller: 'EditUser'
});
EditUser controller:
app.controller("EditUser", function ($scope,UserIdService) {
$scope.userTemplate = '/master/edituser/'+UserIdService.userEditId;
});
And then edituser.html would be:
<div ng-include="userTemplate"></div>
Of course, I would ask why you would want a separate template per user, rather than a single template that is dynamically modified by Angular, but that is not what you asked.
EDITED:
Service would be something like
app.factory('UserIdService',function() {
return {
userEditId: null
}
});
As simple as that.

How to reuse controllers in AngularJs when success locations are different and $location.path('..') is not supported?

Right now the $location service is getting in the way. Suppose one wants to use the same controller for multiple routes, however the expectation is that upon a successful 'save' the destination routes would be different.
.when('/sponsors/:sponsorId/games/add', {templateUrl: 'partials/games/create',controller: 'GameCreateCtrl', access: 'sponsor'})
// an admin can see all the games at
.when('/admin/games/add', {templateUrl: 'partials/games/create',controller: 'GameCreateCtrl', access: 'admin'})
A game is is displayed on success of either action. The route is just the parent path.
e.g. /admin/games or /sponsors/:sponsorId/games.
The $location service does not seem to support the relative path $location.path('..'). Should it?
What is the best way to reuse the GameCreateCtrl in this situation?
$scope.save = function () {
GameService.save($scope.game).$promise.then(function(res){
console.log(res);
growl.addSuccessMessage("Successfully saved game: " + $scope.game.name);
console.log("saving game by id:" + $scope.game._id);
var path = $location.path();
$location.path(path.replace('/add', '')); // this seems like a hack
});
}
You can do it with resolve:
.when('/sponsors/:sponsorId/games/add', {
templateUrl: 'partials/games/create',
controller: 'GameCreateCtrl',
resolve: {
returnUrl: function($routeParams){
return '/sponsors/' + $routeParams.sponsorId + '/games';
}
}
})
.when('/admin/games/add', {
templateUrl: 'partials/games/create',
controller: 'GameCreateCtrl',
resolve: {
returnUrl: function(){
return '/admin/games';
}
}
})
In controller:
app.controller('myCtrl', function($scope, returnUrl){
$scope.save = function () {
GameService.save($scope.game).$promise.then(function(res){
// ...
$location.path(returnUrl); // this seems like a hack
});
};
});
You are passing different returnUrl parameter to controller depending on route.
I would like to thank the poster karaxuna with their solution. Its the answer I am accepting. However, it is often helpful to have other options at ones disposal.
Another way to solve this would be to create a global function.
function getParentPath($location) {
if ($location.path() != '/') /* can't move up from root */ {
var pathArray = $location.path().split('/');
var parentPath = "";
for (var i = 1; i < pathArray.length - 1; i++) {
parentPath += "/";
parentPath += pathArray[i];
}
return parentPath;
}
}
This works very where when edits/adds follow a rest style with regards to route locations. In these cases the parentPath would always go back to the plural listing of all records.
and perhaps add a method to root scope
$rootScope.goParentPath = function ($location) {
$location.path(getParentPath($location));
}
function inside controllers could call the getParentPath function. e.g.
$scope.cancel = function() {
$scope.goParentPath($location)
}
I am actually leaning considering an approach that combines the first answer with a getParentPath is some situations.
For a little bit of brevity, the routes would make use of the resolve callout, but use the parentPath function in many cases. ex:
.when('/admin/games/:id', {templateUrl: 'partials/games/edit', controller: 'EditGameCtrl', access: 'admin',resolve:{returnUrl: getParentPath}})
.when('/admin/games/add', {templateUrl: 'partials/games/create', controller: 'EditGameCtrl', access: 'admin', resolve:{returnUrl: getParentPath}})

AngularJS - change $location silently - remove query string

Is there any way to silently change the route in the url bar using angular?
The user clicks a link for the email that goes to:
/verificationExecuted?verificationCode=xxxxxx
When the page loads I want to read the verificationCode and then clear it:
if($location.path() == '/verificationExecuted'){
this.registrationCode = this.$location.search()["verificationCode"];
this.$location.search("verificationCode", null); //Uncomment but make this silent!
if(registrationCode != null) {
....
}
else $location.path("/404");
}
What happens when I clear it is the remaining part of the route ("/verificationExecuted") remains buts the route re-triggers so it comes around again with no verificationCode and goes straight to 404.
I want to remove the code without doing anything else.
You can always set the reloadOnSearch option on your route to be false.
It will prevent the route from reloading if only the query string changes:
$routeProvider.when("/path/to/my/route",{
controller: 'MyController',
templateUrl: '/path/to/template.html',
//Secret Sauce
reloadOnSearch: false
});
try this
$location.url($location.path())
See documentation for more details about $location
I had a similar requirement for one of my projects.
What I did in such a case was make use of a service.
app.factory('queryData', function () {
var data;
return {
get: function () {
return data;
},
set: function (newData) {
data = newData
}
};
});
This service was then used in my controller as:
app.controller('TestCtrl', ['$scope', '$location', 'queryData',
function ($scope, $location, queryData) {
var queryParam = $location.search()['myParam'];
if (queryParam) {
//Store it
queryData.set(queryParam);
//Reload same page without query argument
$location.path('/same/path/without/argument');
} else {
//Use the service
queryParam = queryData.get();
if (queryParam) {
//Reset it so that the next cycle works correctly
queryData.set();
}
else {
//404 - nobody seems to have the query
$location.path('/404');
}
}
}
]);
I solved this by adding a method that changes the path and canceling the event.
public updateSearch(){
var un = this.$rootScope.$on('$routeChangeStart', (e)=> {
e.preventDefault();
un();
});
this.$location.search('new',search.searchFilter);
if (!keep_previous_path_in_history) this.$location.replace();
}

Resources