Angular Elements
Since Angular 6, the team released Angular Elements
which allows you to bootstrap Angular components within an existing Angular application by registering them as Custom Components.
This opens a whole lot of opportunities to web developers who are used to argue about superiority of Angular vs React. Some people believe Angular is suitable for developing enterprise applications, while others prefer React because of it's speed and bundle size. Meanwhile, Angular team has been working hard to bring this feature into V6 release and let Angular lovers create Custom Components which not only can be used in other framework, but also give ability to create standalone widgets easily to be consumed even in a simple HTML page, think for example SharePoint.
The result component will be like a web component for the React application like any other HTML components like button
or input
. Plus, finally I had some time to play with the concepts and get a quick demo ready and it was pretty easy as expected. So let's get started.
For the purpose of this post, I will create a book list component in Angular which accepts a list of books and shows them in the page. This component is ported into a React application which will send the list of books as props to it. Once a book is added to the card, the changes will be propagated to React app.
At a high level, here is a picture describing the solution from documentation:
Create your Angular component
First let's create a new Angular application using Angular CLI:
ng new book-list
You can go ahead and delete the default app component and create a new component called book list:
ng g c book-list
Note: I'm using Angular CLI's short hand commands here (g for generate and c for component), for more info on those please visit their documentation.
Since we want to evaluate the communication between our Angular and React applications, we will pass the book list as input. Just to test the other way around, we define an output as an EventEmitter
that gets triggered whenever a book is selected from the list. So the component code will look like this:
import { Component, Input, Output, EventEmitter } from '@angular/core'
@Component({
selector: 'book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.css'],
})
export class BookListComponent {
public bookList: any[]
@Input('books')
set books(books: string) {
this.bookList = JSON.parse(books)
}
@Output('bookSelected') bookSelected = new EventEmitter<any>()
constructor() {}
selected(book: any) {
this.bookSelected.emit(JSON.stringify(book))
}
}
Note: Currently passing a complex object like array into a web component is a debatable (you can set a property value using JavaScript or just use attributes). For simplicity, we just pass a JSON
string for the purpose of this post.
And the HTML is pretty simple:
<h1>List of recent books</h1>
<div *ngFor="let book of bookList">
<strong>{{book.name}}</strong>
<p>{{book.description}}</p>
<label class="container"
>Add to card <input type="checkbox" (change)="selected(book)" />
<span class="checkmark"></span>
</label>
</div>
Bootstrapping the component as an element
Now that we have created our component, it's time to bootstrap our component as an Angular element:
import { BrowserModule } from '@angular/platform-browser'
import { NgModule, Injector } from '@angular/core'
import { createCustomElement } from '@angular/elements'
import { BookListComponent } from './book-list/book-list.component'
@NgModule({
declarations: [BookListComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [],
entryComponents: [BookListComponent],
})
export class AppModule {
constructor(private injector: Injector) {
const customElement = createCustomElement(BookListComponent, { injector })
customElements.define('book-list', customElement)
}
ngDoBootstrap() {}
}
To create a custom element from the book list component, we should invoke the new createCustomElement
method which doesn't insert the newly created component inside CustomElementRegistry, hence using AppModule
constructor to do it manually. Maybe in future that will happen automatically but until then it is our job to do it.
At this point the element is ready. It's time to build our element:
ng build --prod --output-hashing none
If you have a look at your dist/book-list
folder now, you should see three JS files generated for you, main.js
, polyfills.js
, runtime.js
.
Hosting our book list inside a React app
It's time to create our React app. We can start by creating one using React CLI:
npx create-react-app react-host
When the command finished you should have the initial React app template setup. If you run npm start
from inside the react-host folder, you should see the default app:
We can copy the three files created by Angular CLI into the public folder of our React app and reference them inside index.html
(You can setup the the projects in a way to export the Angular Element as npm
package and consume it in React app, I am just taking it easy here π).
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<script type="text/javascript" src="./book-list/runtime.js"></script>
<script type="text/javascript" src="./book-list/polyfills.js"></script>
<script type="text/javascript" src="./book-list/main.js"></script>
</body>
Using the book list component
Now that we have everything in place, let's start by modifying our app component and add a list of books as it's state so we can pass it down to our book list:
constructor(props){
super(props)
this.state = {
books: [
{
name: '10% Happier',
description: `Practicing meditation and mindfulness
will make you at least 10 percent happier.`
},
{
name: 'The 10X Rule',
description: `The biggest mistake most people
make in life is not setting goals high enough.`
},
{
name: 'A Short Guide to a Happy Life',
description: `The only thing you have that nobody
else has is control of your life.`
}
]
};
}
Now we can use our book list component and pass the books down as property:
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<div className="book-list">
<book-list ref={elem => this.nv = elem}
books={JSON.stringify(this.state.books)}></book-list>
</div>
<div className="selected-books">
<h1>Shopping card</h1>
{this.renderSelectedBooks()}
</div>
</div>
);
}
Note: As I mentioned above, we need to pass a string to our custom component or it won't receive the correct data.
We used a method to render the selected books, so let's define it:
renderSelectedBooks() {
return(
<div>
{
this.state.selectedBooks.map(function(book, index){
return <div><strong key={ index }>{book.name}</strong></div>;
})
}
</div>
)
}
I am using internal state here, but note that this is not a React post and I am not following any best practices here.
Also we used a variable called nv
to have a reference to the component. We will add an event listener to it which listens to bookSelected
event and called a method to handle the event.
componentDidMount() {
this.nv.addEventListener("bookSelected", this.handleBookSelected);
}
Warning: The name of the event should match the name of event that you used when defining the Angular element.
Let's initialise our state in our event handler:
handleBookSelected = event => {
const book = JSON.parse(event.detail)
let selectedBookList = []
if (this.state.selectedBooks.find(x => x.name === book.name)) {
selectedBookList = this.state.selectedBooks.filter(
x => x.name !== book.name
)
} else {
selectedBookList = [...this.state.selectedBooks, book]
}
this.setState({
...this.state,
selectedBooks: [...selectedBookList],
})
}
The above code looks a bit busy, but it is very simple. We first check if the book is already selected and remove it if it is. If the book is not in selected list we add it and update the state. Once the state is updated React will reload the page and shows the updated selected books.
If you run the app now you should see a screen like this:
And that's it you're officially running an Angular Element inside a React app and they get along really well ππ₯π―.
You can find the source code on my GitHub repository.
Now go and create awesome stuff ππ».
Top comments (0)