25

I was using AngularJs-1.0.7 and Bootstrap in my application. Recently I migrated from AngularJs-1.0.7 to AngularJs-1.2. I am using Bootstrap's Accordions and Tabs.

Html code for Tab contains <a href="#id_for_content"> as shown below.

<ul id="myTab" class="nav nav-tabs">
    <li class="active"><a href="#firstTab" data-toggle="tab">Home</a></li>
    <li><a href="#secondTab" data-toggle="tab">Profile</a></li>
</ul>

<div id="myTabContent" class="tab-content">
    <div class="tab-pane fade in active" id="firstTab">
      <p>Content for first tab.</p>
    </div>
    <div class="tab-pane fade" id="secondTab">
      <p>Content For second tab.</p>
    </div>
</div>

In Angular's old versions, route change happens only if we give anchor tag like <a href="#/firstTab">. But AngularJs-1.2 redirects <a href="#firstTab">. It doesn't consider the / in between # and firstTab. Hence while clicking on Tab it redirects to http://web_url/#/firstTab . How to solve this issue?


My Solution

I found a solution for this issue. I wrote a directive for a tag. In that directive I checked for href attribute. If it matches prevent from its default behavior. Check the following code.

app.directive('a', function() {
    return {
        restrict: 'E',
        link: function(scope, elem, attrs) {
            if(attrs.href === '#firstTab'|| attrs.href === '#secondTab'){
                elem.on('click', function(e){
                    e.preventDefault();
                });
            }
        }
   };
}); 

But the problem with this method is, I have to check each and every tab ids or accordion ids here. If I use dynamic Ids for them, its not possible to check in directive.

If you can find better solution, let us all know about it.

T J
  • 42,762
  • 13
  • 83
  • 138
Sajith
  • 2,842
  • 9
  • 37
  • 49
  • What if you use a `class` directive? I mean, a directive named `prevent-default` with `restrict: 'C'`, link function is the same without if part, then you can use it anywhere by adding class `"prevent-default"` to it... Like `...` I guess mine is also a hacky solution but better than writing all id's. If you want I can modify code and send as answer. – Umut Benzer Nov 15 '13 at 18:08
  • `if(/^#/.test(attrs.href))` will work for all links beginning with a `#`. – Adam Nov 16 '13 at 20:49
  • 5
    Try adding `target="_self"` it generally disabled the angular routing on links. – Chris Nicola Nov 17 '13 at 07:50
  • Take a lot at these native angular directives based on bootstrap http://angular-ui.github.io/bootstrap/ – dimirc Nov 17 '13 at 16:31

9 Answers9

50

Just add this to your links:

target="_self"

And you're ready ;)

Vassilis Pits
  • 3,788
  • 4
  • 33
  • 48
21

try data-target="#something", it works fine for me in both development and production environment.

Paritosh
  • 11,144
  • 5
  • 56
  • 74
Sonymon Mishra
  • 210
  • 2
  • 3
  • Oh man this is great! Now i don't have to use angular-strap or angular-ui just for the modal and this is way easier. – Catfish Apr 13 '15 at 22:48
11

I would just like to let you know that there is another solution to solve this issue (without defining function in controllers).

You can create a directive that will prevent default browser's behaviour (which is actually changing URL):

var PreventDefault = function () {

    var linkFn = function (scope, element, attrs) {
        $(element).on("click", function (event){
            event.preventDefault();
        });
    };

    return {
        restrict: 'A',
        link: linkFn
    }
};

and then just add the directive to each a element responsible for toggling a tab like this:

<ul id="myTab" class="nav nav-tabs">
    <li class="active"><a href="#firstTab" prevent-default data-toggle="tab">Home</a></li>
    <li><a href="#secondTab" prevent-default data-toggle="tab">Profile</a></li>
</ul>

<div id="myTabContent" class="tab-content">
    <div class="tab-pane fade in active" id="firstTab">
      <p>Content for first tab.</p>
    </div>
    <div class="tab-pane fade" id="secondTab">
      <p>Content For second tab.</p>
    </div>
</div>
PrimosK
  • 13,848
  • 10
  • 60
  • 78
6

In general you can define functions on the prototype of all scopes by using the rootscope. Add ng-click on your anchor tags and pass the $event to the rootscope handlers. Angular can send the originating event as well with ng-click:

<a ng-click="tabclick($event)">

in a run block of your module you can then set a tabClick function on $rootScope

$rootScope.tabclick = function ($event) { $event.preventDefault(); }

This will work for any scopes and you won't have to check against specific element ids.

Sample fiddle: http://jsfiddle.net/stto3703/wQe6P/

Cheers

Stefan
  • 446
  • 3
  • 9
2

You can use ng-click to call a function in your JS. In that function, use the event's preventDefault method, and use $location's hash method to change the hash in your URL.

Yaron Schwimmer
  • 5,327
  • 5
  • 36
  • 59
1

I've been having this problem for a while now too, and I only just stumbled across this question (and your own answer). It turns out that you are almost correct with your solution, just a slight modification makes it work as you'd like.

In addition to the issue you were having with tabs, I was also encountering this problem with dropdown links -- they were navigating away from the current page when clicked, instead of dropping down the menu -- and in fact, this problem is present on any link that was designed to toggle something, i.e. with the data-toggle attribute set.

So, a slight modification to your code to check for the presence of the 'toggle' attribute solves it for me:

app.directive('a', function() {
    return {
        restrict: 'E',
        link: function(scope, elem, attrs) {
            if(attrs.toggle){
                elem.on('click', function(e){
                    e.preventDefault();
                });
            }
        }
   };
});

Hope this helps!

Tom Spink
  • 46
  • 3
0

Your solution in your question does work, but to prevent having to check for each anchor's id, change

if (attrs.href === '#firstTab'|| attrs.href === '#secondTab')

to

if (attrs.href && attrs.href.indexOf('#') > -1)

Directive:

.directive('a', function () {
    return {
        restrict: 'E',
        link: function (scope, elem, attrs) {
            if (attrs.href && attrs.href.indexOf('#') > -1) {
                elem.on('click', function (e) {
                    e.preventDefault();
                });
            }
        }
    };
})  
Marcel
  • 7,909
  • 5
  • 22
  • 25
0

credits to https://prerender.io/js-seo/angularjs-seo-get-your-site-indexed-and-to-the-top-of-the-search-results/

My solution: - inside app.js try adding "$locationProvider.hashPrefix('!');"

    .config(['$stateProvider', '$urlRouterProvider', '$httpProvider', '$locationProvider',
    function($stateProvider, $urlRouterProvider, $httpProvider, $locationProvider) {
    $stateProvider
    .state('menu', {
        url: '/',
        cache: false,
        abstract: true,
        templateUrl: 'static/www/templates/menu.html'
    })
    .state('menu.main_page', {
    url: 'app/main',
    cache: false,
    views: {
        'menuContent': {
          templateUrl: 'static/www/templates/mainpage.html',
          controller: 'MainCtrl'
        }
    }
    })
    $locationProvider.hashPrefix('!');
    $urlRouterProvider.otherwise(function ($injector, $location) {
        var $state = $injector.get('$state');
        $state.go('menu.mainpage');
    });
   }])
  • inside index.html try adding

    <meta name="fragment" content="!">

and as Marcel said above

.directive('a', function () {
return {
    restrict: 'E',
    link: function (scope, elem, attrs) {
        if (attrs.href && attrs.href.indexOf('#') > -1) {
            elem.on('click', function (e) {
                e.preventDefault();
            });
        }
    }
};
})

that's all! works for me!

0

If you don't success with previous answers, you could try use $timeout... In this case I have used AngularUI tabs...

vm.removeUserTab = function (user) {
    $timeout(function () {
        vm.activeTab = 0;
        var index = vm.userTabs.indexOf(user);
        vm.userTabs.splice(index, 1);
    });
};
soreal
  • 264
  • 1
  • 9