Skip to main content
Ben Nadel at RIA Unleashed (Nov. 2009) with: Dee Sadler
Ben Nadel at RIA Unleashed (Nov. 2009) with: Dee Sadler

An Experiment In What React's JSX Might Feel Like In AngularJS

By
Published in Comments (5)

The other week, I took my first look at ReactJS. While I believe that ReactJS and AngularJS do the same thing (in terms of the View), I'm trying to see what kind of cross-pollination of ideas I can generate. I already took a look at using a .setState() method in AngularJS. Now, today, I want to see what React's JSX might look like in AngularJS.

Run this demo in my JavaScript Demos project on GitHub.

As a ReactJS noob, I'm sure that I am greatly over-simplifying this; but, to me, JSX is basically "HTML in your JavaScript." In the context of ReactJS, this means that the HTML compiles down to React's virtual-DOM (Document Object Model) syntax. But, in AngularJS, since there is no virtual-DOM, I'm going to manifest this as HTML in the JavaScript.

NOTE: While AngularJS doesn't have a virtual-DOM, per say, it accomplishes the same exact thing using $watch() bindings. As such, you can think of AngularJS and ReactJS as using very similar approaches when it comes to dirty data checking.

In JSX, there is no demarcation of the HTML - it just sits there, commingled with the JavaScript. This requires a fairly intelligent parser - one much more robust than what I wanted to create in this little thought experiment. As such, I've chosen to use the triple-back-tick as a wrapper for the embedded HTML. It's fairly unobtrusive and is unlikely to be confused with something in the embedded HTML.

In the following code, notice that my "Hello World" component view is embedded directly in the JavaScript using this extended Template syntax. Although, to be fair - it's not really "JavaScript," it's [what I am calling] "NGX," which is parsed pre-bootstrapping of the AngularJS application:

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />

	<title>
		An Experiment In What React's JSX Might Feel Like In AngularJS
	</title>

	<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body>

	<h1>
		An Experiment In What React's JSX Might Feel Like In AngularJS
	</h1>

	<div bn:hello-world>
		Woot!
	</div>


	<!--
		Load scripts.
		--
		NOTE: Our main script block isn't a JavaScript block - it's a "text/ngx" block.
		The ngx.js file will defer the bootstrapping of the AngularJS application until
		the ngx content has been parsed and re-injected into the Head as valid JavaScript.
	-->
	<script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.16.min.js"></script>
	<script type="text/javascript" src="./ngx.js"></script>
	<script type="text/ngx">

		// Create an application module for our demo.
		angular.module( "Demo", [] );


		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //


		angular.module( "Demo" ).directive(
			"bnHelloWorld",
			function( $log ) {

				// Return the directive definition object.
				return({
					controller: Controller,
					controllerAs: "vm",
					link: link,
					restrict: "A",
					transclude: true,
					scope: true,
					template:
					```
						<div class="container">
							<div class="header">

								<h2>
									HTML <em>in</em> Your JavaScript?!
								</h2>

							</div>
							<div class="content" ng-transclude>

								<!-- Transcluded content will appear here. -->

							</div>
							<div ng-click="vm.cycleQuote()" class="footer">

								<strong>Inspiration</strong>: {{ vm.quote }}

							</div>
						</div>
					```
				});


				// I control the HelloWorld component.
				function Controller( $scope ) {

					var vm = this;

					var quoteIndex = 0;
					var quotes = [
						"Asphinctersayswhat?",
						"Ah, Nuprin. Little. Yellow. Different.",
						"As you can see, it sucks as it cuts.",
						"If you're gonna spew, spew into this.",
						"Party on Wayne. Party on Garth.",
						"Calgon - ancient Chinese secret.",
						"I don't even own A gun, let alone the many guns that would necessitate a rack.",
						"Game on!"
					];

					vm.quote = quotes[ quoteIndex ];

					// Expose the public API.
					vm.cycleQuote = cycleQuote;


					// ---
					// PUBLIC METHODS.
					// ---


					// I move on to the next quote in the collection.
					function cycleQuote() {

						if ( ++quoteIndex >= quotes.length ) {

							quoteIndex = 0;

						}

						vm.quote = quotes[ quoteIndex ];

					}

				}


				// I bind the JavaScript events to the view-model of the component.
				function link( scope, element, attributes ) {

					element.mouseenter(
						function handlerMouseenterEvent( event ) {

							$log.info( "Moused into component." );

						}
					);

					var footer = element.find( "div.footer" )
						.hover(
							function hoverOver() {

								footer.addClass( "active" );

							},
							function hoverOut() {

								footer.removeClass( "active" );

							}
						)
					;

				}

			}
		);

	</script>

</body>
</html>

Since AngularJS automatically bootstraps the application on DOM-ready, we have to defer bootstrapping until we've had a chance to find, parse, and replace the NGX script tags with valid JavaScript script tags. In order to do this, the ngx.js file will strip the "ng-app" attribute out of the HTML tag. This will stop AngularJS from performing implicit bootstrapping. It will then find, parse, and convert the "text/ngx" blocks into valid "text/javascript" blocks. And, once this is done, it will manually bootstrap the AngularJS application using the module name identified in the original "ng-app" attribute.

(function configureNgx( $, ng ) {

	// Locate the root of the application. This demo assumes that there is only
	// one AngularJS application on the page and that is uses the ng-app syntax.
	var bootstrapElement = $( "*[ ng-app ]" );

	// Get the application module name - we'll need this to manually bootstrap
	// the application after the page loads.
	var moduleName = bootstrapElement.attr( "ng-app" );

	// Remove the ng-app attribute so that AngularJS doesn't automatically
	// bootstrap the application before we've had a chance to parse our NGX scripts.
	bootstrapElement.removeAttr( "ng-app" );

	// Once the DOM is ready, find the inline NGX scripts and convert them to JavaScript.
	$( findAndParseNgxBlocks );


	// ---
	// PRIVATE METHODS.
	// ---


	// I find all of the NGX block and convert them into JavaScript Script tags that get
	// injected back into the page (where they are executed). Once this is done, the
	// AngularJS application is bootstrapped.
	function findAndParseNgxBlocks() {

		// Convert all the NGX blocks to active Script elements.
		$( "script[ type = 'text/ngx' ]" )
			.remove()
			.map(
				function convertToScript() {

					var script = document.createElement( "script" );
					script.type = "text/javascript";
					script.text = parseNgx( this.innerHTML );

					return( script );

				}
			)
			.appendTo( "head" )
		;

		// Once the script tags are all converted, bootstrap the AngularJS application.
		ng.bootstrap( document, [ moduleName ] );

	}


	// I parse the given NGX content into valid JavaScript content. For this demo, this
	// consist of finding the "```" (triple back-tick) delimited values and converting
	// them into single-lines of quoted text.
	function parseNgx( ngxContent ) {

		var templatePattern = /```([\w\W]*?)```/g;

		var jsContent = ngxContent.replace(
			templatePattern,
			function( $0, template ) {

				// Remove leading and trailing spaces from each line.
				template = template.replace( /^\s+|\s$/gm, "" );

				// Escape any embedded double-quotes.
				template = template.replace( /(")/g, "\\$1" );

				// Replace line-returns with spaces.
				template = template.replace( /[\r\n]+/g, " " );

				// Quote the entire template value.
				return( "\"" + $.trim( template ) + "\"" );

			}
		);

		return( jsContent );

	}

})( jQuery, angular );

Really, this code isn't doing much more than finding the triple-back-tick substrings and replacing them with single-line strings. In fact, if you inspect the source of the generated Script tag, you can see that it created a one-line string for the template:

What React's JSX might look like in AngularJS as NGX.

Having the View markup embedded in the JavaScript is certainly an interesting concept. I think, with a small component, it is quite nice. But, as a component gets larger, I suspect it might be nicer to have the View in a separate file so that you can easily flip back-and-forth between the View and the View-Model (switching IDE tabs requires a smaller mental model than scrolling up and down to related parts of the same file). Of course, I'm sure people would argue that having smaller components is better anyway; which, it probably is. But, you also don't want to create arbitrarily small components - that's just needless complexity.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

At first JSX looks like inline templates, but is more than that.

JSX builds `elements`, which are first class.
Similar to functions in javascript you can assign them to a variable, pass them as argument, etc.

var infoTab = <Tab title={<Icon src="img/info.png" />}>...body content...</Tab>

This gives React a lot of programatic freedom compared to templates.

Ps. ReactElements are used to build the vDOM and are not ReactComponents yet.

15,902 Comments

@Bob,

Good explanation. From what I read in the documentation, all the JSX stuff gets compiled down into React.createElement() type calls, which is what you are saying, I think. In AngularJS, since there is no virtual DOM, per say (it uses $watch() bindings), the best I could do was just treat this like inline templates. It's definitely a really interesting concept.

15,902 Comments

@Chris,

Thanks, I'll take a look. I've actually had this article open in another tab for a few weeks and have not yet read it :( Some days just feel way too hectic!

1 Comments

Great and imaginative.. always love programmatic generation of front-end elements. Usually use angular's $compile to render real-time markup from service/factory objects but this might be another alternative!

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel