Angular has web components.
By that I mean a modular piece of front end UI code with the following:
- HTML template
- TypeScript class to handle UI logic
- Encapsulated CSS
Angular’s web components also support one way data flow via Output
and Input
class decorators, and dependency injection.
So how do I Reactify these concepts?
Let’s start with a basic dumb Angular Component, with a form, an input value and an output emitter, and see if we can’t shiny it up with some React:
useful-form.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
export interface UsefulInformation {
firstName: string;
crucialInformation: string;
}
@Component({
selector: 'app-useful-form',
templateUrl: './useful-form.component.html',
styleUrls: ['./useful-form.component.css']
})
export class UsefulFormComponent {
@Input() name: string;
@Output()
usefulUserInformation: EventEmitter<UsefulInformation> = new EventEmitter<
UsefulInformation
>();
public usefulForm: FormGroup;
public crucialOptions = ['important', 'necessary', 'crucial'];
constructor() {
this.usefulForm = new FormGroup({
number: new FormControl(this.name, Validators.required),
crucialInformation: new FormControl('', Validators.required)
});
}
public submit() {
this.usefulUserInformation.emit(this.usefulForm.value);
}
}
useful-form.component.html
<form [formGroup]="usefulForm">
<h2>Hello <span *ngIf="!name">person</span>{{ name }}</h2>
<label for="number">Number</label>
<input
id="number"
type="number"
formControlName="number"
placeholder="pick a number!"
>
<label for="crucialInformation">Crucial information</label>
<select
id="crucialInformation"
formControlName='crucialInformation'
>
<option
*ngFor="let option of crucialOptions;"
[value]="option"
>
{{ option }}
</option>
</select>
<button
(click)="submit()"
[disabled]="usefulForm.invalid"
>Submit</button>
</form>
So here we have a component that can be embedded in other HTML, with an input passed in via a name field, that we’re rendering in the template, an output event that can be listened to, and a reactive form. There is also an *ngFor
and and *ngIf
.
The component can be used like below in a parent component:
<app-useful-form
name='Rob'
(usefulUserInformation)="handleUserInformation($event)"
></app-useful-form>
All pretty standard stuff. Let’s try and replicate this behaviour in React.
Reactified useful component
First of all I want to roughly map some Angular concepts related to components, to their React equivalents:
- Components -> Components
- Inputs -> Props
- Outputs -> Callback functions
- HTML -> JSX
- Reactive forms -> Controlled components?
- Local state -> Local state
useful-form.js
import React from 'react';
export default class UsefulForm extends React.Component {
constructor(props) {
super(props);
this.state = {
number: '',
crucialInformation: ''
};
this.crucialOptions = ['important', 'necessary', 'crucial'];
this.handleNumberChange = this.handleNumberChange.bind(this);
this.handleCrucialInformationChange = this.handleCrucialInformationChange.bind(
this
);
}
handleNumberChange(event) {
this.setState({ ...this.state, number: event.target.value });
}
handleCrucialInformationChange(event) {
this.setState({ ...this.state, crucialInformation: event.target.value });
}
render() {
return (
<form onSubmit={() => this.props.handleSubmit(this.state)}>
<h1>Hello {this.props.name || 'person'}</h1>
<label>
Number:
<input
type="number"
required
placeholder="pick a number!"
value={this.state.number}
onChange={this.handleNumberChange}
/>
</label>
<label>
Crucial information:
<select
required
value={this.state.crucialInformation}
onChange={this.handleCrucialInformationChange}
>
<option value=''>Pick option</option>
{this.crucialOptions.map(option => <option value={option} key={option}>{option}</option>)}
</select>
</label>
<input
type="submit"
value="Submit"
disabled={!(this.state.number && this.state.crucialInformation)}
/>
</form>
);
}
}
The component is used as follows in a parent component:
class App extends Component {
handleUsefulFormSubmit(event) {
window.alert(JSON.stringify(event));
}
render() {
return (
<UsefulForm
name="Rob"
handleSubmit={this.handleUsefulFormSubmit}
/>
);
}
}
export default App;
What are the key differences/learnings?
- React seems to be much less opinionated about how you do things and more flexible. It is also JavaScript, where Angular is in many ways its own thing.
- Forms in React seem to require more work to achieve the same as in Angular (validation, disabled buttons, updates to form values etc.). I suspect as I mess around with this I will find some nicer patterns for handling programatic driven forms.
Overall the differences are not as great as I had feared. Both allow for controlling child components from a parent component, and flowing data into and out of the component without mutating things. Also the difference between *ngIf and *ngFor and just using interpolated JavaScript is very minimal.
I am pleasantly surprised by how much I like React so far…