DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Fabian Andres Cano
Fabian Andres Cano

Posted on

Working with Nx.dev using React, Angular and native web components (Part 2 of 9)

A complementary tutorial on how to work with Nx.dev using web components and not die trying.

Code available in GitHub

Find me on Twitter as @Fabian0007

Angular App and web components libraries created with Reactβ€” Part #2 of 9

In this second part of the tutorial, we will work with the Angular application
created in the first part and this time we will create a web component library
using React. The first part of this tutorial is here.

Creating a web component library using React

We will start by adding the React capabilities, then we will create the first
web component library using React in our project:

npm i @nrwl/react

npm run nx g @nrwl/react:lib header-lib

Adding to the Angular app

The first thing that comes to mind is to add the import in
/apps/angularapp/src/main.ts:

import '@tutorial/header-lib';

Now we will run:

npm start angular app --watch

An error will appear on our console:

ERROR in libs/header-lib/src/index.ts:1:15 β€” error TS6142: Module β€˜./lib/header-lib’ was resolved to β€˜C:/Users/FCano/Documents/personal-tutorials/test-continuing/tutorial/libs/header-lib/src/lib/header-lib.tsx’, but β€˜ β€” jsx’ is not set.

1 export * from β€˜./lib/header-lib’;

We need to enable the jsx option. TypeScript ships with three JSX modes:
preserve, react, and react-native. These modes only affect the emit stage - type checking is unaffected. The preserve mode will keep the JSX as part of the output to be further consumed by another transform step (e.g.
Babel). Additionally the output will have a .jsx file
extension. The react mode will emit React.createElement, does not need to go through a JSX transformation before use, and the output will have a .js file extension. The react-native mode is the equivalent of preserve in that it keeps all JSX, but the output will instead have a .js file extension[1]. For our case we will use the reactoption. you must add the code marked in bold to /apps/angularapp/src/tsconfig.json:

{

    "extends": "../../tsconfig.json",

    "compilerOptions": {

        "types": ["node", "jest"],



    }

}

An new error will appear on our console:

ERROR in libs/header-lib/src/lib/header-lib.tsx:1:19 β€” error TS2307: Cannot find module β€˜react’.

1 import React from β€˜react’;

This is very simple, without having React installed, how are we going to work
with a React application? An alternative to not having to install it would be to
compile our library from the outside and add it as a resource, but that would
complicate updating this library and that is not the idea of ​​a monorepo. We
will install React and react-dom(Since we are working with a web application, we need to install the glue between react and the DOM) by executing the following command:

npm install react react-dom

And of course we need to install the types for these packages since we are using Typecript.

npm install β€” save-dev @types/react @types/react-dom

The error will disapper and we will replace the file
/apps/angularapp/src/app/app.component.html with:

<header-lib></header-lib>

<main></main>

<template id="template-test" [innerHTML]="template"> </template>

<footer-lib [attr.creator]="creator"></footer-lib>


Expected result

Our header library will not be shown, and it is obvious, we are calling a web
component that does not exist, what we have for now is a React library, to make it a web component we have two options each with its own advantages and disadvantages.

Do it yourself

React and Web
Components
are built to solve different problems. Web Components provide strong encapsulation for reusable components, while React provides a declarative library that keeps the DOM in sync with your data. The two goals are complementary. As a developer, you are free to use React in your Web Components, or to use Web Components in React, or both[2]. Our need at the moment is to encapsulate the react library that we already have in a web component.

First we will change the name of /libs/footer-lib /src/lib/header-lib.tsx to
ReactHeader.tsx and we will replace the content with:

import * as React from β€˜react’;

import β€˜./header-lib.css’;

/* eslint-disable-next-line */

export interface ReactHeaderProps {}

  export const ReactHeader = (props: ReactHeaderProps) => {

    return (

      <div>

        <h1>Welcome to header-lib component!</h1>

      </div>

    );

};

export default ReactHeader;

Now we will create on the same folder the file header-lib.tsx and will replace
with:

import * as React from 'react';

import * as ReactDOM from 'react-dom';

import { ReactHeader } from './ReactHeader';

export class HeaderLib extends HTMLElement {

  public mountPoint: HTMLDivElement;

  connectedCallback() {

    this.mountReactApp();

  }

  disconnectedCallback() {

    ReactDOM.unmountComponentAtNode(this.mountPoint);

  }

  mountReactApp() {

    if (!this.mountPoint) {

      this.mountPoint = document.createElement('div');

      this.appendChild(this.mountPoint);

    }

    ReactDOM.render(<ReactHeader />, this.mountPoint);

  }

}

customElements.define('header-lib', HeaderLib);

Here we are using the life cycle of the web components (connectedCallback and disconnectedCallback) and ReactDOM for render a React component over a web component.


Expected result

Our component not is using the ShadowDOM(see the first part of this tutorial), so the h1 label is centered. To use it we must add the constructor and we will attach a shadow DOM tree to the web component β€œthis.attachShadow({ mode: β€˜open’})”, it must be open because we need to access to shadowRoot property of the web component, then we must to append the div element to the shadowRoot property instead of over the web component.

constructor() {

  super();

  this.attachShadow({ mode: 'open' })

}

this.appendChild(this.mountPoint); -> this.shadowRoot.appendChild(this.mountPoint);


Expected result

Now if we want to add the style, we need to add it as a inline style, because
the shadowDOM doesnt we permit to use the external css, we can to use
style-it for insert the css directly
in the DOM, but we need to be able to export the css as a module, so I invite
you to review this on your own.

import * as React from 'react';

import './header-lib.css';

/* eslint-disable-next-line */

export interface ReactHeaderProps {}

export const ReactHeader = (props: ReactHeaderProps) => {

  return (









    </div>

  );

};

export default ReactHeader;

Now if we want to pass a parameter, the first is update the React component:

import * as React from 'react';

import './header-lib.css';

/* eslint-disable-next-line */

export interface ReactHeaderProps {

  creator: string

}

export const ReactHeader = ({ creator }: ReactHeaderProps) => {

  return (

    <div style={display: 'flex', alignItems: 'center', justifyContent: 'center'}>

      <img

        alt="Nx logo"

        width="75"

        src="https://nx.dev/assets/images/nx-logo-white.svg"

      />

      <h1>Header {creator}</h1>

    </div>

  );

};

export default ReactHeader;

In header-lib.tsx we must add the attributeChangedCallback lifecycle to get the attribute β€œcreator” and pass it to the mountReactApp function where we will pass that value to the React component.

import * as React from 'react';

import * as ReactDOM from 'react-dom';

import { ReactHeader } from './ReactHeader';

export class HeaderLib extends HTMLElement {

  public mountPoint: HTMLDivElement;

  public static observedAttributes = ['creator'];

  constructor() {

    super();

    this.attachShadow({ mode: 'open' })

  }

  connectedCallback() {

    this.mountReactApp('');

  }

  attributeChangedCallback() {

    const creator: string = this.getAttribute('creator');

   this.mountReactApp(creator);

  }

  disconnectedCallback() {

    ReactDOM.unmountComponentAtNode(this.mountPoint);

  }

  mountReactApp(creator: string) {

    if (!this.mountPoint) {

      this.mountPoint = document.createElement('div');

      this.shadowRoot.appendChild(this.mountPoint);

    }

   ReactDOM.render(<ReactHeader creator={creator}/>,   this.mountPoint);

  }
}

customElements.define('header-lib', HeaderLib);

Finally we will need to update the app.component.html in angularApp:

<header-lib [attr.creator]="creator"></header-lib>

<main></main>

<template id="template-test" [innerHTML]="template"> </template>

<footer-lib [attr.creator]="creator"></footer-lib>


Expect result

The fast way

We will create a new library for test this way:

npm run nx g @nrwl/react:lib header-fast-lib

We will install these dependencies:

npm i prop-types
npm i react-to-webcomponent

We will create ReactHeader en lib of header-fast-lib:

import * as React from 'react';

import * as PropTypes from 'prop-types';

/* eslint-disable-next-line */

export interface ReactHeaderProps {

  creator: string

}

export const ReactHeader = ({ creator }: ReactHeaderProps) => {

  return (

    <div style={display: 'flex', alignItems: 'center', justifyContent: 'center'}>

      <img

        alt="Nx logo"

        width="75"

        src="https://nx.dev/assets/images/nx-logo-white.svg"

      />
      <h1>Header {creator}</h1>

    </div>

  );

};

ReactHeader.propTypes = { creator: PropTypes.string };

export default ReactHeader;

The only change here regarding header-lib is β€œReactHeader.propTypes = { creator: PropTypes.string };” because react-to-webcomponent need it. Now inside header-fast-lib.tsx we will write:

import * as ReactDOM from 'react-dom';

import * as React from 'react';

import reactToWebComponent from 'react-to-webcomponent';

import { ReactHeader } from './ReactHeader';

export const webcomponent = reactToWebComponent(ReactHeader, React, ReactDOM);

customElements.define('header-fast-lib', webcomponent);

Inside main.ts of angularApp we will include the library and in
app.component.html we will use instead of :

import '@tutorial/header-fast-lib';

<header-fast-lib [attr.creator]="creator"></header-fast-lib>


Expected result

As we have seen working web components with React is not complicated, and most of the things we knew from the previous tutorial where we work with native web components are applied, in the next part of this tutorial we will work with web components built with Angular, see you soon.

Referencias

[1]
https://www.typescriptlang.org/docs/handbook/jsx.html

[2]
https://en.reactjs.org/docs/web-components.html

Top comments (0)

Take Your Github Repository To The Next Level

Take Your Github Repository To The Next Level πŸš€οΈ: A step-by-step guide on creating the perfect Github repository.