Towards content-driven Angular apps

An implementation proposal for dynamically rendering component trees, plus a concept for embracing hypermedia-driven content browsing in Angular.

David
Sparkles Blog
8 min readMay 8, 2017

--

Angular’s web-application platform is built around the concept of components. Components’ HTML templates are written by developers and compiled at build-time into a static asset.

A major challenge is to render dynamic web content at run-time. Dynamic content can be text, images, videos, etc. that is delivered by a content-management-system (CMS). This implies that content changes frequently whereas the application’s HTML, CSS, and JavaScript code is a static build artefact. A solution to this challenge is especially interesting for integrating an Angular frontend with a headless CMS — one of the hot topics around.

A simple demo app that will be discussed throughout the stories. Content will be dynamically rendered from a JSON source file.

This story describes some naive approaches to render web content in Angular, thus demonstrating the need for a more flexible solution. It proposes an alternative solution that dynamically renders components and represents a content structure in a tree structure of components.

We will also discuss some background knowledge of hypermedia and how this approach will eventually be scaled-out to enable hypermedia-driven, rich content browsing.

Angular content challenge

First, let’s look at two naive, simple approaches for rendering dynamic content in an Angular app.

Naive approach #1: innerHTML

The first naive approach is to use property bindings with [innerHTML]. This needs SafeHtml parsing and introduces a potential security hole. It should certainly not be used with user-generated content.

Also, since [innerHTML] binding values are treated as raw strings, it is not possible to create child components or child directives.

https://github.com/angular/angular/blob/master/aio/src/app/layout/doc-viewer/doc-viewer.component.ts#L80-L90

The upcoming angular.io app uses this pattern and needs to implement some hacks with direct access to DOM APIs in order to embed child components.

Naive approach #2: templates with switch/case cascades

https://angular.io/docs/ts/latest/cookbook/dynamic-form.html#!/%23form-component

A second solution are templates with cascades of[ngSwitch and *ngSwitchCase that select the appropriate component.

In this pattern — which is similar to a solution suggested by the Dynamic Forms Guide — the parent templates “switches” between child components.

The drawback is that the parent template is hard-wired and needs to know every possible child component. A developer is required to update templates for every component that is added. Assume that anImageGalleryComponent, is added to an existing page, then templates needs to be hand-wired in every place where an image gallery can be displayed.

Alternative idea

The motivation for this story is a general-purpose approach in which content documents drive an Angular application. The idea is that an “out-of-band source” describes content and its structure and that an Angular app will react to that content description and render a component tree.

Concept visualization: content and its tree structure is described in a generic format (left) and rendered dynamically to a tree of Angular components (right)

The term “out-of-band source” implies that the content document comes from an external source and is — in the first place — unrelated to the Angular app. It could be a JSON document from a headless CMS. There is no coupling or only loose coupling between the JSON document and Angular components/HTML templates.

Out of that follow three points:

First, a piece of content in a JSON document is rendered by an Angular component. There must be some sort of mapping and it should be relatively easy to extend that mapping.

Second, values in a content document prescribe what is rendered on the screen. Values need to be passed from JSON to a component instance.

Third, most important, structure and links are described in JSON. There are parent-child relations and hyperlinks in content. Both need to be rendered by the Angular application. It must be possible to embed child components into a designated container of a parent component.

In summary, we represent content in a tree structure, the content tree is transformed into a tree of components, and component instances are dynamically rendered.

Skeleton code for implementation

The fundamental idea for this approach based on Dynamic Component Loading, as described in the official Angular guides.

The central API is ComponentFactory that lets us create component instances from code, attach them to host elements in the DOM, and also call change detection cycles.

Notice that we are going to resolve ComponentFactory by a mapping here. With arbitrary content coming in, we need the content to be descriptive about itself. Take as an example, that content of type "image"should be rendered by ImageComponent, we need a mapping from stringvalues to components’Type<any>, where the component type is a reference to the constructor function.

Then, there must be a host element in the DOM before creating and attaching a component instance in one step withcomponentFactory.create(). Host elements will be created by ViewRef and ViewContainerRef APIs in the actual implementation. Both are higher-level APIs than Renderer and take care of setting-up data structures properly. The creation of the host element is explicitly shown in the above code listing for clarification and learning purposes.

Finally, the newly created component is going to receive some input values bypassContentValues(component, content) and will be rendered to screen by invoking a change detection cycle. The input values could be the text of a headline or the source URL of an image, or anything else the component wants to render to screen.

Advantages

The conceptual advantage is that we introduce a flexible content-to-component mapping. When scaled-out to a general-purpose library, it will be relatively easy to add new types of content and components.

Now, we should try to achieve Inversion of Control by two measures.

First, dependency injection. The mapping should rather be owned by a concrete application than the library. With Angular’s DI system, we can do so with@Inject(MAPPING) and an application will simply provide its content-to-component map as an object literal value.

Second, delegation. We should try to delegate the handling of input properties to a lifecycle hook, e.g. a common interface ContentOnCreate, similar to Angular’s built-in lifecycle hooks. By implementing these two concepts, an application developer is able to add, remove, and change components without touching the general-purpose library.

Disadvantages

As mentioned before, the flexible content-to-component mapping is the biggest advantage, yet it also turns out to be the weakest link of this concept.

Every type of content requires a corresponding component, which will eventually become very fine-grained. Say, one wants to implement a grid system, it will likely result in a GridComponent, a RawComponent, and a ColumnComponent to render the grid. That are three implementation components for one visual building block. The same could apply for tables with rows and columns. We will see this disadvantage in the follow-up story to this one.

Hosting content in Angular

The missing piece is a host component that serves as an entry point to content-driven rendering. The component takes a document as @Input and creates component instances “on-the-fly”.

A ViewContainerRef serves as the anchor for dynamic child components. When the input document changes, it will be necessary to clean-up previous child components and render a new tree of child components.

A mental model of ‘content’

When we speak of content on web pages we usually talk about the term hypermedia. To give us a good understanding of the term, let’s recall some of our academic education and clarify the concepts that drive the web.

Hypermedia is a term that was crafted by people like Vannevar Bush, Douglas Engelbart, and T.H. Nelson. Wikipedia, the Cambridge Dictionary, and german Duden each have their own slightly divergent definition of the term. For me, the essentials are summarized by a Ted Nelson quote:

“By Hypertext I mean nonsequential writing — text that branches and allows choice to the reader, best read at an interactive screen.” — Ted Nelson

Though Ted Nelson only talks about writing and text, hypermedia includes numbers of media formats such as text, images, video, audio, etc. Then, thinking of branches means there is a tree structure, thus hypermedia is a tree structure of multi-media content.

Hypermedia escapes the prison of paper

Last, we need to talk about the term nonsequential: with printed books we are parented to follow order. Paper prescribes a rigid linear structure. You have one page on the left, and a second page on the right. Then you flick the paper and advance two pages forward, and, again, you see one page on the left, a second on the right.

Hypermedia escapes the prison of paper. Links allow jump-navigation from one piece of content to another. Links can be to another document, to a piece of supplementary content within the same document, or real deep links, e.g. to a video starting at a certain playback position.

Nonsequential hypermedia is all about linkage. Cross-referencing from one piece of content to another piece of content. It can be a link to another web page, or the odd “jump to top” link that scrolls-up the viewport. The first is an inter-document link. The latter is an intra-document link.

A real hypermedia link is also some sort of deep link, connecting diverse types of content. Here is an example: a passage of text jumping straight into video footage, starting playback at 3m 0s.

The road ahead

With that model of hypermedia in mind, the challenge for a content-driven Angular app becomes:

Map a tree of hypermedia content to a tree of Angular components — allow linking and jumping between them.

A first implementation proposal was shown above. In addition, that implementation will need to support parent-child relations and hyperlinks.

Child components need to be embedded into parents. Parent components need to provide a view slot or rendering outlet, then children are created in that designated place.

A piece of content — and its component — needs to be identifiable. Identifiers will be used as anchors or jump targets.

Components will need an API for rendering hyperlinks and one for explicitly triggering a navigation jump — like the routing framework does with [routerLink] and router.navigate(..).

Next: a proof-of-concept implementation

To demonstrate that a content-driven hypermedia application does work in Angular, it’s best to implement a proof-of-concept app, showing that those ideas are indeed working. I will do so in the near future!

Later on, it will be possible to cut-out a general-purpose, re-usable library, so end developers are able to build on top of that.

Various applications are going to benefit from a content-driven approach, e.g. newspapers, publication sites, blogs, marketing material of a web shop, and many many more pages featuring editorial content. Integration with a content-management system in general or a headless CMS empowers developers to build rich, content-driven Angular apps.

In the follow-up story, I will demonstrate a proof-of-concept implementation and open-source it on GitHub. So stay tuned! If you enjoyed reading, spread the word! ♡ ❤ 💜 💝

References

T. H. Nelson. 1965. Complex information processing: a file structure for the complex, the changing and the indeterminate. In Proceedings of the 1965 20th national conference (ACM ‘65), Lewis Winner (Ed.). ACM, New York, NY, USA, 84–100. DOI: 10.1145/800197.806036

Douglas Engelbart. 1963. A Conceptual Framework for the Augmentation of Man’s Intellect.

Douglas Engelbart, 1968, Demonstration of the online system NLS. Demo Video.

Vannevar Bush. 1945. As We May Think. The Atlantic. July 1945 Issue.

--

--