Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preserve traditional anchor behavior with ng-include

Tags:

angularjs

I am not building a single-page application, but rather a "traditional" site that uses AngularJS in places. I've hit the following problem (using 1.3.0-beta.6):

Standard, working anchor links:

<a href="#foo">Link text</a>
... [page content]
<a id="foo"></a>
<h1>Headline</h1>
[more content]

That works fine. Now I introduce a template partial somewhere:

<script type="text/ng-template" id="test-include.html">
  <p>This text is in a separate partial and inlcuded via ng-include.</p>
</script>

which is invoked via:

<div ng-include="'test-include.html'"></div>

The partial is included properly, but the anchor link no longer works. Clicking on "Link text" now changes the displayed URL to /#/foo rather than /#foo and the page position does not change.

My understanding is that using ng-include implicitly tells Angular that I want to use the routes system and overrides the browser's native anchor link behavior. I've seen recommendations to work around this by changing my html anchor links to #/#foo, but I can't do that for other reasons.

I don't intend to use the routes system - I just want to use ng-include without it messing with browser behavior. Is this possible?

like image 668
shacker Avatar asked Apr 25 '14 18:04

shacker


4 Answers

The reason is that angular overrides the behavior of standard HTML tags which include <a> also. I'm not sure when this change happened because angular v1.0.1 works fine with this.

You should replace the href attribute with ngClick as:

<a ng-click="scroll()">Link text</a>

And in a controller so:

function MyCtrl($scope, $location, $anchorScroll) {
  $scope.scroll = function() {
    $location.hash('foo');
    $anchorScroll();
  };
};

Demo: http://jsfiddle.net/HB7LU/3261/show/

Or simply use double hash as:

<a href='##foo'>Link text</a>

Demo: http://jsfiddle.net/HB7LU/3262/show/

Update: I did not know that you want no modification in HREF. But you can still achieve the desired result by overriding the existing a directive as:

myApp.directive('a', function() {
  return {
    restrict: 'E', 
    link: function(scope, element) {
        element.attr('href', '#' + element.attr('href'));
    }
  };
});

Demo: http://jsfiddle.net/HB7LU/3263/

like image 129
codef0rmer Avatar answered Nov 20 '22 06:11

codef0rmer


My understanding is that using ng-include implicitly tells Angular that I want to use the routes system and overrides the browser's native anchor link behavior. I've seen recommendations to work around this by changing my html anchor links to #/#foo, but I can't do that for other reasons.

Routing system is defined in a separate module ngRoute, so if you did not injected it on your own - and I am pretty sure you did not - it is not accessible at all.

The issue is somehow different here.

ng-include depends on: $http, $templateCache, $anchorScroll, $animate, $sce. So make use of ng-include initiate all these services.

The most natural candidate to investigate would be $anchorScroll. The code of $anchorScroll does not seem to do any harm, but the service depends on $window, $location, $rootScope. The line 616 of $location says:

 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''

So basically the base href is set to '', if it was no set before.

Now look HERE - from BalusC answer :

As to using named anchors like , with the tag you're basically declaring all relative links relative to it, including named anchors. None of the relative links are relative to the current request URI anymore (as would happen without the tag).

How to mitigate the issue?

I do not have much time today, so cannot test it myself, but what I would try to check as the first option is to hook up to '$locationChangeStart' event and if the new url is of #xxxxxx type just prevent the default behaviour and scroll with $anchorScroll native methods instead.

Update

I think this code should do the work:

$scope.$on("$locationChangeStart", function (event, next, current) {

    var el, elId;

    if (next.indexOf("#")>-1) {
        elId = next.split("#")[1];
        el = document.getElementById(elId);
        if(el){
           // el.scrollIntoView(); do not think we need it
           window.location.hash = "#" + elId;
           event.preventDefault();
        }    
    }
});
like image 44
artur grzesiak Avatar answered Nov 20 '22 06:11

artur grzesiak


This is the best solution, and works in recent versions of Angular:

Turn off URL manipulation in AngularJS

like image 2
Kohjah Breese Avatar answered Nov 20 '22 06:11

Kohjah Breese


A lot late to the party but I found that adding a simple target="_self" fixes it.

<a href="#anchor" target="_self">Link</a>
like image 2
Motoman Avatar answered Nov 20 '22 06:11

Motoman