How to use Angular Web Components in React JS

How to use Angular Web Components in React JS

This article is about how you can use Angular components in your React project and Vice Versa. First let's discuss what is a Web Component? A Web Component is a set of different technologies or frameworks allowing you to create reusable custom elements — although its functionality is written somewhere else, you can utilise them anywhere in your code, especially when re-usability is the concern. This will help you while migrating one framework to another or one tech stack to another. If you don’t want to write all the code once again, you can use web components. There will be two parts of this article. In the first part we will see how to implement the Angular web component to a React project and in the second part we will learn How to use React Web Components in Angular projects. So, without a further ado, let's start...

1. We start with creating a fresh Angular Project

I am using a sample app to show the flow of implementing an angular component to a react application. So first we should create an angular application.

ng new angular-element

2. Then we create a component that we want to use as a Web Component in our React app.

ng g c form

3. Add FormComponent to the entryComponents of app.module.ts

import { FormComponent } from './form/form.component';

@NgModule({
  declarations: [
    AppComponent,
    FormComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  entryComponents: [FormComponent],
  bootstrap: [AppComponent],
})
export class AppModule {...}

4. Set selector name in your FormComponent - form.component.ts

@Component({
    selector: 'app-form',
    templateUrl: './form.component.html',
    styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {...}

Note: I created my project with routing that is why I am using

5. To see FormComponent from AppComponent, you can also write <app-form></app-form> to see the template if you are not using routing in your dummy angular app

import { FormComponent } from './form/form.component';

const routes: Routes = [
  {
    path: '',
    component: FormComponent,
    pathMatch: 'full'
  }
];

6. app.component.html

<router-outlet></router-outlet>

or add FormComponent to the app.component.html if you don't want to use routing.

<app-form></app-form>

7. Add a basic form in form.component.ts then create the form in TS and add business logic.

export class FormComponent implements OnInit {
    myForm!: FormGroup;

    constructor() {}

    ngOnInit(): void {
      this.myForm = new FormGroup({
        name: new FormControl(''),
        email: new FormControl(''),
      })
    }

    submitForm(): void {
      console.log(this.myForm.value);
    }

}

8. form.component.html

<form [formGroup]="myForm" (ngSubmit)="submitForm(myForm)">
    <input type="text" formControlName="name">
    <input type="email" formControlName="email">
    <button type="submit">Submit</button>
</form>

Now, our form component is ready, but I want some extra features like, I want the data of the name field to be pre-populate with the help of the React Application where I am gonna use this Angular Web Component and I don’t want to handle the form submission in the Angular Component, let React handle it.

export class FormComponent implements OnInit {
    myForm!: FormGroup;
    @Input() name !: String;
    @Output() formSubmit = new EventEmitter();

    constructor() {}

    ngOnInit(): void {
        this.myForm = new FormGroup({
            name: new FormControl(this.name),
            email: new FormControl(''),
        })
    }

    submitForm({ value, valid } : { value: any, valid: any }): void {
        if (valid) {
            this.formSubmit.next(value);
        }
    }

}

So we need to specify the name of the input field with the help of @Input() decorator to pre-populate the data through React application, and we need to add @Output() decorator to handle the submission using React application. We are almost there, let’s build the Web Component now...

npm i @angular/elements

9.app.module.ts

import { createCustomElement } from '@angular/elements';

define injector for creating custom element
export class AppModule {
    constructor(private injector: Injector) {
      const el = createCustomElement(FormComponent, {injector: this.injector});
      customElements.define("app-form" , el);
    }
}

If you get any error like saying Failed to create: HTMLElement error while running this code in your browser then you need to install @webcomponents/webcomponentsjs

npm i @webcomponents/webcomponentsjs

and define it to polyfills.ts

import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js';

1633103720.gif !!!IMPORTANT: We will also remove AppComponent as a bootstrap component from AppModule and add ngDoBootstap which tells Angular for auto bootstrapping this module.

@NgModule({
    declarations: [...],
    imports: [...],
    providers: [],
    entryComponents: [FormComponent],
    // bootstrap: [AppComponent],
})
export class AppModule {
    constructor(private injector: Injector) {
      const el = createCustomElement(FormComponent, {injector: this.injector});
      customElements.define("app-form" , el);
    }
    ngDoBootstrap() {}
}

Now it's time to build our scripts add this line to package.json

"build": "ng build --configuration production --output-hashing none && cat dist/angular-element/runtime.js dist/angular-element/polyfills.js dist/angular-element/main.js > distributable/angular-element.js"

Run npm build rather than ng build

10. Now it's time to add our angular component to React application

in public/index.html

<script src="./angular-element.js"></script>

Integrate Component at App.js like this

class App extends Component {
    componentDidMount() {
      this.component.addEventListener('feedbackSubmit', this.onFeedbackSubmit);
    }

    componentWillUnmount() {
      this.component.removeEventListener('feedbackSubmit', this.onFeedbackSubmit);
    }

    onFeedbackSubmit(event) {
      console.log('Got', event.detail);
    }

    handleRef = component => {
      this.component = component;
    };

    render() {
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>Angular in React</p>
          </header>

          <app-form name="Susmita" ref={this.handleRef} ></app-form>

        </div>
      );
    }
  }

If you are seeing any such error like Error:base href not found in your dev tool while running the react application in browser add <base href=""> to your index.html.

1633104062.gif That is it. Thank you, kind reader! Happy hacking! 😊

giphy.gif