angulartinymceassetsstorybookangular-formly

Get storybook to use local assets in a node_module for a story of a component with a tinymce editor?


I am writing an Angular 15 Application making use of the formly-package to generate forms for me. Formly provides most fields to render inside of its forms out of the box, but not a field with a rich text-editor. I wanted to use tinymce for that.

My goal is to bundle tinymce together with my angular application in order to not need to use their CDN. To do so as defined in section 7 of the tinmyce docs you just need to add the node_modules folder to the assets section of your angular.json file to add them to your bundle:

...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "assets": [
              "src/favicon.ico",
              "src/assets",
              { "glob": "**/*", "input": "node_modules/tinymce", "output": "/tinymce/" },
            ],
            ...
          }
...

Now I want to have a storybook story that also loads the local assets instead of fetching them from the tinymce CDN. That is where I'm struggling, how do I do this? The storybook docs mention that just importing them in the stories.ts file should suffice.

You can import any media assets by importing (or requiring) them. It works out of the box with our default config.

However, that does not appear to work as far as I understand it. What is the problem?

Find below the explicit component and storybook file with which I am trying to do this:

# formly-field-editor.component.ts
import { Component } from '@angular/core';
import { FieldType, FieldTypeConfig } from '@ngx-formly/core';

@Component({
  selector: 'app-formly-editor-field',
  templateUrl: './formly-editor-field.component.html',
  styleUrls: ['./formly-editor-field.component.scss'],
})
export class FormlyEditorFieldComponent extends FieldType<FieldTypeConfig>{
  settings = {
    plugins: [
        'advlist autolink lists link image charmap anchor',
        'searchreplace visualblocks fullscreen media',
        'table paste help wordcount'
    ],
    toolbar: [
        'undo redo | formatselect | bold italic underline strikethrough subscript superscript link unlink blockquote | backcolor forecolor hilitecolor fontsizeselect |',
        'alignleft aligncenter alignright | bullist numlist outdent indent | removeformat | table help'
    ],
    skin: 'oxide-dark',
    content_css: 'dark',
    browser_spellcheck: true,
    menubar: false,
    height: 500,
    convert_urls: false,
    relative_urls: false,
    branding: false,
    base_url: '/tinymce',
    suffix: '.min'
  };
}
# formly-field-editor.component.html
<label [for]="to['name']">{{to.label}}</label>
<editor 
  name="fieldName"
  #textEditorElement
  [formControl]="formControl"
  [formlyAttributes]="field"
  [init]="settings"
></editor>

And here the storybook file with which I am trying to use this:

import { forwardRef } from '@angular/core';
import { FormGroup, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { FormlyBootstrapModule } from '@ngx-formly/bootstrap';
import { FormlyModule } from '@ngx-formly/core';
import { Meta, StoryFn, componentWrapperDecorator, moduleMetadata } from '@storybook/angular';
import { EditorModule } from '@tinymce/tinymce-angular';
import { FormlyEditorFieldComponent } from './formly-editor-field.component';


export default {
  title: 'DesignSystem/Organisms/FormlyEditorFieldComponent',
  component: FormlyEditorFieldComponent,
  args: {
    form: new FormGroup({}),
    model: {},
    options: {},
    fields: [
      {
        key: 'text',
        type: 'text-editor',
      },
    ],
  },
  decorators: [
    moduleMetadata({
      declarations: [
        FormlyEditorFieldComponent
      ],
      imports: [
        ReactiveFormsModule,
        FormlyBootstrapModule,
        EditorModule,
        FormlyModule.forRoot({
          types: [
            { name: 'text-editor', component: FormlyEditorFieldComponent }
          ]
        }),
      ],
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => FormlyEditorFieldComponent),
          multi: true
        }
      ],
    }),
    componentWrapperDecorator(() => `
      <form [formGroup]="form">
        <formly-form 
          [model]="model" 
          [fields]="fields" 
          [options]="options" 
          [form]="form"
        ></formly-form>
      </form>
    `),
  ],
} as Meta<FormlyEditorFieldComponent>;

const Template: StoryFn<FormlyEditorFieldComponent> = (args: FormlyEditorFieldComponent) => ({ 
  props: {
    ...args,
  },
});

export const Default = Template.bind({});
Default.args = {}

This will lead to tinmce.min.js still being loaded from the cloud (or at least attempted to): enter image description here


Solution

  • JSmith's comment lead me to the answer. It really is as simple as just importing the tinymce module, but the import can not be done the angular way as storybook doesn't work particularly well with it.

    So the imports should look like this:

    ... many other import statements...
    import { EditorModule } from '@tinymce/tinymce-angular';
    import * as all from 'tinymce/tinymce';
    import { FormlyEditorFieldComponent } from './formly-editor-field.component';
    
    const sth = all; //Needed so that the import of "tinymce/tinymce" does not get auto removed by my IDE
    
    export default {
      ...
    

    With this, storybook will recognize the import location and load assets from there automatically.