16

How can I pass actual URL (with slashes, commas, etc.) as a $routeParam to AngularJS App?

this will work: http://paprikka.github.io/le-bat/#/preview/asdadasda

this won't: http://paprikka.github.io/le-bat/#/preview/http://page.com

neither will this: http://paprikka.github.io/le-bat/#/preview/http%3A%2F%2Fpage.com

or this: http://paprikka.github.io/le-bat/#/preview/?url=http%3A%2F%2Fpage.com

Details

AngularJS routing mechanism by its design does not allow to pass strings with slashes as query parameters. I can understand the reasoning behind this decision - we don't want to create a stateless server here.

However, there are still cases when using different separators or regular expressions in routes might be necessary.

I wanted to create an app that takes a url hash string parameter and loads its content to an iframe (link here). Routes are set up in pretty standard way (I'm using Coffeescript, but this snippet does not differ from pure js):

$routeProvider
  .when('/preview/:src', {templateUrl: 'partials/preview.html',
  controller: 'PreviewCtrl'})
  .when('/preview', {templateUrl: 'partials/preview.html',
  controller: 'PreviewCtrl'})

Of course, I can load url from hash before AngularJS gets bootstrapped and then pass it to the library, but it would be nice if I could also update current route parameter when changing data in scope - that's why I think it's much better not to avoid AngularJS API.

Rafal Pastuszak
  • 3,100
  • 2
  • 29
  • 31
  • 1
    Sorry I didn't get what was the question? – jsalonen Apr 27 '13 at 20:34
  • 1
    Hm.. I've updated the question with a more terse description of the problem. Take a look at the link I've added (hopefully, this won't be treated as spam:)) – Rafal Pastuszak Apr 27 '13 at 20:46
  • Couldn't you just use query-param style encoding? Like `http://example.com/#/preview/?url=http://page.com`. See also: http://docs.angularjs.org/api/ng.$routeParams – jsalonen Apr 27 '13 at 20:51
  • Well, I wouldn't ask if I could:) Did you check the demo? And, of course - I've read the docs. – Rafal Pastuszak Apr 27 '13 at 20:58
  • Ah right of course, that's the problem here! Have you tried escaping the url with `encodeURIComponent`? Results in something like `http://example.com/#/preview/?url=http:%3A%2F%2Fpage.com` – jsalonen Apr 27 '13 at 21:00
  • Ok so I guess that doesn't work either :/ – jsalonen Apr 27 '13 at 21:01
  • Yes, this was the first think I though of:( slash escaped to `%2F` will break routing as well. On the other hand `%20` will get passed. – Rafal Pastuszak Apr 27 '13 at 21:04
  • Thanks. The problem is ultra-clear now, only a solution is missing :) – jsalonen Apr 27 '13 at 21:08
  • @RafalPastuszak Did you ever find a solution to this? I'm trying to achieve something similar. – JVG Aug 04 '13 at 02:38
  • Not one I've tested. But after some time spent with angular I've come with a rather simple idea: you could transform url fragment with your own service before it gets parsed via $location and $routeParams and remove conflicting characters, eq. `#/http://paprikka.pl` would be #/url=paprikka.pl. Then, you could create a rule for things beginning with `url=` in the $routeProvider config. I'll check that soon in practice and let you know:) – Rafal Pastuszak Aug 04 '13 at 10:26
  • @Jascination, I've posted an answer with Plunkr. Let me know if it works in your case:) – Rafal Pastuszak Aug 04 '13 at 12:57

4 Answers4

17

Using $routeProvider in Angular 1.2, you can pass in a url if it's at the end of the path by adding an asterik to the pattern. The following should work whether or not you URLComponentEncode the url.

The route:

angular.module('angularApp', ['ngRoute'])
      .when('/frame/:picture_url*', {
        templateUrl: 'views/frame.html',
        controller: 'PictureFrame'
      });

The controller:

      .controller('PictureFrame', function($scope, $routeParams, $sce){
        //whitelist the URL
        $scope.picture_url = $sce.trustAsResourceUrl($routeParams.picture_url);
      });

Then in your template:

<iframe ng-src="{{picture_url}}"></iframe>
mattwad
  • 1,743
  • 17
  • 13
5

Ok, I've managed to find a solution working with current stable version (@1.0.7).

Current way of handling this problem will involve $route-related events, parsing angular-incompatible urls on the fly and handling them via an additional service working in a similar way as $http interception.

You can see working code examples here: http://embed.plnkr.co/fIA2xj/preview

Main steps

  1. pass an angular-incompatible url as usual, eg. go to site.com/url/http://site.com
  2. listen to a $routeChangeStart event and extract correct url parameter for paths beginning with /url/
  3. encode the correct url parameter to an angular-compatible form (in this particular case, I use base64). Don't use encodeURIComponent, because angular will treat as any other url
  4. redirect to another route with your business logic, eg. site.com/parsed-url/BASE64_GOES_HERE
  5. decode the URL in the controller and use it as usual :)

Code

Create angular app module as usual

angular.module('routes',[]).config([

  '$routeProvider',

  function($routeProvider){
    $routeProvider
      .when('/test', {templateUrl: 'test.html'})
      // This one is important:
      // We define a route that will be used internally and handle 
      // parameters with urls parsed by us via the URLInterceptor service 
      .when('/parsed-url/:url', {templateUrl: 'url.html', controller:'URLCtrl'})
      .when('/', {redirectTo: '/test'})
      .otherwise({templateUrl: '404.html'});

  }

])

URL Interceptor service (singleton)

.service('URLInterceptor', function($rootScope, $location){
  // We listen to $routeChangeStart event and intercept it if 
  // the path matches our url scheme. In this case, every route
  // beginning with /url/ will be caught
  $rootScope.$on('$routeChangeStart', function(e, next, current){

    // $location.path does change BEFORE actual routing happens,
    // so in this case we get parsed new location object
    // for free.

    // To be hones, a better way of handling this case might be using 
    // $locationChangeStart event instead, but it would require us to parse urls 
    // manually.
    var path = $location.path();
    // check if string begins with '/url/'
    var matcher = path.slice(0,5);
    var cleanPath = '';
    if (matcher === '/url/'){
      // Yes it does, yay!
      // Remove leading '/url/' to extract the actual parameter
      cleanPath = path.slice(5);
      // Encode our url to a safe version. We know that encodeURIComponent won't 
      // work either, so a good choice might be base64.
      // I'm using https://code.google.com/p/javascriptbase64/downloads
      $location.path('/parsed-url/' + Base64.encode(cleanPath));
      // Prevent default event execution. Note that, it won't cancel related $location Events
      e.preventDefault();
    }
  });

  return {
    decode: Base64.decode,
    encode: Base64.encode
  }
})

Controllers

// Main application controller
// We instantiate our URLInterceptor service here
.controller('AppCtrl',function($scope, $location, URLInterceptor){
  $scope.navigateTo = function (path) {
    $location.path('/url/' + path);
  }
})
.controller('URLCtrl', function($scope, $routeParams, URLInterceptor){
  $scope.url = URLInterceptor.decode($routeParams.url);
});

Two things you should remember:

  1. Although I tried to create a solution as clean as possible, usually passing the data this way to angular isn't considered a good practice, so try not to use it unless you really need to.
  2. You can handle this issue with only one route. I just find it cleaner this way.
Rafal Pastuszak
  • 3,100
  • 2
  • 29
  • 31
1

I have a solution but I don't know if it will help you. From Angular documention http://docs.angularjs.org/api/ng.$location $location has a function search(search, paramValue)

To pass the parameter:

parameter = encodeURIComponent url
$location.search({ yourURLParameter: parameter }).path('/preview')

To read the parameter:

url = decodeURIComponent $location.search().yourURLParameter

Of course you need to inject $location dependency

jtello
  • 451
  • 4
  • 7
0

I have mixed search params with routes. Your search needs to come before your routes.. specifically for older browsers. I think ie7 blows up if its not url/?search/#/hash

Try this format:

domain.com/?my=params&another=param/#/my/hashes
KesaVan
  • 1,031
  • 16
  • 32
Nawlbergs
  • 1,012
  • 1
  • 11
  • 10
  • That's cool, but search and routing is done by the front-end. I'm sorry but I had to downvote your answer :( Someone suggested using query-style params in url fragments, but it's not a good idea as well. Anyway, thanks for taking your time. – Rafal Pastuszak Jul 28 '13 at 09:07