1

I'm working on an Angular app that needs to link to a fixed (external) route with some query params that are set on the Angular page itself. I'd like to provide some sort of nice data-binding in my Angular HTML, like:

<a href="http://www.api.com/query?param={{value}}&flag={{check}}">Link</a>

However, all my query parameters are optional. This is easily handled within an Angular app itself, using something like

$location.path('/query').search({param: value, flag: check});

Is there any way I can get the benefits of this declarative style when I just want to format a text link?

Daniel Buckmaster
  • 7,108
  • 6
  • 39
  • 57

3 Answers3

5

I realised that the ideal mechanism for this is probably a filter. It leverages automatic data-binding and is really simple to use. The HTML becomes:

<a ng-href="http://www.api.com/endpoint{{params | query}}">Link</a>

And the filter code:

myApp.filter('query', function() {
  return function(opts) {
    var params = [];

    for(var opt in opts) {
      if(opts.hasOwnProperty(opt)) {
        if(opts[opt] !== "" && opts[opt] !== undefined) {
          params.push(opt + "=" + opts[opt]);
        }
      }
    }

    return params.length
      ? "?" + params.join("&")
      : "";
  };
});

And here's a fiddle. This filter handles undefined properties and empty strings... perfect for my own use-case but I realise it might not be for everyone. Anyway, the filter code itself is pretty simple to modify (and you could easily replace it with $.param if that suits you).

EDIT: I've since realised that AngularJS sets models bound to an empty input to null, so in my actual code I'm checking against null in the filter.

Daniel Buckmaster
  • 7,108
  • 6
  • 39
  • 57
1

Use the ngHref attribute like this:

<a ng-href="http://www.api.com/query?param={{value}}&flag={{check}}"></a>

Here is a link to the documentation: http://docs.angularjs.org/api/ng.directive:ngHref

Pavel Nikolov
  • 9,401
  • 5
  • 43
  • 55
  • Oh! That's a good idea, thanks. But from the documentation I see no mention of what might happen if `value` or `check` (or both) is undefined. In the `$location` code, they will not be included in the URL. – Daniel Buckmaster Jan 12 '14 at 23:38
  • If you don't want them to be included in the URL then have a `queryString` variable and populate it in the controller. Then just include this variable in the URL `http://example.com/query?{{ queryString }}` – Pavel Nikolov Jan 12 '14 at 23:52
  • By the way if you use an `undefined` variable, then your url will look like this `"http://www.api.com/query?param=&flag="` which might work out of the box - the parameters on the other end will be empty strings. – Pavel Nikolov Jan 12 '14 at 23:54
  • I wanted to find a solution that means I don't have to bother with manual data-binding (using a string, I would have to `$watch` the components and regenerate the string myself). – Daniel Buckmaster Jan 13 '14 at 23:54
1

You can use a ternary operator in the expression enclosed by the double curlys. For instance: {{value ? 'param='+value : ''}} will result in "param={{value}}" if param exists and is true otherwise it will result in "".

So you can do the following:

<a href="http://www.api.com/query?{{value ? 'param='+value : ''}}{{check ? '&flag='+check+'' : ''}}"></a>

demo fiddle

KayakDave
  • 24,636
  • 3
  • 65
  • 68
  • That's what I was afraid of! I guess it's not so bad, and having `?` on the end of the URL even with no parameters isn't going to cause any havoc. – Daniel Buckmaster Jan 12 '14 at 23:46
  • True, or you could write a test that checks if any parameter exists to use for the "?". The tradeoff is your html starts getting complicated with logic – KayakDave Jan 12 '14 at 23:48
  • @DanielBuckmaster I would still populate the whole query string in the controller and then have an url like this `example.com/query?{{ query_string }}` - avoid having any calculations in the databound expressions. If the expression is in the controller it will be executed only once! – Pavel Nikolov Jan 12 '14 at 23:50
  • If you go the controller route- which does have it's separation of concerns advantages- I'd be tempted to use a function so that your parameters are updated along with value, check, etc... (assuming those values change during execution) So in @PavelNikolov's example I might do: example.com/query?{{ query_string(value,check) }} – KayakDave Jan 12 '14 at 23:57
  • @KayakDave usually I avoid having functions in the bound expressions - it's not always possible but if it's a large grid with links etc I would have just a single value or using some sort of `bind once` mechanism (there are multiple available out there). The whole point is that for every data-bound expression Angular registers a watcher which evaluates the expression. If the expression is a variable then it's very fast. If there are any operations or function calls it tends to get slower. Of course if you just have a single data-bound expression in the view it doesn't matter. – Pavel Nikolov Jan 13 '14 at 00:02
  • @KayakDave do function calls automatically get reevaluated when their arguments change in the controller? I'm tempted to try this route. Or would I have to `$watch` the parameters? – Daniel Buckmaster Jan 13 '14 at 00:09
  • @PavelNikolov in this case I'm only binding to a single URL so performance isn't critical. I appreciate the discussion though. – Daniel Buckmaster Jan 13 '14 at 00:10
  • Function calls are executed each time the expression is evaluated. Thus, @PavelNikolov valid concern. They can become expensive, but they give you the data binding you're looking for. And I think it's easy to prematurely optimize in JS as the engines are moving in a direction where clean code is sometimes also the fastest code. – KayakDave Jan 13 '14 at 00:12