Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

LocationHashbangUrl() undefined error 1.2 rc3 #4776

Closed
adrianprofero opened this issue Nov 4, 2013 · 18 comments
Closed

LocationHashbangUrl() undefined error 1.2 rc3 #4776

adrianprofero opened this issue Nov 4, 2013 · 18 comments

Comments

@adrianprofero
Copy link

My site has two types of pages using Angular 1.2rc3. The first uses $locationProvider.html5Mode(true) for changing the url without reloading and the other type act like a normal html page with full page refreshes on clicking a link

Each section works fine but if i try click the back button from a non html5mode page into a page with html5Mode(true) then the error below is thrown because "withoutBaseUrl" is undefined. The beginsWith function can return undefined so .charAt(0) on the return value of that function is bad

Back button works fine on pages within either section so seems like a routing variable is being carried over into the non html5 page and either Angular is checking for a hash when there is no need to or the appBase reference is wrong

TypeError: Cannot call method 'charAt' of undefined
at Object.LocationHashbangUrl.$$parse (angular.js:7694:49)
at angular.js:8112:39
at Scope.$eval (angular.js:10700:44)
at Scope.$digest (angular.js:10548:53)
at angular.js:8115:61
at angular.js:3883:17
at Array.forEach (native)
at forEach (angular.js:213:21)
at fireUrlChange (angular.js:3882:13)
at x.event.dispatch (jquery-1.10.1.min.js:5:14113)

function LocationHashbangUrl(appBase, hashPrefix) {
var appBaseNoFile = stripFile(appBase);
parseAbsoluteUrl(appBase, this);

    /**
     * Parse given hashbang url into properties
     * @param {string} url Hashbang url
     * @private
     */
    this.$$parse = function(url) { 
        var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
        var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
@feens
Copy link

feens commented Nov 5, 2013

Not sure if helps or not...I was having this same issue and did a lot of digging. It turns out the dropdown-toggle directive in ui-bootstrap was causing the issue.

@adrianprofero
Copy link
Author

Im not using ui-bootstrap.

@adriansdev
Copy link

Still have this error with 1.23

Have done a hack to get around it with addition of if(withoutBaseUrl==undefined){ window.location = url; return} to catch when the url has no hash, withoutBaseUrl is undefined and the charAt would cause an error

function LocationHashbangUrl(appBase, hashPrefix) {
    var appBaseNoFile = stripFile(appBase);
    parseAbsoluteUrl(appBase, this, appBase);

    this.$$parse = function(url) {
        var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
        if(withoutBaseUrl==undefined){
            window.location = url
            return
        }

No idea if it's the ideal solution but seems to work for me.

@IgorMinar
Copy link
Contributor

is this still an issue with 1.2.7+?

if so, would it be possible to reproduce it via two plunkers or something like that? we need more information in order to do anything about this.

we have seen some back button issues caused by browser bugs and I wonder if this is yet another one of those.

@adriansdev
Copy link

It's still there in 1.2.7. "TypeError: Cannot call method 'charAt' of undefined"

I did try to set up a plunker but its difficult to simulate in the iframe.

The problem is still that 'charAt' is being called on the output of the beginsWith method, which can return undefined. That code needs to be changed so that a check is made for a undefined response.

I have had no problems since adding

if(withoutBaseUrl==undefined){
window.location = url
return
}

@YukonSaint
Copy link

Is this fixed in 1.3 by any chance? The hack works for us but obviously that's not ideal.

@IgorMinar Our code isn't live yet, but the situation that reproduces it for us is we change a route on our browse movies page by adding a section to the url. This triggers an overlay to load which shows the details for that movie. The way our code is written if you hit refresh it's like deep linking straight to the movie details, so the overlay becomes a full page showing just that movie. At that point if you try to click back to the movie browse page, we get the charAt error.

As Adrian noted, in the LocationHashbangUrl, $$parse method - withoutBaseUrl is undefined because the other vars look like this:
appBase = http://foo.com/movies/someMovieIdGoesHere
appBaseNoFile = http://foo.com/movies/
url = http://foo.com/movies

Notice the extra trailing / on appBaseNoFile. This is causing beginsWith to return undefined for both conditions in: var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);

I hope some of this helps, I know it's a rinky dink way to debug. If you want to have me try a patch I will be glad to and give you feedback.

@YukonSaint
Copy link

Actually I just realized another key point. We are only setting HTML5 mode to true on our overlays. We have a sort of a hybrid multi-page site with several mini-single page apps embedded within it. So when the movie details page is refreshed, we are getting the non-HTML5 location provider. We tried turning html 5 mode on globally but it's caused all kinds of problems. Our routing needs are very tricky :(

@valentinkostadinov
Copy link

Seeing the same bug in 1.2.15. Any help is appreciated.

@YukonSaint
Copy link

Here's the fix we came up with to override the angular function without hacking into the angular source. I'm sure it has custom stuff that's native to us. Use at your own risk.

dtvModule.config(['$provide', function($provide) {
  $provide.decorator('$location', ['$delegate', '$browser', '$window', function($delegate, $browser, $window) {
    var pushStateSupported = (window.history && window.history.pushState);

    // handle scenario of user opens overlay, then refreshes, then clicks back (angular bug: https://github.com/angular/angular.js/issues/4776)
    if (!$delegate.$$html5) {
      var pseudoAppBase = stripHash($browser.url());
      var pseudoAppBaseNoFile = stripFile(pseudoAppBase);
      var originalParse = $delegate.$$parse.bind($delegate); // clone existing $$parse function, obviously this could break if we upgrade, but we can't do that until we no longer have to support IE8

      $delegate.$$parse = function(url) {
        var pseudoWithoutBaseUrl = beginsWith(pseudoAppBase, url) || beginsWith(pseudoAppBaseNoFile, url);
        if (typeof pseudoWithoutBaseUrl === 'undefined') {
          var prevUrls = JSON.parse((sessionStorage.getItem('angularPatch') ? sessionStorage.getItem('angularPatch') : '{"prevUrls":[]}')).prevUrls;

          // check most recent 2 urls, if either matches just send the user to home page, otherwise they get into an endless loop
          if (url == prevUrls[prevUrls.length-1] || url == prevUrls[prevUrls.length-2]) {
            console.log('redirecting to home!, too many repeat urls - url='+url);
            sessionStorage.setItem('angularPatch', JSON.stringify({"prevUrls":[]}));
            window.location = dtvClientData.url.appHomePage;
          }
          else {
            console.log('redirecting to url!, url='+url);
            prevUrls.push(url);
            sessionStorage.setItem('angularPatch', JSON.stringify({"prevUrls":prevUrls}));
            window.location = url; // redirect user to where they're trying to go
          }
        }
        else {
          console.log('executing $$parse as normal!!!')
          originalParse(url);
        }
      }.bind($delegate);
    }
}

@valentinkostadinov
Copy link

Having exact same issue with v1.2.16.

I have a scenario very similar to MattSavino - main page (html5mode=true) shows item list, click on an item routes to an ng-view of the item detail. Now, on refresh, the server generates a standalone details page (html5mode=false). Navigating back from there throws at the exact same place:
TypeError: Cannot read property 'charAt' of undefined
at Object.$$parse ...

@btford btford removed the gh: issue label Aug 20, 2014
@nirajmchauhan
Copy link

Facing the same issue, it seems jquery mobile and angularjs are conflicting. In my case if I remove jquerymobile it works fine but not without it.

@ghost
Copy link

ghost commented Dec 2, 2014

The beginsWith function (as documented) can return undefined, so the following lines

var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' ? beginsWith(hashPrefix, withoutBaseUrl) : (this.$$html5) ? withoutBaseUrl : '';

can fail with this error. I see it happening when clicking the back button which navigates away from the application to a non angular page.

Can we have some defensive code added there please?

@pkozlowski-opensource
Copy link
Member

@tonyleeper absolutely, we can add defensive code! Just please, please - provide a reproduce scenario in punker or similar - this will help us assure that we are fixing the actual bug you've got and are not "too defensive" at the same time

@ghost
Copy link

ghost commented Dec 2, 2014

@pkozlowski-opensource it's intermittent, so not that easily reproducible. Seems like a unit test could catch that particular code path though. cheers.

@pkozlowski-opensource
Copy link
Member

@tonyleeper sure. Still we need to be able to write this unit test and write it in a way that corresponds to the real-life scenario. So if you can send a PR with a test that you think illustrates the problem, it would be as good as a live reproduce :-)

@YukonSaint
Copy link

FYI - the scenario I describe above is highly reproducible. Unfortunately the code is way too involved and complex to stick in a punker. I can tell you that the

if (withoutBaseUrl==undefined) {
window.location = url;
return;
}

hack works fine. As does the extremely hairy decorator fix I posted.

Maybe try this:

  1. Go from a page with $locationProvider.html5Mode(true) to a page with $locationProvider.html5Mode(false)
  2. Hit refresh
  3. Try to go back

Like someone else said, trying to simulate refreshes and back events in plunker or fiddle seems really hard.

@jirihelmich
Copy link

Just experienced this. Handling undefined in a variable, which is assigned a result of a function that is documented to return undefined is not too much defensive, that should be something you just do.

@jirihelmich
Copy link

Here's my workaround based on what @YukonSaint suggested:

$provide.decorator('$location', function($delegate, $browser) {
                function beginsWith(begin, whole) {
                    if (whole.indexOf(begin) === 0) {
                        return whole.substr(begin.length);
                    }
                }

                function stripHash(url) {
                    var index = url.indexOf('#');
                    return index == -1 ? url : url.substr(0, index);
                }

                function stripFile(url) {
                    return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
                }

                var appBase = stripHash($browser.url());
                var appBaseNoFile = stripFile(appBase);

                var originalParse = $delegate.$$parse.bind($delegate);
                $delegate.$$parse = function(url) {
                    var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
                    if (withoutBaseUrl) {
                        originalParse(url);
                    }
                }.bind($delegate);
            });

petebacondarwin pushed a commit that referenced this issue Jun 20, 2015
Previously, if you navigated outside of the current base URL angular
crashed with a `Cannot call method 'charAt' of undefined` error.

Closes #11302
Closes #4776
netman92 pushed a commit to netman92/angular.js that referenced this issue Aug 8, 2015
Previously, if you navigated outside of the current base URL angular
crashed with a `Cannot call method 'charAt' of undefined` error.

Closes angular#11302
Closes angular#4776
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants