Skip to content

Wrapper libraries: Authoring

Ben Grynhaus edited this page Mar 17, 2019 · 1 revision

Authoring a wrapper library is pretty straightforward, and you can reference other ones, especially @angular-react/fabric.

The basic steps are as follows:

  1. Create a standard Angular library (ng generate library my-lib)

  2. Add @angular-react/core to your peerDependencies.

  3. For each component in the underlying UI library, as a set of components (that go together), create an NgModule as follows (we're using "my" as the prefix in this example):

    import { registerElement } from '@angular-react/core';
    import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
    import { MyCounter } from 'my-react-ui-library';
    import { MyCounterComponent } from './counter.component';
    
    const components = [MyCounterComponent];
    
    @NgModule({
      declarations: components,
      exports: components,
      schemas: [NO_ERRORS_SCHEMA],
    })
    export class MyCounterModule {
      constructor() {
        registerElement('MyCounter', () => MyCounter);
      }
    }

    And the accompanying component:

    import {
      ChangeDetectionStrategy,
      ChangeDetectorRef,
      Component,
      ElementRef,
      Input,
      OnInit,
      Renderer2,
      ViewChild,
    } from '@angular/core';
    import {
      InputRendererOptions,
      JsxRenderFunc,
      ReactWrapperComponent,
    } from '@angular-react/core';
    import { MyCounterProps } from 'my-react-ui-library';
    
    @Component({
      selector: 'my-counter',
      exportAs: 'myCounter',
      template: `
        <Counter
          #reactNode
          [count]="count"
          [RenderCount]="renderCount && onRenderCount"
        >
        </Counter>
      `,
      styles: ['react-renderer'],
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class FabBreadcrumbComponent
      extends ReactWrapperComponent<MyCounterProps>
      implements OnInit {
      @ViewChild('reactNode') protected reactNodeRef: ElementRef;
    
      @Input() count?: MyCounterProps['count'];
      @Input() renderCount?: InputRendererOptions<MyCountRenderProps>;
    
      @Output() readonly onIncrement = new EventEmitter<{ count: number }>();
    
      onRenderCount: (
        props?: MyCountRenderProps,
        defaultRender?: JsxRenderFunc<MyCountRenderProps>
      ) => JSX.Element;
    
      constructor(
        elementRef: ElementRef,
        changeDetectorRef: ChangeDetectorRef,
        renderer: Renderer2
      ) {
        super(elementRef, changeDetectorRef, renderer, { setHostDisplay: true });
      }
    
      ngOnInit() {
        this.onRenderCount = this.createRenderPropHandler(this.renderCount);
      }
    }

    For further explanation see ReactWrapperComponent, as well as the inline-documentation in the code.

Conventions

  • Pick a prefix for your wrapper library, and use it in your modules, components (including selectors) etc.
    • e.g. @angular-react/fabric uses "fab" as the prefix, meaning that every NgModule and @Component is prefixed with Fab*, and every selector is prefixed with fab-*
  • extend ReactWrapperComponent in your components, to gain the benefits of things handled in @angular-react/core
  • See more conventions in ReactWrapperComponent

Templates

Wrapper component templates are a hybrid between Angular and React. To be more specific, they're Angular templates, with React component type names as their elements. See more details on this in component registry.

In addition, the Angular compiler prevents some stuff from being defined as Inputs, namely - you can't have @Inputs prefixed with on:

Binding to event property 'onFoo' is disallowed for security reasons, please use (Foo)=... If 'onFoo' is a directive input, make sure the directive is imported by the current module.Angular

This error, and the suggested workaround are not applicable in our use-case, since they do need to be inputs (for example, function that return a value, or render props). Therefore, as a convention - any Input which is PascalCased is prefixed with on when inheriting from ReactWrapperComponent.

In addition - there's one reserved prop name that can't be used - key, since it's already being used by React, and we leverage that in more ways that usual internally.

Lastly, when extend-ing ReactWrapperComponent there are a few more restrictions, see there for more details.