Cordova and AngularJS – Opening links in system browser

In the course of writing my Android/Cordova/Ionic app, I ran into another tricky requirement: Display HTML content from a web service, containing links. These links should open in the user’s preferred browser. In the end, there are several steps necessary to getting this right.

Step 1: Display unescaped HTML in the app.

When AngularJS binds content, special characters are “escaped”, so HTML tags are visible and of course non-functional. Fortunately AngularJS has a fix for this: ng-bind-html. Use this instead of ng-bind (which is equivalent to the double-bracket method). Your content will be slightly sanitized (which will be a problem later), but the links are preserved.

<div ng-bind-html="content.Description"></div>

At this point you’ll see some strange behavior: the links open inside webkit. So the user stays in your app, but there is no way to go back to your Angular HTML5 pages. In my case, even the back button did not work!

As with so many Cordova issues, “there’s a plugin for that.”

Step 2: Install the inappbrowser plugin

Despite its name, the inappbrowser plugin enables the behavior we’re looking for – opening a browser window EXTERNAL to our application. The following command will install it:

cordova plugin add org.apache.cordova.inappbrowser

In theory, all you have to do is set up your links as follows:

window.open('http://intown.biz', '_system', 'location=yes');
Take Note: There might be a better solution for step 3, that does not require Jquery. See comment by Rewon below.

 

Step 3: Apply the “inappbrowser” approach (part 1).

The next question is how to apply this to your downloaded content? Because of AngularJS, there are two issues:

  • JavaScript is “sanitized” (i.e. not rendered) by ng-bind-html. Otherwise, we would be able to write a filter to replace our ordinary Anchor tags with tags that invoke the javascript you see above.
  • I tried directives, but since I don’t have control over the HTML being passed in, that proved more complicated.  Depending on your circumstances, a directive may work for you.

I finally arrived at a two step process. First, use a simple custom filter to add ‘class=”ex-link”‘ to each section where we want the links to open in the external browser.

.filter('externalLinks', function() {
   return function(text) {
     return String(text).replace(/href=/gm, "class=\"ex-link\" href=");
   }
 })

The above filter would need to be more sophisticated if there are classes within the HTML that need to be preserved. Applying the filter in AngularJS, your HTML now looks like this:

<div ng-bind-html="content.Description | externalLinks"></div>

Step 3: Apply the “inappbrowser” approach (part 2).

Finally, we address the question of how to force our links to open in the _system window using JavaScript. For this we’ll resort to JQuery. I generally like to keep JQuery out of my projects, but in my case it was already included for a third-party tool. If anyone knows of a “pure Angular” approach to this, I’m all ears.

Using $timeout ensures that this handler is one of the last applied to the page.

$timeout(function () {
   $('.ex-link').click(function () {
     var url = $(this).attr('href');
     window.open(encodeURI(url), '_system', 'location=yes');
     return false;
   })
})

You might place the above code into the controller of the page that needs it, or in a factory containing utility functions. Don’t forget to inject $timeout in the appropriate place.

This two-step approach has advantages: obviously the “ex-link” class can be added directly to a template’s HTML, allowing you to place an “external link” anywhere in your app, and distinguish from your app’s internal links.

Of course there are some risks to displaying foreign HTML in a Cordova app. For my project, I have been assured that the HTML will be correct, but of course if it’s not, my app could break. So down the road I may add HTML integrity checking, but in the Agile tradition, I will only address this if it’s a real issue.

Comments
  1. 3 years ago
  2. 3 years ago
  3. 3 years ago
    • 2 years ago
  4. 2 years ago
    • 2 years ago
  5. 2 years ago
  6. 2 years ago
  7. 1 year ago
  8. 1 year ago
  9. 12 months ago

Leave a Reply

Your email address will not be published. Required fields are marked *