React & Angular 2
Michael K. Snead
ReactEverything:
...and pretty much everything else!
2
Aurelia
Kendo
Knockout
Why?
- Legacy codebases may be stuck on old frameworks or in jQuery spaghetti. Plug in React!
- Large enterprises may take time (if ever) to converge on one framework. Share React!
- You may want to switch to or prototype in React - use React!
- You may want to take advantage of React's simplicity
- You may want to take advantage of React's performance
Let's build an Angular 2 React Component
- The environment:
Webstorm, Webpack, ES2016 (ES7), Babel
Angular 2.beta1, React 0.14.6 - Create a basic component
- Inject the native DOM element
- Hook up the Angular 2.0 lifecycle
- Create/destroy/update the React component
npm start
Bundle.js
A single entry-point (usually 'app.js') begins the tree of dependencies, which node-based webpack includes in the bundle.
Babel converts ES-next to ES-today
'import' statements become CommonJS 'require' that node.js understands
ES*
ES*
ES*
CJS
CJS
CJS
Building the Project
Angular2 is designed to be used in ES5, ES6, ES7, Dart or TypeScript.
Hello, world
//ES6 module import syntax
import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
@Component({ //ES7 decorator
selector: 'my-app' //matches <my-app></my-app> in HTML
})
@View({
template: `<h1>My First Angular 2 App</h1>` //ES6 multi-line string
});
class AppComponent { } //ES6 class
bootstrap(AppComponent);
There's a lot of modern JavaScript syntax here. If you know ES6/7 it should look familiar to you.
Decorators: ES6 or ES7?
import { Component, View } from 'angular2/core';
//Either do this ... (ES6)
export class ReactComponent { }
ReactComponent.annotations = [
new Component({
selector: 'react-component'
}),
new View({
template: '<span>This will become a React Component!'
})
];
//Or this ... (ES7)
@Component({
selector: 'react-component'
})
@View({ template: '<span>This will become a React Component!' })
export class ReactComponent { }
- Bleeding edge ES7 + Babel 5
- Bleeding edge ES7 + Babel 6 + Decorator legacy plugin
- Official ES6 + Babel 6
- TypeScript is essentially option #1 with the TS compiler
I illustrate the differences in syntax on my blog
Create A Component
import { Component, View, Input } from 'angular2/core';
/* Using this element would be something like ...
template: `
<div>Hey, this is a cool app!</div>
<div><react-component component="FooBar"></react-component></div>
`
*/
//Using ES7 with class properties
@Component({ selector: 'react-component' })
@View({ template: '<span>You told me it was {{component}}</span>' })
export class ReactComponent {
@Input() component = undefined; //Must initialize to something (bug?)
}
//Using ES7 with @Component array
@Component({ selector: 'react-component', inputs: ['component'] })
@View({ template: '<span>You told me it was {{component}}</span>' })
export class ReactComponent {}
A2's class property decorators seem to have a bug where they become read-only if you don't initialize them with something in ES7 - anything works, even undefined.
Native DOM Access
import { Component, View, Input } from 'angular2/core';
import { ElementRef } from 'angular2/core';
//Using ES7 with class properties
@Component({ selector: 'react-component' })
@View({ template: '<span>You told me it was {{component}}</span>' })
export class ReactComponent {
@Input() component = undefined; //Must initialize to something (bug?)
static parameters = [ElementRef];
constructor(elementRef) {
this.element = elementRef.nativeElement; //Save a reference
}
}
//Using ES6 static getter (zero ES7)
export class ReactComponent {
static get parameters() { return [ElementRef]; }
constructor(elementRef) {
this.element = elementRef.nativeElement; //Save a reference
}
}
ReactComponent.annotations = [
new Component({ selector: 'react-component', inputs: ['component'] }),
new View({ template: '<span>You told me it was {{component}}</span>' })
];
TypeScript "sugars over" adding the parameters array and infers the type from the type annotation.
A 3rd alternative is to put 'bindings' array in @Component.
Hook Up Lifecycle
import { Component, View, Input } from 'angular2/core';
import { ElementRef } from 'angular2/core';
//This example is in ES7
@Component({ selector: 'react-component' })
@View({ template: '<span>You told me it was {{component}}</span>' })
export class ReactComponent {
@Input() component = undefined; //Must initialize to something (bug?)
static parameters = [ElementRef];
constructor(elementRef) {
}
ngOnInit() {
//Fires one time at the beginning
console.log("Hey! I'm initialized! Component is ", this.component);
}
ngOnChanges(changes) {
//Fires one time at the beginning and again when bindings change
console.log("Hey, I'm changing! Component is ", this.component);
}
ngOnDestroy() {
//Fires when A2 needs to rerender or remove element from DOM
console.log("Hey, I'm being destroyed! Ouch!");
}
}
1. Constructor, 2. ngOnChanges, 3. ngOnInit
Create/Update/Destroy
import { Component, View, Input } from 'angular2/core';
import { ElementRef } from 'angular2/core';
@Component({ selector: 'react-component' })
@View({ template: '' })
export class ReactComponent {
@Input() component = undefined;
@Input() props = {};
@Input() children = [];
static parameters = [ElementRef];
constructor(elementRef) {
this.element = elementRef.nativeElement;
}
ngOnChanges(changes) {
ReactDOM.render(React.createElement(this.component,
this.props,
this.children),
this.element);
}
ngOnDestroy() {
ReactDOM.unmountComponentAtNode(this.element);
}
}
Wiring It Up
- import the ReactComponent directive
- Add the directive to your @View directives array
- Bring the React component(s) you want into scope
<react-component [props]="{
onRemovePerson: removePerson,
people: people
}" [component]="PeopleList"></react-component>
class App {
constructor() {
this.PeopleList = window.PeopleList;
this.onRemovePerson = this.onRemovePerson.bind(this);
}
onRemovePerson() {
//...
}
}
Don't lose 'this'...
Your React components are likely to have events that your Angular2 components will want to subscribe to.
When React invokes them (ie: button click), your 'this' will be lost.
An easy way to fix this is binding your method in the constructor, like so:
class App {
constructor() {
//make sure "this" sticks with the function/method
this.onReactButtonClicked = this.onReactButtonClicked.bind(this);
}
onReactButtonClicked(event) {
//"this" refers to App!
}
}
This problem isn't limited or specific to React :(
Using React with Angular 1.x
- Add React/ReactDOM
- Add ngReact.js script after Angular/React
- Register string value for each component
- Register a directive for each value
- For Angular 1.x compatibility, make sure you support props in all lower case!
ngReact's repo is here:
Working demo...
(Still waiting for the full release?)
Using React with Knockout
- Add the reactHandler.js binding handler
(ie: via <script> tag) - Add React/ReactDOM scripts
- Use the "react" binding
<div data-bind="react: { $: PeopleList,
props: { people: people, onRemovePerson: removePerson } }"></div>
Very much inspired by...
Working demo...
(Reliable! Not flashy!)
Using React with Kendo MVVM
- Add a reference to the component(s) you want to your viewmodel, to bring them in scope. Same as Angular2
- On the target element, specify data-role="reactcomponent"
This tells Kendo to wire up the widget - Fill in data-bind="reactcomponent: ..."
TypeScript demo source:
(I feel so bad for you)
Like the Angular2 component, I wrote this from scratch.
<div data-role="reactcomponent"
data-bind="reactcomponent: { component: PeopleList,
props: { people: people, onRemovePerson: removePerson } }"></div>
Widget:
Demo markup:
...and also in TypeScript
Using React with Durandal
- There's a sample project that I did not write here:
https://github.com/bshorrosh/Durandal-React-Sample - If you're familiar with Durandal/Require, it's using a Require.js plugin + the JSX Transformer to load .JSX files.
- Since Durandal really just uses knockout under the hood, you might just use the binding handler from before.
(Aurelia is in beta now!)
Using React with Aurelia
- There's a blog post on this that I did not write here:
http://ilikekillnerds.com/2015/03/how-to-use-react-js-in-aurelia/
- One thing not in this example is Aurelia's 'unbind'
I would add the ReactDOM.unmountComponentAtNode() in a new 'unbind()' method.
Aurelia's documentation for custom binding behaviors:
http://aurelia.io/docs.html#/aurelia/binding/latest/doc/article/binding-binding-behaviors
I am not an Aurelia expert.
Using React with Ember 1.x
- Another repo I am not responsible for:
https://github.com/ghempton/ember-react - And the sample project that goes along with it:
http://ghempton.github.io/ember-react/
(If you're on Ember 2.x, good for you!)
I am also not an Ember expert.
Another use of React alongside X...
React's isomorphic nature means you can render the initial state on the server.
This means the markup is in the correct state the moment the initial HTML hits the browser.
This is great for mobile!
(and everything else)
Get to know...
ECMAScript 6 and 7 (ES2015, ES2016)
What browsers support ES6 and ES7?
https://kangax.github.io/compat-table/es6/
ES6 Features
https://github.com/lukehoban/es6features
Try ES-next in your browser
https://babeljs.io/repl/
ECMAScript 6 - New Features: Overview & Comparison
http://es6-features.org/
Taming the asynchronous beast with ES7
http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html
JavaScript Decorators
https://github.com/wycats/javascript-decorators
ECMAScript 6 modules: the final syntax
http://www.2ality.com/2014/09/es6-modules-final.html
Questions?
Thanks for your time.
React and Angular2
By Michael Snead
React and Angular2
Illustrate how to embed React into Angular2 - and other frameworks.
- 3,623