$location.path change hash to %23 & .hash is not working fine - angularjs

I want to redirect to the another page & focus should be on the some DIV with id let's say 'some-div-id'.
I tried following
$location.path('/' + $scope.config_path.school + '/' +
$routeParams.someUrl + '/#some-div-id')
This is working fine but it first change # to %23
Like
/%23some-div-id #If '#' is not already present in the URL
/%23some-div-id#some-div-id #If '#' is laready present in the URL
/#some-div-id
I also tried following
$location.path('/' + $scope.config_path.school + '/' +
$routeParams.someUrl + '/').hash('some-div-id')
it is creating a proper URL but not scroll down to the DIV with id some-div-id
EDITED
app.run(function($rootScope, $location, $anchorScroll, $routeParams) {
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
setTimeout(function() {
if ($location.hash()) {
$anchorScroll($location.hash());
}
});
});
})
app.controller('MainCntrl', function($scope, $location, $anchorScroll, $routeParams) {
});
Also i tried $location.path & $location.url

Try using $location.url instead of $location.path .
As per the documentation , $location.path only changes the path whereas $location.url changes the path, search and hash.
So the code would be like
$scope.goto = function() {
$location.url('pageone#one');
};
Where pageone is URL of the state and #one is the ID
Also to make it work when directly visiting the URL with the ID in it , use $anchorScroll . On the $stateChangeSuccess event , checking weather $location.hash is present or not.If present , then call $anchorScroll . The code would look like
.run(function($rootScope, $location, $anchorScroll,$timeout) {
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams) {
$timeout(function() {
if ($location.hash()) {
$anchorScroll();
}
});
});
})
Example - http://plnkr.co/edit/4kJjiMJImNzwLjRriiVR?p=preview ( For seeing change in URL check http://run.plnkr.co/plunks/4kJjiMJImNzwLjRriiVR/#/pageone )

This behavior is expected. Angular will not allow a second # in your url by default, thus every # beyond the first one will be escaped, resulting in %23 which is the escape character for #: http://www.w3schools.com/tags/ref_urlencode.asp.
You could try enabling html5mode by adding the following to your .config block:
$locationProvider.html5Mode({
enabled: true
});
If that doesn't solve your problem, or if you need to support support IE8, your best bet would probably be to use $anchorScroll().
Here's the documentation.
In your app, you could pass the id of the div you want to scroll to as a parameter in your url (using $routeParams or ui-router's $stateParams), and then call a function that will scroll to that div immediately on page load, such as:
$scope.scrollTo = function(id) {
$location.hash(id);
$anchorScroll();
}
Please also read the following for a more detailed explanation on how to use $anchorScroll() for your specific problem:
How to handle anchor hash linking in AngularJS
Namely, this particular section:
Set up your angular routing as usual, then just add the following code.
app.run(function($rootScope, $location, $anchorScroll, $routeParams) {
//when the route is changed scroll to the proper element.
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
$location.hash($routeParams.scrollTo);
$anchorScroll();
});
});
and your link would look like this:
Test/Foo

$location.path(page);
just add the dependency in ur controller by add $location,
This method redirects you on this route, without any error.

In ng-book, there is something about %13 signs replacing normal letter.
Unfortunately, it's about different topic - it is about an $http or xhr api call through REST, where there are two modes ( same would be here ) - one normal mode and second JQuery like. It turned out that jquery adds these %13 signs to things like arr[]=1.
Why i am saying this?
Because after solution comes libraries and functions which support marginal conditions.
The situation in Your question is quite simple. There is $location service, which is solution and urls are created simillary in two modes, which are hashbang and not hashbang.
In problem with jquery parameters, developers provided library for exchanging jquery parameters to normal. There are many converters in real life, like pdf to doc etc.
Please look at short chapter in this book.
It is stated that in html5 mode, you cannot have two hashes in url, but in hashbang mode you can.
Browser cannot know which hash is to go to in hashbang mode. The support for this is anchorScroll service.
I haven't checked if this works in this case but try to configure your app with AnchorScrollProvider:
.config(function($anchorScrollProvider){
$anchorScrollProvider.disableAutoScrolling();
});
Then as i written, you can inject $anchorService service to any place in your app ( especially in controller binded to view containing some-div-id div ) And call anchorScroll() function of this service at any chosen time.
I cannot test it right now so this is theoretical answer.

I had the same problem before and this post helped me out.
Let's hope it is going to help you aswell, if it doesn't let me know so I can help you find another solution.
Good luck.

Related

Angular change URL on page load

I'm creating a schedule for a convention and would like to have the URL change during the convention time. If the user loads the page on Saturday of the con, the URL should change to [URL]?day=saturday.(The URL will also change when the user clicks to view different day)
I'm running into an issue on page load only, where the history.replaceState is causing
angular.js:13920 Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
So I came across a forum that says to use something like
$location.path('/').search('day='+day).replace();
But it comes out as
/#/?day=saturday
I don't want the /# in the URL. So I found forums explaining to use $locationProvider.
I even added in the
app.config(function($locationProvider) {
$locationProvider.html5Mode(true);
})
And it still says $locationProvider is not defined. I don't know what I'm doing wrong or if this is even the right direction to be going in. I just want to change the ?day=day_here when the page loads and when the user clicks to change the day.
Capture the route change on click when routing and replace the url. For the error use and your syntax seems to be right
.config(function($locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
})
But if you want to persist the url change you will have to do the changes to url in the routeChange
.run(function($rootScope, $location) {
$rootScope.$on('$routeChangeStart', function(next, current) {
//... you could trigger something here like replacing the actual url ...
//$location.path('/').search('day='+day).replace();
});
})
Alternatively you can also look at https://github.com/angular-ui/ui-router as an alternative and do the changes to $location.url().replace() in onEnter or resolve options of state definition https://github.com/angular-ui/ui-router/wiki
If you want to use ngRouter and still getting the error a plunkr code for demo will surely help debug better.

How to append url to the end of a button link in angularjs?

Assuming I am currently on the page:
www.mysite.com/root/#/?page=orderpage
On the page, I have a link:
Click Here for Mobile
I want to make it such that when the user clicks on the "Click Here For Mobile" link, I can take the user to:
www.mysite.com/root/mobile/#/?page=orderpage
Notice that the URL is similar to what it was before (preserving whatever parameters were there), except there is a "mobile" in between the root and the #.
How do I make it such that the "Click Here For Mobile" link will have the redirect to the appropriate URL as described above?
Is it good practice to do this? Examples appreciated.
As Ajay said, client-side routing will not be possible in this case.
So what you're left with is either doing (like Ajay said):
<a target="_self" href="/root/mobile/#/?page=orderpage">
Or, you could define a function (in a service, in the specified controller, or someplace else where you have access to it) that takes a string and changes the $window.location.href (this will result in a page reload).
Something like this:
app.controller("SomeCtrl", function ($scope, $location, $window) {
$scope.changeUrl = function (url) {
var newUrl = $location.$$absUrl.replace("/#/", url);
$window.location.href = newUrl;
};
});
And then in your html you would use:
Go mobile
Without Javascript enabled (which I'm assuming you're not gonna be supporting with an Angular app) this would not take you anywhere. In that case, Ajay's solution is definately the prefered one.
The benefit of turning it into a function is that all you have to pass is what you're changing before the "/#/".
Good luck :)

Access scope or service in ui router onEnter to check access (auth)

Ok, so I have a state for the url "/securepage" which I need to check whenever a user tries to access it. So I read there is an onEnter function I can use. But I cannot seem to get hold on the scope nor a service from there. What am I doing wrong?
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function(){
// if (!$scope.main.isAuthenticated) $state.go("/login");
// if (!myLoginService.currentUser()) $state.go("/login");
Current options as I see it are to use resolve and/or check the authentication in the controller. But wouldnt an auth check be better placed in onEnter?
I ran into a similar problem today. Spent a whole day and finally came up with a workable solution other than the ones already suggested here.
My main goal is to find easy and effective way to selectively secure certain specific webpages. The security check needs to be performed before the HTML or any of the relevant controllers are loaded or called. If the check fails, the page may be forwarded to elsewhere without any side effects from the other controllers.
I tried the other suggested approaches. Each one has their own set of problems:
Using OnEnter:
There is no way to block ui-router from continuing with the state transition while making an async call to do the security check.
Using $rootScope.$on('$stateChangeStart'):
The management of the states that rquire security check will become separated from the $stateProvider.state() definitions. Ideally, I would rather see everything about the definition of a state defined all in one place. Though this is not a showstopper, it is not ideal.
A much bigger problem is that the $stateChangeStart event is not being called for the initial loading of a page. This one is a showstopper.
My solution is to use a resolve function to define a promise that will cause the view controllers to wait for the deferred to complete before they are called. This work perfectly for blocking the controller from starting in an asynchronous fashion.
Here's a rough outline of the code I used:
.config(['$stateProvider', function ($stateProvider) {
// Handler for Restricting Access to a page using the state.resolve call
var accessRestrictionHandler = function($q, $rootScope, $state) {
var deferred = $q.defer();
// make sure user is logged in
asyncCheckForLogin(function(status) {
if (status != "Logged In") {
// You may save target page URL in cookie for use after login successful later
// To get the relative target URL, it is equal to ("#" + this.url).
// The "this" here is the current scope for the parent state structure of the resolve call.
$state.go("loginPage");
}
else // if logged in, continue to load the controllers. Controllers should not start till resolve() is called.
deferred.resolve();
}.bind(this));
return deferred.promise;
};
$stateProvider
.state('userProfile', {
url: '/userProfile',
views: {
'main': {
templateUrl: 'userProfile.html',
controller: 'userProfileCtrl'
}
},
// SIMPLY add the line below to all states that you want to secure
resolve: { loginRequired : accessRestrictionHandler }
})
.state(.... some other state)
.state(.... some other state);
}]);
I hope this will help some of you out there.
Another approach would be to have a service/controller listen to the "$stateChangeStart" event. There, you could check if the called state needs authentication and reroute the request. Here's a snippet:
$rootScope.$on('$stateChangeStart', function (event, nextState, currentState) {
if (!isAuthenticated(nextState)) {
console.debug('Could not change route! Not authenticated!');
$rootScope.$broadcast('$stateChangeError');
event.preventDefault();
$state.go('login');
}
});
isAuthenticated could hold the call to your services, check nextState.data for authentication-related properties, etc.
Check out this issue and this example from their github page. It should give you some clues.
A late answer but well I'd rather write it anyway. I prefer not touching the $rootScope wherever possible. Here's the code that I'm currently working on which sums up another solution for your question:
state('dash', {
url:'/',
views:{
"topNav":{
templateUrl:"user/dash/partials/top-nav.html",
controller:'logoutCtrl',
},
"sideNav":{
templateUrl:"user/dash/partials/side-nav.html"
},
"body":{
templateUrl:"user/dash/partials/body.html",
controller:'testCtrl'
}
},
onEnter: function(Auth, $state){
if(!AuthSvc.isAuthenticated){
$state.go('login');
}
}
})
I'm using JWTs to store the tokens on the localstorage by using ngStorage which gives a $localStorage service which I inject in the Auth factory I injected in onEnter
probably you mean
.state('securepage', {
url: "/securepage",
template: securepage.html,
//this is how you actually inject things to onEnter property
onEnter: ['$state','myLoginService',function($state,myLoginService){
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");`}]
....
I am using angularjs 1.3 with ui.router 0.2.10
I'm sure a lot has changed since this question was asked but as I had to figure it out myself and my search led me here...
As far as checking authentication you could do it like this
.state('securepage', {
url: "/securepage",
template: securepage.html,
onEnter: function($scope,$state,myLoginService){ //THIS IS THE CHANGE
if (!$scope.main.isAuthenticated) $state.go("/login");
if (!myLoginService.currentUser()) $state.go("/login");
You can put pretty much provider/service/factory in the onEnter function to gain access to it, and this does work from inside the .config of the app.
Having said that, they (ui-router makers) suggests to use custom rule function handle it inside .$on('$stateChangeStart', function(e, to) https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-create-rules-to-prevent-access-to-a-state
I don't know why they do this instead of onEnter, maybe someone could expand on that.

AngularJS + Safari: Force page to scroll to top when pages switch

I am having an odd, safari-only scrolling behavior using AngularJS.
Whenever the user flips between pages, the pages are being changed as if they are AJAX. I understand they are in AngualrJS, but the resulting behavior is that the browser does not scroll to top when the user switches pages.
I've tried to force the browser to scroll to top whenever a new controller is being used, but it does not seem to do anything.
I'm running the following JS at the top of every controller:
document.body.scrollTop = document.documentElement.scrollTop = 0;
This is also a Safari-only bug, every other browser will scroll to top when the page changes. Has anyone encountered a similar issue or think of a better way to resolve it?
$window.scrollTo(0,0) will scroll to the top of the page.
I just found a nice plugin (pure angularJS) that supports animations also:
https://github.com/durated/angular-scroll
you can use this:
.run(["$rootScope", "$window", '$location', function($rootScope, $window, $location) {
$rootScope.$on('$routeChangeStart', function(evt, absNewUrl, absOldUrl){
$window.scrollTo(0,0); //scroll to top of page after each route change
}}])
or for tab switches you can use the $window.scrollTo(0,0); in your controller
Have you tried using $anchorScroll()? it's documented here.
I got the same problem while using AngularJS in a Cordova App. In a normal Browser or on Android i have no trouble but on ios i get the same behavior as discribed by Neil.
The AngularJS documentation on $anchorScroll is not that great so i thought to post this link which helped me way more:
http://www.benlesh.com/2013/02/angular-js-scrolling-to-element-by-id.html
You can use $anchorScroll
$scope.gotoTop = function (){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('top');
// call $anchorScroll()
$anchorScroll();
};
Like #nonstopbutton said, adding autoscroll="true" to my ngView element worked for me too. I mention this here because it was a comment to an answer and it was not easy to see his reply.
More information here: https://stackoverflow.com/a/24549173/1578861
I'd a similar issue with Chrome. However, I don't know if any specific external library is causing this issue or otherwise.
However I wrote this piece of code at app level and it works.
$rootScope.$on('$viewContentLoaded', function(){
$window.scrollTo(0, 0);
});
Call $window.scrollTo(0,0); after locationChangeSuccess event:
$rootScope.$on("$locationChangeSuccess",
function(event, current, previous, rejection) {
$window.scrollTo(0,0);
});
In the controller you can actually drop the $ from window and simply putwindow.scrollTo(0,0); without having to inject $window into the controller. It worked great for me.

AngularJS Paging with $location.path but no ngView reload

My single page application loads a home page and I want to display a series of ideas. Each of the ideas is displayed in an animated flash container, with animations displayed to cycle between the ideas.
Ideas are loaded using $http:
$scope.flash = new FlashInterface scope:$scope,location:$location
$http.get("/competition.json")
.success (data) ->
$scope.flash._init data
However, to benefit from history navigation and UX I wish to update the address bar to display the correct url for each idea using $location:
$location.path "/i/#{idea.code}"
$scope.$apply()
I am calling $apply here because this event comes from outwith the AngularJS context ie Flash. I would like for the current controller/view to remain and for the view to not reload. This is very bad because reloading the view results in the whole flash object being thrown away and the preloader cycle beginning again.
I've tried listening for $routeChangeStart to do a preventDefault:
$scope.$on "$routeChangeStart", (ev,next,current) ->
ev.preventDefault()
$scope.$on "$routeChangeSuccess", (ev,current) ->
ev.preventDefault()
but to no avail. The whole thing would be hunky dory if I could figure out a way of overriding the view reload when I change the $location.path.
I'm still very much feeling my way around AngularJS so I'd be glad of any pointers on how to structure the app to achieve my goal!
Instead of updating the path, just update query param with a page number.
set your route to ignore query param changes:
....
$routeProvider.when('/foo', {..., reloadOnSearch: false})
....
and in your app update $location with:
...
$location.search('page', pageNumber);
...
From this blog post:
by default all location changes go through the routing process, which
updates the angular view.
There’s a simple way to short-circuit this, however. Angular watches
for a location change (whether it’s accomplished through typing in the
location bar, clicking a link or setting the location through
$location.path()). When it senses this change, it broadcasts an
event, $locationChangeSuccess, and begins the routing process. What
we do is capture the event and reset the route to what it was
previously.
function MyCtrl($route, $scope) {
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
$route.current = lastRoute;
});
}
My solution was to use the $routeChangeStart because that gives you the "next" and "last" routes, you can compare them without the need of an extra variable like on $locationChangeSuccess.
The benefit is being able to access the "params" property on both "next" and "last" routes like next.params.yourproperty when you are using the "/property/value" URL style and of course use $location.url or $location.path to change the URL instead of $location.search() that depends on "?property=value" URL style.
In my case I used it not only for that but also to prevent the route to change is the controller did not change:
$scope.$on('$routeChangeStart',function(e,next,last){
if(next.$$route.controller === last.$$route.controller){
e.preventDefault();
$route.current = last.$$route;
//do whatever you want in here!
}
});
Personally I feel like AngularJS should provide a way to control it, right now they assume that whenever you change the browser's location you want to change the route.
You should be loading $location via Dependency Injection and using the following:
$scope.apply(function () {
$location.path("yourPath");
}
Keep in mind that you should not use hashtags(#) while using $location.path. This is for compability for HTML5 mode.
The $locationChangeSuccess event is a bit of a brute force approach, but I found that checking the path allows us to avoid page reloads when the route path template is unchanged, but reloads the page when switching to a different route template:
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function (event) {
if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) {
$route.current = lastRoute;
}
});
Adding that code to a particular controller makes the reloading more intelligent.
Edit: While this makes it a bit easier, I ultimately didn't like the complexity of the code I was writing to keep friendly looking URL's. In the end, I just switched to a search parameter and angular handles it much better.
I needed to do this but after fussing around trying to get the $locationChange~ events to get it to work I learned that you can actually do this on the route using resolve.
$routeProvider.when(
'/page',
{
templateUrl : 'partial.html',
controller : 'PageCtrl',
resolve : {
load : ['$q', function($q) {
var defer = $q.defer();
if (/*you only changed the idea thingo*/)
//dont reload the view
defer.reject('');
//otherwise, load the view
else
defer.resolve();
return defer.promise;
}]
}
}
);
With AngularJS V1.7.1, $route adds support for the reloadOnUrl configuration option.
If route /foo/:id has reloadOnUrl = false set, then moving from /foo/id1 to /foo/id2 only broadcasts a $routeUpdate event, and does not reload the view and re-instantiate the controller.

Resources