7

The context:
It is necessary for me to fetch the URL from the server when the user clicks the link. It is not available beforehand. The click on the link should work as normal, but with that fetched URL.

The procedure is as follows:

  • The link contains href="#" before it is clicked
  • The link is clicked
  • I hook into the click with ng-mousedown or ng-click, and fetch the URL from the server using a $http request.
  • The href of the link is updated with the URL.
  • The original click should complete (with that new url, not the # placeholder).

The problem comes on the last step. Since the $http request is asynchronous, I suspect there is a timing issue. My suspicion is: If the server request is fast enough, it will go through, and the URL is changed before the original click goes through. If not, then the original click goes through and tries to go to the # placeholder URL. So nothing will happen when clicking the link.

I want to make the original click wait for the result of the $http request to have returned with the URL. The reason the original click is important is that it could either be a left or a middle mouse button click (cmd+click), and I don't know which one, so it'd be hard to call it myself from the javascript.

So, any clever ideas on how can make the original click go through with the fetched URL?

Magne
  • 16,401
  • 10
  • 68
  • 88
  • Can I ask why do you need to load the url just in the onclick event? If you can give a scenario/example would be appreciate also. – Carlos Verdes Apr 23 '14 at 06:43
  • Yeah, sure. Basically it's a search result listing, and we're not creating the records of every search result until the user clicks on a result. Then we make a $http post request to the server to create the record, and get the link for it containing it's database id. – Magne Apr 23 '14 at 08:18
  • I don't understand something, you have a result list, do you have on this result list the result id? – Carlos Verdes Apr 23 '14 at 11:32
  • @CarlosVerdes No, I don't have the result ID's. We populate the list from an external search engine, that uses their own ID's. We need to route the user to our own ID's when he clicks the link. – Magne Apr 23 '14 at 11:45

5 Answers5

10

If you really can't resolve the link before a click is detected, then you would be better off using an ngClick directive to call a function which might show a loading screen while waiting for your $http promise to resolve and then use $location.path() to actually initiate the route change. For example:

The HTML

<button ng-click="changeRoute($event)">Click me</button>

And the code

angular.module('myApp', [])
  .controller('pageCtrl', function($scope, $http, $location, $window) {
    $scope.changeRoute = function(event) {
      console.log('Fetching URL');

      // The popup must be created before the callback, because the callback 
      // is not considered user-initiated by the browser. Non-user-initiated
      // events always open in a new browser window (not a tab).
      if (event.which === 2 || (event.which ===1 && (event.metaKey || event.ctrlKey))) {
        // MIDDLE CLICK or CMD+LEFTCLICK - new tab
        var popup = $window.open("about:blank", "_blank"); 
        // the about:blank is to please Chrome, and _blank to please Firefox
      }

      $http.get('/some/path')
        .success(function(data){
          //check that it's a good URL, then route to it
          //$location.path(data);
          if (event.which === 1 && !event.metaKey && !event.ctrlKey) {
            // LEFTCLICK - same window
            $window.location.href = data;
          } else if (event.which === 2 || (event.which ===1 && (event.metaKey || event.ctrlKey))) {
            // MIDDLE CLICK or CMD+LEFTCLICK - new tab
            popup.location = data;
          }
        })
        .error(function(data, status, headers, config) {
          console.error('Failed to get URL: ' + status);
        });
      console.log('Fetch request sent, awaiting response...');
    };
  });

Edit

Updated to support external links and middle click will now open in a new window

Updated to support:

  • CMD+click and CTRL+click to function as a middle mouse click.
  • Putting in a new tab, instead of a new window.
Magne
  • 16,401
  • 10
  • 68
  • 88
morloch
  • 1,781
  • 1
  • 16
  • 23
  • Hm, this seems to add the path to the url, instead of replacing it. Also, when cmd+clicking to open a new tab, it will only change the url of the current tab, and open a new tab with an empty url. – Magne Apr 16 '14 at 10:14
  • In that case, you'll need to use a directive, load the url as soon as you can and let the browser handle the clicking. – morloch Apr 16 '14 at 11:32
  • Can you elaborate a bit? A directive to do what? I can't load the URL when the page loads, it has to be on click I'm afraid.. – Magne Apr 16 '14 at 12:10
  • Interesting about firefox needing `_blank`, that should be the default option, but pre-defining the popup makes sense. – morloch Apr 21 '14 at 00:00
  • @Magne, has this answered your question now? – morloch Apr 22 '14 at 12:07
  • Yeah, kind of. I'm still trying to figure out how to make the popup not steal the focus, but rather pop-under as a new unfocused tab, like the normal chrome behavior. – Magne Apr 22 '14 at 13:49
1

You should do event.preventDefault() first to make time for $http to complete its process. To do that in Angular, you should pass $event to the ng-click handler.

Example HTML:

<body ng-controller="appController">
    <a href="#" ng-click="navigate($event)">LINK</a>
</body>

App:

var app = angular.module('app', []);
app.controller('appController',
    function ($scope, $http, $window, $log) {
        // generic function for window navigation
        function nav(url, newTab) {
            if (newTab) {
                $window.open(url, '_blank');
            } else {
                $window.location.href = url;
            }
        }
        // handler for ng-click directive
        $scope.navigate = function (event) {
            // This is the key -> preventing default navigation
            event.preventDefault();
            // determine whether to open a new page or not
            var newTab = ((event.which === 1 && (event.metaKey || event.ctrlKey)) || event.which === 2);
            // get the URL from a file
            $http.get('links.json')
                .success(function (data, status, headers, config) {
                    // navigate when URL is fetched
                    nav(data.someLink, newTab);
                })
                .error(function (data, status, headers, config) {
                    $log.debug('Error:', data, status);
                });

        };
    });

See an example of the exposed $event object here.

Onur Yıldırım
  • 32,327
  • 12
  • 84
  • 98
0

I'm assuming you know how to use a callback with angular's $http and that you've probably already tried adding the link as a callback. So I have an alternative.

Alternative:
If your not being picky. You might want to just simulate the action performed by a user clicking on a link with the following function. (more about this here.)

window.location.href = "http://stackoverflow.com";

I'm not to familar with AJAX and Angular, but I'm learning them. I'll try putting that in the $http callback for you. It would look something like this.

$http.get("my/api/getUrl").success(function (data) { 
  window.location.href = data.url;
});
Community
  • 1
  • 1
John
  • 7,114
  • 2
  • 37
  • 57
  • This would always open the page in the current window, right? The thing is, I want to be able to open it in a new tab, if the user clicked the middle mouse button. – Magne Apr 12 '14 at 10:56
0

There are 2 ways to do this simply in Angular

Use...

##

<a ng-href="{{linkModel}}" href target="_blank" ng-click="linkModel='http://google.com'">Link1 to Google</a>

##

to trigger the value either directly or by calling a function from your controller.

--

See example...

EXAMPLE: http://jsfiddle.net/dsmithco/e94SH/

dsmithco
  • 351
  • 5
  • 7
  • This solution would always open in a new window, right? The thing is, I want to preserve the original click behavior. So that left-clicking will open in the same window, and middle mouse button clicking (or mcd+leftclick) will open in a new window. – Magne Apr 12 '14 at 10:52
  • Just remove target="_blank" to open in the same window. I just did that for the demo. – dsmithco Apr 24 '14 at 16:39
  • Yeah, that works. Actually, this whole approach works well, if it weren't for the delayed asynchronous http request which is needed in my case. Then, the timing issue I mentioned in the question will become a problem. – Magne Apr 25 '14 at 14:12
  • This will only work if the function you use is synchronous. It wouldn't work if you need to make an ajax call to discover the `{{linkModel}}` – xabitrigo May 08 '17 at 16:53
0

This will actually intercept the click on the link, and change the location before the click goes through.

This is because of using ng-mousedown instead of ng-click, and using an empty href tag together with ng-href. Note that it will not work if the href tag has a value like you normally would (ie. href="#" or href="javascript:"). You also can't remove the href tag completely and only have the ng-href tag, because the browser needs the href tag to recognize the anchor as a clickable link.

HTML:

<a href ng-href="{{linkModel}}" ng-mousedown="changeRoute($event)">Link1 to Google</a>

Angular Code:

angular.module('myApp', [])
  .controller('pageCtrl', function($scope, $http, $location, $window) {
    $scope.changeRoute = function(event) {
      console.log('Fetching URL');
      $http.get('/some/path')
        .success(function(data){
          linkModel = data; // or data.url if it is stored as a url on the data object
        })
        .error(function(data, status, headers, config) {
          console.error('Failed to get URL: ' + status);
        });
      console.log('Fetch request sent, awaiting response...');
    };
  });

pros:

  • will catch leftclick and middle mouse click (and cmd+click or ctrl+click) in presumably all browsers.
  • no need to click the link twice (if using ng-click with an existing href you'd have to click twice. The first click would just set the new href on the link, and the browser would also try to access the old/existing href).

cons:

  • will not catch rightclick context menu "open link in new tab", which is impossible with javascript since the normal context menu belongs to the browser (unless you want to override the context menu completely with your own right click event or context menu).
Magne
  • 16,401
  • 10
  • 68
  • 88