Search code examples
jqueryangularfroala

In Angular 2, how to approach consuming plugin that is documented in and written for jquery?


My current situation is this..

I am installing and configuring Froala which has a dependency on and is documented for jquery.

I know server side much better than JS/Angular. And I know its best practice to avoid using jquery in Angular.

My current approach when this situation arises is to:

What is the Angular way to configure and consume plugins like this? Is this a situation where following the docs and using jquery to configure is Ok? Such as the following from Extended Config above:

  ngOnInit () {
    $.FroalaEditor.DefineIcon('alert', {NAME: 'info'});
    $.FroalaEditor.RegisterCommand('alert', {
      title: 'Hello',
      focus: false,
      undo: false,
      refreshAfterCallback: false,

      callback: function () {
        alert('Hello!');
      }
    });
  }

Edit 1: In response to comment.

  • Its best practice to wrap jquery plugins in an Angular component. - This gives me a good jumping off point to research and google more.

Solution

  • I ultimately approached this by:

    • Abstracting into a new component.
    • Incorporated ControlValueAccessor, although I am not entirely clear on all benefits of this approach.
    • Created an interface for the weakly typed options. I scraped these from their docs site.
    • And on an as needed basis, will add @Input and @Output when I need to configure a setting.

    I ultimately came up with the following:

    declare var $: any;
    
    @Component({
      selector: 'froala-component',
      template: `
        <div #froala [froalaEditor]="froalaOptions" (froalaModelChange)="onChange($event)" [(froalaModel)]="froalaModel"></div>
       `,
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => FroalaEditorComponent),
          multi: true
        }
      ]
    })
    export class FroalaEditorComponent implements ControlValueAccessor, OnInit {
    
      @ViewChild('froala') froalaEditor: ElementRef;
    
      @Input() froalaModel;
      @Output() private froalaModelChange = new EventEmitter();
    
      @Input() set height(value: number) {
        this.froalaOptions.height = value;
      }
    
      @Input() set width(value: string) {
        this.froalaOptions.width = value;
      }
    
      public froalaOptions: FroalaOptions = {};
    
      ngOnInit() {
        $ = $(this.froalaEditor.nativeElement);
      }
    
      // implements ControlValueAccessor Interface
      public registerOnChange(fn: (_: any) => void): void {
        this.onChange = fn;
      }
    
      public registerOnTouched(fn: () => void): void {
        this.onTouched = fn;
      }
    
      public writeValue(content: any): void {
        this.froalaModel = content;
      }
    
      // event handlers
      public onChange(x) {
        this.froalaModel = x;
        this.froalaModelChange.emit(this.froalaModel);
      }
    
      public onTouched() { }
    
      // froala methods - https://www.froala.com/wysiwyg-editor/docs/methods
      public toggleFullScreen() {
        $.froalaEditor('fullscreen.toggle');
      }
    }
    

    I added an interface scraped from their options page to get intellisense for options like so:

    export interface FroalaOptions {
      aviaryKey?: Boolean; /* ADOBE_CREATIVE_CLOUD_KEY */
      aviaryOptions?: Object; /* {
        displayImageSize: true,
        theme: 'minimum'
      } */
      charCounterCount?: Boolean; /* TRUE */
      charCounterMax?: Number; /* -1 */
      etc...
    }
    

    And on an as needed basis will add an @Input for or @Output when consuming. So currently I can use like so:

    <froala-component [(froalaModel)]="model.HTML" height=850></froala-component>