Skip to content

Wrapper libraries: Helper components: Disguise

Ben Grynhaus edited this page Mar 19, 2019 · 2 revisions

Some React components have a declarative parent-child relationship (like Compound Components).

While this is not an issue in itself, sometimes this relationship is very strict, such that only components at the first level after the parent can be valid children of it. for example:

function Tabs() { return <div className="tabs">...</div> }

class Tab() {
    render() { return <div className="tab">...</div> }
}

// Valid
<Tabs>
    <Tab label="First tab">
        <div>first tab content</div>
    </Tab>

    <Tab label="Second tab" active>
        <div>second tab content</div>
    </Tab>
</Tabs>

// Invalid (in a strict relationship)
<Tabs>
    <div>
        <Tab label="First tab">
            <div>first tab content</div>
        </Tab>
    </div>

    <div>
        <Tab label="Second tab" active>
            <div>second tab content</div>
        </Tab>
    </div>
</Tabs>

In the above example the divs wrapping the Tab components don't make a lot of sense, but when you look how at a naive approach when using Angular-React would work, you'd get this in the DOM:

<fab-tabs>
  <div class="tabs">
    <fab-tab>
      <div class="tab">...</div>
    </fab-tab>

    <fab-tab>
      <div class="tab">...</div>
    </fab-tab>
  </div>
</fab-tabs>

Which wouldn't work if the internal implementation of Tabs is strict:

props.children.filter(child => child instanceof Tab);

For these case the Disguise component was created - which accepts all the relevant inputs and renders things in React rather than in Angular, as much as possible:

interface DisguiseProps {
  /**
   * The type to render the root component as.
   * @default React.Fragment
   */
  disguiseRootAs?: React.ReactType;

  /**
   * The type to render the child components as.
   * @default the Children's own type.
   */
  disguiseChildrenAs?: React.ReactType;

  /**
   * The Angular child components to render.
   */
  ngChildComponents?: ReactWrapperComponent<any>[];
}

This allows exposing the API as described above to the wrapper component's users (app), while the internal implementation is a bit more complex.

Two of the components that use these come from the Fabric package:

  • Link - Since it accepts an (optional) as input (exposed as linkAs in <fab-link>, due to Angular compiler restrictions)
  • Pivot - For the exact reasons described above - it has a strict implementation, not allowing putting any React elements between the Pivot and PivotItem components (besides React.Fragment, which Disguise leverages).

passProp decorator

Decorator to specify a property that should be passed as a prop to the underlying React component implicitly. Mainly useful for passing child components using the Disguise component.

When using the Disguise component to render strict parent-children relationships, you still need to somehow pass Inputs passed to the child wrapper components to the actual child React elements that will be created within Disguise. The passProp decorator solves this, and allows specifying an implicit way to pass down props to these elements.