Angular 4 Case Study: Caution about Binding HTML Content using [innerHTML]
By Rich Franzmeier
What is the problem with binding HTML content using [innerHTML]? A valid question coming from someone who’s done it without any problems in the past so before starting the article, a definition of that is in order. The best way of defining it is to describe the problem I ran into on my project. I’m on a project where we are converting an Angular 1 application into an Angular 4 application. The Angular 1 application was defining constants for an error message page on the server and then downloading those constants to the client. Some of those constants had HTML in them.
- Links with telephone numbers in them with binding for Google analytics
- Links that did navigation (for example, to the home page or login page) — also with binding for Google analytics
- Note that the navigation was done using <a href=’/home’>Home</a>
These constants with HTML in them were then loaded into the message page (I’m talking after conversion to Angular 4) and shown to the user with this code:
The safeHtml pipe allows us to bring in HTML as outlined here. The ‘message’ is a component variable that would be passed into the message component by using one of the aforementioned constants.
So when I say “binding HTML content using [innerHTML]” I mean HTML that has Angular code either retrieved from the server in some way or even used from a constants file on the client that is loaded into an Angular component via binding to the innerHTML property.
What’s the Problem?
The preceding approach has a number of issues:
- Any of the HTML with Angular specific things like binding or directives, etc. don’t work because it’s not supported by Angular 2+
- The navigation links (href) will reload the Angular 4 application from the server, which is not desirable (insert understatement here) for a single page application
- Need to use routerLink instead of href in the anchor (a) tag
- However, see the first problem in this list 🙂
Solution to this problem?
Attempts at a solution to this problem:
- The first thing I did was change “href” to “routerLink” (a necessity to use the Angular router and not do a full page re-load), no problem right?
- Nope, doesn’t work because, as already stated, you can’t put Angular-specific things in the HTML
2. The next thing I did was try to use dynamic component loading as outline here, got it now, right?
- Nope, doesn’t work because I still am loading the message using binding
Ok, at this point after doing a lot of searching and discussion with teammates, I realized that this is the wrong approach. Unless something changes in Angular, it doesn’t appear like there is a good solution for this.
The following outlines why our team came to the conclusion that this was poor design:
- When you put HTML code on the server in constants or in the database, it makes changes/conversion difficult because it is not where a developer who is not on the original team expects it to be
- In fact, we didn’t find it until we thought everything was code complete
- Client code belongs on the client
2. If you have to add a constant on the server, you may not realize the implications in the Angular application
- Let’s say you have to route to the home page in a constant, you may add routerLink=’/home’ or href=’/home’, both of which don’t work properly (routerLink doesn’t work at all)
3. Angular 1 had an answer for binding to HTML like this — the $compile service, Angular 2+ has no answer
- Most likely this is a best practice change on the Angular team — closing a loop hole that caused problems
- It should be a code smell when you have to do something the framework doesn’t support
How Should This Be Done Then?
I’ll assume that if you are reading this post, you are interested in how we actually solved this problem. Well, it’s simple, we stopped binding to the innerHTML property of our p tag for the error message page and did the following instead:
- Pulled down all of the constants that were defined on the server into the Angular application
- For any constant that had run-time HTML in it, created a separate component that basically displayed the message with that HTML in it
- Directly linked to this separate component instead of using a common page that displayed error messages using a ‘title’ and ‘message’ approach
- The common page has the link ‘/message’
- The custom pages have the link ‘/message/custom/<message-description>’ where <message-description> is text you add like ‘session-timeout’
- No more need for constants with HTML in them so deleted those
- Kept the common error message page for scenarios that didn’t involve HTML in the error message
Binding Angular-laden HTML to the innerHTML property as I’ve outlined in this article isn’t supported in Angular 2+ applications and should prompt a change in requirements. Instead, opt for using components that display the HTML.
Originally published at www.intertech.com on December 6, 2017.