26

For example:

 $stateProvider
            .state('external', {
                url: 'http://www.google.com',

            })

url assumes that this is an internal state. I want it to be like href or something to that effect.

I have a navigation structure that will build from the ui-routes and I have a need for a link to go to an external link. Not necessarily just google, that's only an example.

Not looking for it in a link or as $state.href('http://www.google.com'). Need it declaratively in the routes config.

Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
Shane
  • 4,185
  • 8
  • 47
  • 64
  • I'm not sure if there's a simpler way - there may be. But have you considered a half-way solution, where you have a key (maybe `externalURL`) in your `$route` object, a function that redirects the browser if the key/value is present, and simply bind `$on("$routeChangeStart", redirectIfExternal)` during either the config or run stage? – DRobinson May 13 '15 at 17:08

5 Answers5

38

Angular-ui-router doesn't support external URL, you need redirect the user using either $location.url() or $window.open()

I would suggest you to use $window.open('http://www.google.com', '_self') which will open URL on the same page.

Update

You can also customize ui-router by adding parameter external, it can be true/false.

  $stateProvider
  .state('external', {
       url: 'http://www.google.com',
       external: true
  })

Then configure $stateChangeStart in your state & handle redirection part there.

Run Block

myapp.run(function($rootScope, $window) {
  $rootScope.$on('$stateChangeStart',
    function(event, toState, toParams, fromState, fromParams) {
      if (toState.external) {
        event.preventDefault();
        $window.open(toState.url, '_self');
      }
    });
})

Sample Plunkr

Note: Open Plunkr in a new window in order to make it working, because google doesn't get open in iFrame due to some security reason.

Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
  • interesting. I was hoping for a external: true config parameter or something. – Shane May 13 '15 at 17:13
  • 3
    angular doesn't want you to leave :P – HankScorpio May 13 '15 at 17:18
  • Just a joke. Angular doesn't want you to leave, they want you to stay on the webpage so they don't provide a simple config parameter like Shane wanted. – HankScorpio May 13 '15 at 17:19
  • @HankScorpio hahaha..You are correct..script of one page can't work on other page :D – Pankaj Parkar May 13 '15 at 17:21
  • Hmm...interesting. Let me try that. – Shane May 13 '15 at 17:29
  • 3
    +1 this is the way to go. You should most probably avoid mixing with the existing property `url` and rather use something like `externalUrl`, in order to avoid confusion and preserve functionnality of `url` (the `external` flag wouldn't be need anymore of course). – rixo May 13 '15 at 17:34
  • @rixo thanks for suggestion..but you can't define a state without url..As I'm rendering `href` of anchor using `ui-sref`..so if `url` is blank then href will not get created by `ui-sref` directive..& then `$stateChangeStart` event will never fire..so I think that using `url` with `external` flag is an appropriate way of doing this.. – Pankaj Parkar May 13 '15 at 17:42
  • Intereseting idea `@Pankaj Parkar`. How would you update this code to retain access to parameters? For example, if I wanted to go to www.example.com?user_id=x where x is an incoming UI-Router parameter. – Tim Head Nov 09 '16 at 11:47
27

You could use the onEnter callback:

 $stateProvider
    .state('external', {
        onEnter: function($window) {
            $window.open('http://www.google.com', '_self');
        }
    });

Edit

Building on pankajparkar's answer, as I said I think you should avoid overriding an existing param name. ui-router put a great deal of effort to distinguish between states and url, so using both url and externalUrl could make sense...

So, I would implement an externalUrl param like so:

myApp.run(function($rootScope, $window) {
    $rootScope.$on(
        '$stateChangeStart',
        function(event, toState, toParams, fromState, fromParams) {
            if (toState.externalUrl) {
                event.preventDefault();
                $window.open(toState.externalUrl, '_self');
            }
        }
    );
});

And use it like so, with or without internal url:

$stateProvider.state('external', {
    // option url for sref
    // url: '/to-google',
    externalUrl: 'http://www.google.com'
});
rixo
  • 23,815
  • 4
  • 63
  • 68
  • 1
    use $location.url() instead – HankScorpio May 13 '15 at 17:08
  • You're right, of course better to use angular's utilities than raw javascript. `$location.url` doesn't seem to work in this context, so thanks also to @pankajparkar for the rest of the solution. – rixo May 13 '15 at 17:15
  • It doesn't? That's surprising... you just tested that? – HankScorpio May 13 '15 at 17:17
  • 1
    @HankScorpio `$location` is for navigation between app states, [not designed to sail to the external world](https://docs.angularjs.org/guide/$location#page-reload-navigation). – rixo May 13 '15 at 17:26
  • 1
    You also can use $window.location.href oder native JS window.location.href / window.location.replace – hogan Dec 08 '15 at 20:29
  • @KishorK (and hogan) Yes, pure JS works of course, but our point was to use angular wrappers to follow best practice (the main interest is to allow for easy mocking in testing, but that's another question). – rixo Apr 20 '17 at 21:38
11

As mentioned in angular.js link behaviour - disable deep linking for specific URLs you need just to use

<a href="newlink" target="_self">link to external</a>

this will disable angularJS routing on a specific desired link.

Community
  • 1
  • 1
Ebrahim
  • 1,740
  • 2
  • 25
  • 31
  • The OnEnter solution may be the cleanest -- and it prevents the listener on $rootScope from being fired all the time. I don't quite understand how "$window" is available without injecting it -- it just seems to work. – pinnprophead Jul 19 '16 at 14:34
  • @pinnprophead I think this is simpler, when I want to exclude some links to be engaged in angular routing, this is my point. for that sort I need to add a function to decide for that link separately But when I use target="_self" it exclude the deep linking for those ones we want. I hope this could help – Ebrahim Jul 19 '16 at 19:35
1

I transformed the accepted answer into one that assumes the latest version of AngularJS (currently 1.6), ui-router 1.x, Webpack 2 with Babel transpilation and the ng-annotate plugin for Babel.

.run(($transitions, $window) => {
  'ngInject'
  $transitions.onStart({
    to: (state) => state.external === true && state.url
  }, ($transition) => {
    const to = $transition.to()
    $window.open(to.url, to.target || '_self')
    return false
  })
})

And here's how the state may be configured:

.config(($stateProvider) => {
  'ngInject'
  $stateProvider
    .state({
      name: 'there',
      url:'https://google.com',
      external: true,
      target: '_blank'
    })
})

Usage:

<a ui-sref="there">To Google</a>
Christiaan Westerbeek
  • 10,619
  • 13
  • 64
  • 89
1

The original answer is deprecated and disabled by default in UI Router, you may wish to explore implementing it using transition hooks

  .state("mystate", {
    externalUrl: 'https://google.com'
  })

then:

myApp.run(['$transitions', '$window', ($transitions, $window) => {
  $transitions.onEnter({}, function (transition) {
    const toState = transition.to();

    if (toState.externalUrl) {
      $window.open(toState.externalUrl, '_self');
      return false;
    }
    return true;
  });
}]
);
vogomatix
  • 4,856
  • 2
  • 23
  • 46