1

I am working on a MEAN stack app that features plugins. In the server app I managed to load the plugins files using fs.lstatSync() and stat.isDirectory(). I also managed to expose to the public each plugin's /public directory using app.use( pluginPublicPath, express.static( pluginServerPath ));.

Now I have to inject each plugin's main-service.ts inside the angular app. I am using standard Typescript Angular 2 services as illustrated bellow. App classes are imported with system.js, nothing fancy.

Project structure

project
└─── plugins
|    └─── my-plugin
|    |    └─── server // Some server stuff, all ok here
|    |    └─── public // Managed to expose it in the browser
|    |         └─── main.ts
|    |         └─── ...
|    └─── some-plugin
|    |     └─── server
|    |     └─── public
|    └─── another-plugin
|          └─── server
|          └─── public
└─── server // The server app
|    └─── ...
└─── public // The public angular app, nothing special, the usual files
|    └─── services
|    |    └─── foo-service.ts
|    |    └─── ...
|    └─── app.component.ts
|    └─── main-module.ts
|    └─── ...
└─── server.js
└─── package.json
└─── ... 

foo-service.ts

// Static file, known and fixed path, no trouble here
import { Injectable } from '@angular/core';
import { AnotherService } from './another-service'

@Injectable()
export class MyService {
    constructor( private _anotherService: AnotherService ) {}
}

I can inject the plugin's main-service.ts in app.component.ts or in foo-service.ts by typing it directly in the constructor() method.

import { MyPluginMainService } from '../../plugins/my-plugin/public/main-service'
export class MyService {
    constructor( private _myPluginMainService : MyPluginMainService ) {}
}

What I want to achieve is to import the main-service.ts file from each plugin dynamically. Basically I want to have a plugin framework that loads the plugins by convention, not by configuration. I pursued this design in order to avoid changing core files when adding a new plugin in the project. Similarly to how a CMS detects automatically a plugin.

I was expecting to do this by injecting the plugin services from a list generated by the server. The server app already knows which plugins are available and enabled. How should I proceed to inject dynamically plugins/some-plugin/main-service.ts? I could export from the server through an api/modules endpoint a list of file names and file paths required to bootstrap the public app. Also a sample of code covering this situation for components would be welcomed. Thank you!

app.components.ts - My wild guess...

modules.forEach( module => import { module.name } from module.path );
export class MyService {
    constructor( ??? ) {}
}

Also my wild guess for main-module.ts

listOfProviders = [];

modules.forEach( module => 
    listOfProviders.push( import { module.name } from module.path );
);

@NgModule({
    imports: ...
    declarations: ...
    providers: listOfProviders,
    bootstrap: [ AppComponent ]
})
Adrian Moisa
  • 3,923
  • 7
  • 41
  • 66
  • 2
    How are these things `import { module.name } from module.path` supposed to be workable? `import` should be static. It may be `System.import` or `require`, depending on the environment. As for injectable, it is `constructor(private injector: Injector) {}`, it's just not clear how the decisions on which 'dynamic' service should be injected are made. – Estus Flask Sep 18 '16 at 14:31
  • To put it more simply: How can the server command, from a set of arbitrary services, which ones are injected into the frontend app? – Adrian Moisa Sep 18 '16 at 14:40
  • There's not enough information on how the app and plugins are organized. Are they built with Webpack or anything else? What's the convention for plugin exports? Is there something that prevents you from building and bootstrapping the app with all known plugins and controlling their use within app (via router, etc)? – Estus Flask Sep 18 '16 at 15:05
  • I have edited the question with further details. Thank you! – Adrian Moisa Sep 18 '16 at 15:59
  • I would suggest to use `System.import` instead of `import` to load them dynamically (see [this example](http://stackoverflow.com/a/39029435/3731501)). It may be convenient to identify providers as strings (`{ provide: 'PluginName', useClass: PluginName })` to make their injection easier (`inject.get('PluginName')`). Any way, it doesn't look like a good design to me, just because it is hard-coded to SystemJS (which is just slow). – Estus Flask Sep 18 '16 at 16:36
  • I will give it a try, but first I need to study a bit. I don't fully understand everything you suggested. I'll be back after I finish testing if I'm still in trouble. – Adrian Moisa Sep 18 '16 at 16:44
  • Sure. Hope the comments helped. – Estus Flask Sep 18 '16 at 16:56

0 Answers0