Skip to main content
Ben Nadel at the New York ColdFusion User Group (Apr. 2008) with: Dmitriy Goltseker
Ben Nadel at the New York ColdFusion User Group (Apr. 2008) with: Dmitriy Goltseker

Proof-Of-Concept: Injecting Features Into An InVision Share Link Experience Using AngularJS 1.6.7

By on

About a year-and-a-half ago, I put together a proof-of-concept for a Screen Flow feature that I thought might be of interest at InVision. Unfortunately, in the last 20-months, I've been unable to generate any interest in the concept. However, I still think that there may be one or two users out there that are interested in this idea. So, as a follow-up experiment, I wanted to see if there was a way that I could inject the Screen Flow feature into the InVision runtime without having to mess with the product itself. As it turns out, AngularJS 1.6.7 added a method called .loadNewModules() that allows developers to augment the Angular app Injector after bootstrapping. Before applying this to Screen Flow, I wanted to try a simple proof-of-concept to see how .loadNewModules() even works.

View this code in my JavaScript Demos project on GitHub.

Seven years ago, I did a little bit of experimentation with lazy-loading components into an AngularJS application. But, in that experiment, it required that the application developers add special glue-code that would allow the AngularJS injector to be populated even after the application had been bootstrapped. In this new experiment, however, I don't want the existing AngularJS application to know anything about what is about to happen.

Thankfully, in a Google search, I came across this gist by Felix Mosheev. In said gist, Felix pointed out that AngularJS 1.6.7 added a new method, .loadNewModules(), which allows new modules to be added to the AngularJS injector. I had never seen this method before, so I figured it was worth a little research-and-development.

For this proof-of-concept, all I'm going to do is define a new Directive that has a template and a Controller. I'm going to use the .loadNewModules() method to pull this Directive into the runtime. And then, I'm going to use the $compile() function in order to "bootstrap" and inject this Directive into an active Share link experience.

Here's the remote JavaScript file that contains this logic:

(function( ng ) {

	var moduleName = ng.module( "LazyLoadPOC", [ "InVision" ] )
		.directive(
			"lazyPoc",
			function LazyPocDirective( fakeCssModuleCompiler ) {

				// The fakeCssModuleCompiler is a Service defined in the existing
				// application. This will make sure that this lazy-injected directive can
				// make use of the existing application aspects.
				return({
					compile: fakeCssModuleCompiler( "c-lazy" ),
					controller: "lazyPoc.Controller",
					controllerAs: "vm",
					restrict: "E",
					scope: true,
					template: (
						"<div style='background: white ; color: black ; padding: 10px 15px ; position: fixed ; z-index: 999 ;'>" +
							"URL: {{ vm.url }}" +
						"</div>"
					)
				});

			}
		)
		.controller(
			"lazyPoc.Controller",
			function LazyController( $location, $scope ) {

				var vm = this;

				vm.url = $location.url();

				// Listen for changes in the URL so that we can make sure that change-
				// detection digests are being applied to this injected element.
				$scope.$on(
					"$locationChangeSuccess",
					function handleLocationChange() {

						vm.url = $location.url();

					}
				);

			}
		)
		.run(
			function init( $compile, $rootScope, config ) {

				console.group( "Lazy Load POC" );
				console.log( "Initializing run blocks." );
				console.log( "Prototype:", config.project.name );
				console.log( "Share Key:", config.share.key );
				console.groupEnd();

				// Compile, inject, and integrate the test component with the existing
				// AngularJS application.
				$compile( "<lazy-poc></lazy-poc>" )(
					$rootScope.$new(),
					function injectClone( ngElement, scope ) {

						document.body.appendChild( ngElement[ 0 ] );
						scope.$applyAsync();

					}
				);

			}
		)
		.name
	;

	// As of AngularJS 1.6.7, we can use .loadNewModules() to lazy-load modules into an
	// AngularJS application. Huge shout-out to Felix Mosheev for pointing me in this
	// direction with his demo gist:
	// --
	// https://gist.github.com/felixmosh/088657e92390d7450c94216f62ddc738
	ng
		.element( document )
		.injector()
		.loadNewModules( [ moduleName ] )
	;

})( angular );

This "feature" does two things:

  1. It pulls-in the fakeCssModuleCompiler() service from the existing application. This function allows scoped CSS modules to be hacked into AngularJS. And, I'm using it to test whether or not I can depend on existing Share services.

  2. It echoes the $location.url() value in the DOM. I'm using this to make sure that I can plug into the AngularJS change-detection digest of the existing application.

Notice that at the end, I'm grabbing the AngularJS injector for the running application and then I'm loading the new feature module. I put this feature.js file up on a small GitHub repository so that I could host it using GitHub Pages.

With that file in place, I then hand-crafted a tiny bookmarklet that uses jQuery - which is already loaded in AngularJS - and its .getScript() function to pull that feature.js file into the currently-rendered webpage:

javascript:(function(w,$,k){(w[k]||(w[k]=$.getScript("https://bennadel.github.io/poc-invision-share-lazy-load/feature.js?_="+Date.now())));})(window,jQuery,"LazyLoadPOC");void(0);

Now, if I load up an InVision Share link and trigger the bookmarklet, we get the following Browser output:

A proof-of-concept being injected into an InVision share link using AngularJS.

As you can see, we were able to compile and inject the Directive defined in the lazy-loaded module using the Bookmarklet. And, as we navigate around the Share experience, we can see that the Directive hooks into the AngularJS change-detection digest and updates to reflect the current URL.

Awesome sauce!

So, it seems that - at least in theory - I could use the .loadNewModules() function in AngularJS 1.6.7 to inject custom features into an InVision Share link. Of course, there's a massive difference between a proof-of-concept and fully-functioning and user-friendly feature. But, it definitely gives my brain a whole lot of fat to chew on!

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

Reader Comments

9 Comments

Why hasn't this been upgraded beyond Angular 1.x? Not cynical here, but with the belief that there are always tradeoffs are we waiting for a compelling reason to update, just not needed or what is the thinking?

15,155 Comments

@John,

Valid question -- just a lot of code and not enough people to actually focus on the upgrade. We've been on AngularJS 1.x for 8-years; so, you can imagine how much legacy code is in place. I am not sure we'll ever be able to focus on upgrading to modern Angular :( :( :(

Post A Comment — I'd Love To Hear From You!

Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.