The question is how one can achieve similar
"thing" in Angular 2+ to that I managed to do in AngularJS. What the
"thing" is I will describe below.
Question descriptionSo the process that I created in AngularJS can be described as follows:
- We want to inject a custom dynamic content into a page, let's call it a plugin
- That content is not defined in the web-client, instead it is being retrieved from a server in a structured form
That is we receive from the server
html and
js files that are necessary for the plugin to function and we load and display on the page. On the client I create an AngularJs directive to hold the necessary logic if the plugin.
Here is the code example for javascript plugin code:
() => {
return {
templateUrl: 'directive-template-name',
restrict: "E",
controller: ['$scope', ... /* other dependecies */, function ($scope, ...) {
/* directive code */
}]
};
}
Here is the code example for plugin loader code:
// evaluate js function and compile directive
var diretiveFactory = eval(content.jsFile);
// AngularJS CompileProvider
CompileProvider.directive.apply(null, [content.name, diretiveFactory]);
// append html code to element using angular
var wrapper = angular.element("#" + wrapperName);
wrapper.html(requestedDirective[0].htmlContent);
// initialize dynamic content
var wrapperScope = wrapper.scope();
this.$compile(wrapper)(wrapperScope);
As one can see, this method allows to load a valid javascript and
"compile" it into a AngularJS directive and then display it on the page at will.
Angular 2+The problem with new angular is that concept of the directive, as far as I understand, is replace by a concept of component. Moreover, all client-side code is now written in typescript and transpiled into javascript for the client's browser. As far as I understand, all dependencies are being resolved in the traspilation process.
That creates some problems that prevent from creating dynamic plugins for Angular 2+:
- A do not know how to inject dependencies into a dynamically created component as I do not know what exactly do I need when the transpilation happens.
- Let's say I want several components to co-exist simultaneously (for example a parent dynamic components that holds children componets) and be able to communicate, in the case of AngularJs I could solve this problem by using scope variable. In case of Angular 2+ components are isolated so how to I go about this problem.
- Moreover, I do not know how one can go about creating ViewContainerRef to hold the dynamically created component in evaluated javascript code. (See code examples below)
I will provide code examples of I got so far below. Content loader typescript code:
//some imports
import { Component, ComponentRef, Compiler, Injector, NgModule, NgModuleRef, ViewContainerRef} from '@angular/core';
// plugin content structure
class DynamicPageModel {
name: string;
htmlFile: string;
jsFile: string;
}
export class DynamicContentService {
constructor(
private _compiler: Compiler,
private _injector: Injector,
private _m: NgModuleRef<any>
) { }
public LoadDynamicContent(container: ViewContainerRef, content: DynamicPageModel) {
// evaluate js function and compile component
var tmpCmp = Component({
template: content.htmlFile,
selector: content.name
})(eval(content.jsFile)());
var tmpModule = NgModule({declarations: [tmpCmp as any]})(class { });
var factories = this._compiler.compileModuleAndAllComponentsSync(tmpModule)
var f = factories.componentFactories[0];
var cmpRef = f.create(this._injector, [], null, this._m);
// insert component into the container
container.insert(cmpRef.hostView);
}
}
For reference to what is going on in this piece of code, I got it from
here.
Plugin example javascript code:
() => {
function dynamicMain() {
this.componentName = 'Dynamic Example Main';
}
return dynamicExampleMain;
}
As one can see, this is quite simple dynamic plugin that is unable to use any services or other components as it is unable to inject them into itself.
Expected answer
As an answer to my question I would like to hear a solution(s) to the problems I encountered when trying to create dynamic plugins for the Angular 2+.
If you know a better approach to creating dynamic plugins (remember: they are retrieved from a server) at run time in Angular 2+, I would like to hear it too.