38

I am building a navigation tree in Angular JS. Most links in the tree will point to pages within my website, but some may point to external sites.

If the href of a link begins with http:// or https:// then I am assuming the link is for an external site (a regex like /^https?:\/\// does the trick).

I would like to apply the target="_blank" attribute to these links. I was hoping to do this with angular when I am creating my links:

<ul>
    <li ng-repeat="link in navigation">
        <a ng-href="{{link.href}}" [add target="_blank" if link.href matches /^https?:\/\//]>{{link.title}}</a>
    </li>
</ul>

Can anyone help me out?

Thanks

Ben Guest
  • 1,488
  • 2
  • 17
  • 29
  • Don't actually do this. It is bad from a UI point of view. Lets **users** decide when to open new windows or tabs. – Good Person May 10 '14 at 18:23
  • 10
    I know what you mean, but if the user is on the website and wants to click a link to an external resource, it makes sense to open the link in a new tab / window rather than redirecting their current tab. I use it sparingly and only when I think that being directed away from the site without it's tab staying open would be irritating / confusing for some users. – Ben Guest May 10 '14 at 19:33
  • 1
    no. If a user is on a website and wants to click a link then it makes sense to open in the same tab. If they want to open it in a new tab they will ctrl-click, or right click and say "open in new tab". DO NOT break web conventions. – Good Person May 10 '14 at 19:34
  • @BenGuest Web conventions? You have this idea right. I am willing to take the karma on this one. – Coded Container Sep 23 '15 at 18:00
  • 9
    @GoodPerson - There certainly are scenarios in which this is good from a UX perspective. Our AngularJS project is a SPA. Users that tap/click links want to open the link without disrupting their work in the app. Opening in a new tab by default is an appropriate way to meet this need. – rinogo Jan 27 '17 at 17:36
  • Additionally, I think you may be accustomed to designing for tech-savvy users. :) Ctrl-clicking/right clicking is akin to black magic for our users, and on mobile, tap-and-hold is likewise foreign. – rinogo Jan 27 '17 at 17:37
  • 1
    @GoodPerson opening an external link within the current tab is a bad experience as the average user would not know to navigate with the back button. Spend 1 hour in a UAT session and tell me again "web conventions" must always be conformed to. – Wancieho May 13 '19 at 14:27
  • @Wancieho I have spent time observing users. A big part of the problem is different sites providing different experiences (for example, some using _blank and others not) thus never letting people get properly trained on how things work – Good Person May 13 '19 at 18:27

4 Answers4

80

I was just about to create a directive as suggested and then realised that all you actually need to do is this:

<a ng-attr-target="{{(condition) ? '_blank' : undefined}}">
  ...
</a>

ng-attr-xyz lets you dynamically create @xyz, and if the value is undefined no attribute is created -- just what we want.

Bradley Flood
  • 10,233
  • 3
  • 46
  • 43
Mark Birbeck
  • 2,813
  • 2
  • 25
  • 12
  • 2
    this was a great simple solution for me. It conditionally writes the attribute which is what I needed in this case. Simple and I learned I can do this with pretty much any attribute. Thanks! – Hcabnettek Nov 05 '15 at 19:43
  • 1
    nice one, this is just what I was after. wasn't looking forward to adding another directive for something so simple! – tenderloin Dec 19 '15 at 14:22
  • See Angular [Documentation](https://docs.angularjs.org/guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) for how it works. – stomy Oct 31 '19 at 18:39
  • This [question](https://stackoverflow.com/q/32332561/7794769) explains the use of `ng-attr-`. – stomy Oct 31 '19 at 18:40
  • `error NG8002: Can't bind to 'ng-attr-target' since it isn't a known property of 'a'.` – Anton Duzenko Jul 09 '21 at 15:28
36

Update

Or use a directive:

module.directive('myTarget', function () {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var href = element.href;
          if(true) {  // replace with your condition
            element.attr("target", "_blank");
          }
        }
    };
});

Usage:

<a href="http://www.google.com" my-target>Link</a>

When you don't need to use this with Angular routing you can simply use this:

<a href="http://www.google.com" target="{{condition ? '_blank' : '_self'}}">Link</a>
Sebastian
  • 16,813
  • 4
  • 49
  • 56
  • Im under the impression that target="_self" prevents the routeProvider from capturing the link change. See [here](https://groups.google.com/forum/#!topic/angular/basidvjscRk) – Ben Guest May 10 '14 at 17:54
  • @BenGuest correct, in this case you need a simple directive – Sebastian May 10 '14 at 18:15
  • Okay thanks. The directive was exactly what I wanted. I guess that any custom behaviour regarding html elements warrants creation of a directive. – Ben Guest May 10 '14 at 19:29
5

The proposed solutions will only work with hard-coded hrefs. They will fail if they are interpolated because angular will not have done any interpolation when the directive is run. The following solution will work on interpolated hrefs, and is based on Javarome's solution:

yourModule.directive('a', function() {
  return {
    restrict: 'E',
    link: function(scope, elem, attrs) {
      attrs.$observe('href', function(){
        var a = elem[0];
        if (location.host.indexOf(a.hostname) !== 0)
          a.target = '_blank';
      }
    }
  }
}
JimmyRare
  • 3,958
  • 2
  • 20
  • 23
w.brian
  • 16,296
  • 14
  • 69
  • 118
2

A more simple directive does not require changes in your HTML by handling all <a href="someUrl"> tags, and adding target="_blank" if someURL does not target the current host:

yourModule.directive('a', function() {
  return {
    restrict: 'E',
    link: function(scope, elem, attrs) {
      var a = elem[0];
      if (a.hostname != location.host)
         a.target = '_blank';
    }
  }
}
Jérôme Beau
  • 10,608
  • 5
  • 48
  • 52