How can I implement the host-plugin pattern for a module?


#1

#Introduction
I’ve been looking for information about how to implement a host-plugin pattern which in this case are services implementing an interface. This task (information gathering) has been quite difficult for me since English is not my mother lang and I think I’m not using the right “query”.

Having said that, I’m wondering if you guys know how can I implement this pattern using typescript being compiled with ES5.

The Context

What I’m dong is a module which exposes authentication services and components. The problem is that I want to support different authentication back-ends (Firebase, IonicCloud) which are supposed to be plugins implementing an interface but I can’t realize how to work with optional dependencies and how to let the developer to choose which implementation he wants to use by just giving the library name and a configuration objet. May I force the developer to provide the instantiated service to the module through a forRoot method?

Thank you for your support!


#2

I was trying to use npm optional dependencies to manage authentication back-ends (Plugins), but I haven’t found any information about how that is done (Importing, Instantiating the Service). So I’ve decided to use the “host” package to hold all the code and forget about using the host-plugin pattern, since it doesn’t seem to be possible for Ionic.

BTW, if you know any documentation I can read about how to implement this for Ionic, please let me know.

Thank you


#3

Googling for

doesn’t find any relevant results. Are you sure this is the correct name for what you are trying to do?


#4

Well, the actual name is Plugin Pattern. I use that word to emphasize that what I’m having troubles with is defining the host, since the plugin itself as a package uses peerDependencies.

This is an example from npm:

try {
  var foo = require('foo')
  var fooVersion = require('foo/package.json').version
} catch (er) {
  foo = null
}
if ( notGoodFooVersion(fooVersion) ) {
  foo = null
}

// .. then later in your program ..

if (foo) {
  foo.doFooThings()
}

I found this about Optional Module Loading and I haven’t tested it yet, but the examples doesn’t talk about compiling with ES5, which seems to not support this feature and is not clear for me how to implement the code, I mean, is it possible to do something like this?:

@NgModule (/* ... */)
export class myModule {
    public static forRoot (favoriteLib : string, configuration : any) {
        let libService : LibServiceInterface;
        switch (favoriteLib) {
        case 'lib1':
            import { LibServiceFactory } from 'say-we-use-a-prefix-here-lib1';
            break;
        case 'lib2':
            import { LibServiceFactory } from 'say-we-use-a-prefix-here-lib2';
            break;
        }
        libService = LibServiceFactory (configuration); // Seems like a compilation error...

        return {
            ngModule: myModule,
            providers: [{provide: LibServiceInterface, useValue: libService}]
        }
    }
}

#5

I thought the entire point of interfaces is that the consumer (your plugin in this case) is not supposed to know about the concrete implementation of them.

I would look at the forRoot method of IonicModule. Its first argument (the app component) is similar in function to your LibServiceInterface. The host, not the plugin, should be responsible for deciding the concrete instantiation of LibServiceInterface and passing its class constructor to your module’s forRoot() method.


#6

Well, maybe I’m dong something wrong here. What I’m doing is defining the interface in the host package and letting the plugin to implement it, since in this case the host is going to consume the plugin (The plugin here is a service for authentication).

Thank you, this helps me a bit. Now I know that provide is a token for the injector, and an interface is not a valid token. Perhaps I have to give InjectionToken a try. Now it seems like I have to inject the factory method instead of the instantiated class, but his still not solve my doubt, how can I import the factory method conditionally?


#7

Yeah, I still think this is backwards. The host needs to provide the implementation of LibServiceInterface to the plugin.


#8

As much as I read as much as I think you are not totally right. The host provides services to the plugin, of course, but the plugin has to implement the interface defined by the host, at least this is what I get from:

BTW, I think is pretty clear what I want to achieve, an authentication module (with components and stuff) which supports many authentication back-ends through plugable services (implementing those back-ends). So the “host” is responsible of defining the authentication service interface, and the “plugin” is responsible of implementing this interface. Every single “plugin” is supposed to be a separated project (Let’s say library) and dynamically loaded by the “host”

Is it possible to implement such pattern in Ionic+Angular+TypeScript+ES5? and where can I get some guidance?

(@rapropos), I know you are doing the best for helping me, and I appreciate it, but discussing whether the name of the pattern is Pluggin Pattern or not isn’t the subject of this post and I don’t want to be mean, but I invite you to focus on the actual problem, If you want to help, of course.


#9

I forgot to mention that the module will be a package by itself, so this “Plugins”(Libraries) implementing the AuthenticationServiceInterface will be optionally required by the host package and manually installed by the developer, this way we let him to use his/her preferred backend just requesting some configuration or even letting him to implement his/her own authentication backend plugin.


#10

I’m not talking about pattern names. I’m trying to improve your proposed architecture.

The plugin needs an object that implements LibServiceInterface to function.

You are proposing passing a string from the host to the plugin, having the plugin take that string as a discriminator, and then having the plugin be responsible for instantiating the implementation of LibServiceInterface. That is backwards from how Angular’s DI works.

I am proposing passing an implementation instead of a string. That way the plugin does not have to do anything more than IonicModule does:

forRoot(libService: any): ModuleWithProviders {
  return {
    providers: [ {provide: LibServiceInterface, useValue: libService} ]
  };
}
  

#11

I’ve just realized that I’m not implementing Inversion of Control (IoC) here… The code shall be something like this:

AuthenticationService (authenticaiton.service.ts):

/* Some imports here... */
/* You may be wondering: why are you using an abstract class here?,
well, interfaces cannot be used as tokens for dependency injection... */
@injectable()
export abstract class AuthenticationService {
    /* Some abstract functions defining the interface*/
}

AuthenticationModule (authentication.module.ts):

/* some imports here... */
@NgModule (/* ... */)
export class AuthenticationModule {
    public static forRoot (authenticationServiceFactory: (configuration : any) => AuthenticationService,
                           configuration : any) : ModuleWithProviders {
        let authenticationService  = authenticationServiceFactory (configuration);
        return {
            ngModule: AuthenticationModule,
            providers: [{
                provide: AuthenticationService,
                useVaue: authenticaitonService
            }]
        }
    }
    /* Some code here... */
}

It seems to me a little bit clear now, tell me guys what do you think about this implementation and how could I improve it. Of course, this package will be Open Source and I will publish the code as soon as possible.

Thanks for your help.


#12

Hmmm…, seems like I didn’t explain the thing very well… some times it is difficult to me to explain what I want in Spanish… When trying in English the thing gets even worse. Sorry for that @rapropos and I’m still so thankfully for your effort.

The thing is that the code proposed doesn’t belong to the plugin but to the host. Remember that in this particular case the “host” is the (ionic) module and the “plugin” is the authentication service which will be exposed by the module and injected into any component requiring it, not only into the AuthenticationModule but any part of the application since this module will be imported into the AppRoot. Please take a look at my last post giving a code example and tell me what you think about it.

May I request for the service implementation instead of the factory method? remember that the code importing and running the instantiation of the service will be at app.module.ts


#13

Considering that you have asked me in passive-aggressive fashion to not make further comments about theory, I think I will reserve further comment until you have a concrete implementation that you wish input on.