Angular: Creating plugins for 3rd party packages (libraries)
Asked Answered
G

1

14

I created Angular Library (ngx-wig) and I would like to provide an ability to extend its functionality by using plugins.

What would be the best place to declare plugin in Angular? (may be something like myLibModule.forRoot(..)) and what type of instance should be plugin itself?

I solved same issue for AngularJs just by adding module for each plugin in which I register plugin by using configProvider of main module. Don't really like this solution because plugin registers itself, but it should be responsibility of applications where library is used.

UPDATE: related issue is opened on github here.

Garofalo answered 20/6, 2017 at 13:31 Comment(3)
It seems a little bit unclear. Do you want to provide an API like service?Oarsman
it depends on plugin. some of them could extend toolbar with new buttonGarofalo
Good question. I was facing a similar problem in a modular music production system, and the solution was that a "surface part" of each plugin must adhere to an interface which defines the plugin assembly, e.g. key-metadata about the plugin, such as name, key-classes, etc., and then the inner parts of the plugin itself work through decorator-hooks placed on methods (such as @nodeDidActivate, @nodeStateDidChange, etc.). This system uses React as its UI-layer, but it shouldn't be that different in ng.Godmother
B
2

I think you can provide users to use component as a plug-in. This component has to extends you abstract base plugin component.

For example clear-styles plugin could look like

@Component({
  selector: `nw-clear-styles-button`,
  template: `
    <button (click)="clearStyles($event)" 
        [disabled]="editMode || disabled" 
        class="nw-button clear-styles" title="Clear Styles">
      Clear Styles
    </button>`
})
export class NwClearStylesButtonComponent extends Ng2WigPluginComponent {
  constructor() {
    super();
  }

  clearStyles() {
    const div = document.createElement('div');
    div.innerHTML = this.content;
    this.contentChange.emit(div.textContent);
  }
}

format plugin

@Component({
  selector: `nw-formats-button`,
  template: `
    <select class="nw-select"
            [(ngModel)]="format"
            (ngModelChange)="execCommand('formatblock', format.value)"
            [disabled]="editMode || disabled">
      <option *ngFor="let format of formats" [ngValue]="format">{{ format.name }}</option>
    </select>
  `
})
export class NwFormatButtonComponent extends Ng2WigPluginComponent {
  formats = [
    {name: 'Normal text', value: '<p>'},
    {name: 'Header 1', value: '<h1>'},
    {name: 'Header 2', value: '<h2>'},
    {name: 'Header 3', value: '<h3>'}
  ];

  format = this.formats[0];

  constructor() {
    super();
  }
}

where Ng2WigPluginComponent is abstract base class provided by your library:

export abstract class Ng2WigPluginComponent {
  execCommand: Function;
  editMode: boolean;
  content: string;

  editModelChange: EventEmitter<boolean> = new EventEmitter();
  contentChange: EventEmitter<string> = new EventEmitter();
}

So users can easily use declared in base class properties.

To register such plugins we can use mentioned by you forRoot method. For that you need to

1) configure you library module like follows:

ng2wig.module.ts

@NgModule({
 ...
})
export class Ng2WigModule {
  static forRoot(entryComponents: CustomButton[]) {
    return {
      ngModule: Ng2WigModule,
      providers: [
        Ng2WigToolbarService,
        {provide: NG_WIG_CUSTOM_BUTTONS, useValue: entryComponents},
        {provide: ANALYZE_FOR_ENTRY_COMPONENTS, multi: true, useValue: entryComponents},
      ]
    };
  }
}

where

  • NG_WIG_CUSTOM_BUTTONS is your global library token to recognize provided plugins inside library

ng2wig-toolbar.service.ts

@Injectable()
export class Ng2WigToolbarService {
  constructor(@Optional() @Inject(NG_WIG_CUSTOM_BUTTONS) customButtons: CustomButton[]) {
    if (customButtons) {
      customButtons.forEach(plugin => this.addCustomButton(plugin.pluginName, plugin.component));
    }
  }
  • ANALYZE_FOR_ENTRY_COMPONENTS is angular global token to be able to load plugins dynamically

2) Declare NwClearStylesButtonComponent in declarations array of your AppModule module

3) Pass it to the Ng2WigModule.forRoot method

Ng2WigModule.forRoot([
   { pluginName: 'clear-styles', component: NwClearStylesButtonComponent },
   { pluginName: 'format', component: NwFormatButtonComponent }
]) 

And then main you task will be to dynamically generate your component by using ComponentFactoryResolver and ViewContainerRef (see ng2wig-plugin.directive.ts in plunker below)

Plunker Example

Beryl answered 26/6, 2017 at 8:56 Comment(1)
thank you, your answer is great! I have not provided bounty before to motivate others to share their answersGarofalo

© 2022 - 2024 — McMap. All rights reserved.