Credits: Sitepoint
Two React Fans sent to Angular Battleground
Feelings
Tips
How to choose between the two
Let's get started!
Two different basic stack
Modern Javascript Base
Angular
Two different learning curves
- Small basis to learn
- Lots of others library
- Lots of design patterns
- Observables (RxJS)
- Then you are all set
Launch your project
Prepare your coding gear
React Dev Tool
Augury
Prepare your coding gear
Documentation
Let's Code!
Writing components
// Hello.jsx
export class Hello extends Component {
render() {
return (
<div>
Hello { this.props.name }!
</div>
);
}
}
Hello.propTypes = {
name: React.PropsTypes.string
}
Hello.defaultProps = {
name: 'ParisJS'
};
//app.module.ts
@NgModule({
declarations: [
HelloComponent,
],
})
export class AppModule {}
//hello.component.html
<div>
Hello {{ name }}!
</div>
//hello.component.ts
@Component({
selector: 'hello-component',
templateUrl: './hello.component.html',
styleUrls: ['./hello.component.less'],
})
export class HelloComponent {
@Input() name = 'ParisJS';
}
Writing component - JSX
Writing component -JSX
<div style="list">
<div style="list-elem">1</div>
<div style="list-elem">2</div>
</div>
// instead of
<ul style="list">
<li style="list-elem">1</li>
<li style="list-elem">2</li>
</ul>
<div onClick="function">Button</div>
// instead of
<button onClick="function">Button</button>
pas clair
Accessibility
Usual user behaviours
Communication between components
Props
Callback
@Input
@Output and EventEmitter
// declare fields
@Input() data: string;
@Output() handleEvent = new EventEmitter();
// use it
handleEvent.emit(value)
<MyChildComponent
[data]="data"
(handleEvent)="myFunction($event)">
<MyChildComponent
data={data}
handleEvent={myFunction}>
// declare in PropTypes
data: PropTypes.string,
handleEvent: PropTypes.func,
// use them
this.props.handleEvent(value)
App Data Management
- Context API
- State managers
- Data services (singletons)
- State managers
The first obstacles!
Writing components - wrappers
<TodoList title={'Todo list'}>
{this.props.listItems.map((listItem) =>
(<ListItem item={listItem} />)
)}
</TodoList>
<todo-list [title]="Todo list">
<todo-list-item
*ngFor="let listItem of listItems"
[item]="listItem" />
</todo-list>
<todo-list>
<h1>Todo list</h1>
<ul class="group">
<todo-list-item>
<li class="item">learn react</li>
</todo-list-item>
<todo-list-item>
<li class="item">learn angular</li>
</todo-list-item>
<todo-list-item>
<li class="item">decide</li>
</todo-list-item>
</ul>
</todo-list>
Harder to style and understand HTML
<h1>Todo list</h1>
<ul class="group">
<li class="item">learn react</li>
<li class="item">learn angular</li>
<li class="item">decide</li>
</ul>
Component Lifecycle
- constructor
- ngOnChanges
- ngOnInit
- ngOnDestroy
- 5 more methods
- constructor
- getDerivedStateFromProps
- componentDidMount
- componentWillUnmount
- render
- 4 other methods
Sometimes displaying similar APIs...
...sometimes very different ones...
Component Lifecycle - Virtual DOM
- Compares JS objects
- Only renders changed elements
Overall both frameworks are performant, React more for massive amount of data
Component lifecycle
Do not calculate methods in template in Angular
... which can end up in little suprises
import { Component } from '@angular/core';
@Component({
selector: 'app-header',
templateUrl: `
<div class="header-container">
{{ countMyRenders() }}
</div>
`,
})
export class HeaderComponent {
countMyRenders() {
console.count('render of header component');
}
}
Can render erratically - even if inputs don't change
Observables are powerful...
- Between a promise and an event
- Reactive programing - your UI listens and reacts to changes
- Powerful and particularly adapted to Real Time
"You can think of an observable as an array whose items arrive asynchronously over time." Angular docs
...but complicated to master...
@Injectable()
export class ApiService {
constructor(private toolService: ToolService) {}
getAPIData(): Observable<ApiAnswer> {
return this.http.get('API_URL');
}
}
...
// in a component
this.ApiService.getAPIData(); // does not trigger the call
this.ApiService.getAPIData().subscribe(); // does trigger the call
It's a new paradigm to learn alltogether
- ~ 20 different classes (observable, subject ...)
- ~ 120 operators =>
...and you don't have a choice
DOM
LISTENERS
Dependency injection can make your life easier
... but your Hands and Eyes Sore
Makes testing easier
Makes code Flexible/Easier to re-use
@Injectable()
export class ApiService {
method() {...}
}
...
@NgModule({
declarations: [],
imports: [],
providers: [
ApiService,
],
})
export class AppModule {}
...
@Component({
selector: 'app-header',
})
export class HeaderComponent {
constructor (apiService: ApiService) {
this.apiService.method();
}
}
Adds a lot of boiler-plate code in tests
Tests - Angular
describe('MyComponent', () => {
let fixture;
let component;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
HttpClientModule,
],
declarations: [
MyComponent,
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.debugElement.componentInstance;
fixture.detectChanges();
}));
...
Boiler Plate
Mock with DI
Complicated
API
Tests - Angular
...
it('should be a great test', fakeAsync(() => {
// mandatory to refresh dom
fixture.detectChanges();
// mandatory to wait for asynchrone events to finish
tick()
fixture.detectChanges();
...
fixture.detectChanges();
tick()
fixture.detectChanges();
}));
...
Component lifecycle to update manually
Tests - Angular
ERROR ZoneAwareError {__zone_symbol__error: Error: Uncaught (in promise): Error
Error
at Error.ZoneAwareError (http://localhost:4200/vendor.……}message: (...)name: (...)originalStack: (...)promise: ZoneAwarePromiserejection: Errorstack: (...)task: ZoneTasktoSource: function ()toString: function ()zone: ZonezoneAwareStack: (...)__zone_symbol__error: Error: Uncaught (in promise):
Error
Error
at Error.ZoneAwareError (http://localhost:4200/vendor.bundle.js:97291:33)
at ZoneAwareError (http://localhost:4200/vendor.bundle.js:97288:35)
at injectionError (http://localhost:4200/vendor.bundle.js:2061:86)
at noProviderError (http://localhost:4200/vendor.bundle.js:2099:12)
at ReflectiveInjector_._throwOrNull (http://localhost:4200/vendor.bundle.js:3601:19)
at ReflectiveInjector_._getByKeyDefault (http://localhost:4200/vendor.bundle.js:3640:25)
at ReflectiveInjector_._getByKey (http://localhost:4200/vendor.bundle.js:3572:25)
at ReflectiveInjector_.get (http://localhost:4200/vendor.bundle.js:3441:21)
at AppModuleInjector.NgModuleInjector.get (http://localhost:4200/vendor.bundle.js:4406:52)
at resolveDep (http://localhost:4200/vendor.bundle.js:11810:45)
at createClass (http://localhost:4200/vendor.bundle.js:11673:147)
at createDirectiveInstance (http://localhost:4200/vendor.bundle.js:11504:37)
at createViewNodes (http://localhost:4200/vendor.bundle.js:12853:49)
at createRootView (http://localhost:4200/vendor.bundle.js:12758:5)
at callWithDebugContext (http://localhost:4200/vendor.bundle.js:13889:42)
at Error.ZoneAwareError (http://localhost:4200/vendor.bundle.js:97291:33)
at ZoneAwareError (http://localhost:4200/vendor.bundle.js:97288:35)
at injectionError (http://localhost:4200/vendor.bundle.js:2061:86)
at noProviderError (http://localhost:4200/vendor.bundle.js:2099:12)
at ReflectiveInjector_._throwOrNull (http://localhost:4200/vendor.bundle.js:3601:19)
at ReflectiveInjector_._getByKeyDefault (http://localhost:4200/vendor.bundle.js:3640:25)
at ReflectiveInjector_._getByKey (http://localhost:4200/vendor.bundle.js:3572:25)
at ReflectiveInjector_.get (http://localhost:4200/vendor.bundle.js:3441:21)
at AppModuleInjector.NgModuleInjector.get (http://localhost:4200/vendor.bundle.js:4406:52)
at resolveDep (http://localhost:4200/vendor.bundle.js:11810:45)
at createClass (http://localhost:4200/vendor.bundle.js:11673:147)
at createDirectiveInstance (http://localhost:4200/vendor.bundle.js:11504:37)
at createViewNodes (http://localhost:4200/vendor.bundle.js:12853:49)
at createRootView (http://localhost:4200/vendor.bundle.js:12758:5)
at callWithDebugContext (http://localhost:4200/vendor.bundle.js:13889:42)
at resolvePromise (http://localhost:4200/vendor.bundle.js:96964:31) [angular]
at resolvePromise (http://localhost:4200/vendor.bundle.js:96935:17) [angular]
at http://localhost:4200/vendor.bundle.js:97012:17 [angular]
at Object.onInvokeTask (http://localhost:4200/vendor.bundle.js:4965:37) [angular]
at ZoneDelegate.invokeTask (http://localhost:4200/vendor.bundle.js:96665:36) [angular]
at Zone.runTask (http://localhost:4200/vendor.bundle.js:96465:47) [<root> => angular]
at drainMicroTaskQueue (http://localhost:4200/vendor.bundle.js:96845:35)
[<root>]
at HTMLAnchorElement.ZoneTask.invoke
(http://localhost:4200/vendor.bundle.js:96723:25) [<root>]get message: function ()set message: function (value)get name: function ()set name: function (value)get originalStack: function ()set originalStack: function (value)get stack: function ()set stack: function (value)get zoneAwareStack: function ()set
zoneAwareStack: function (value)__proto__: Object
defaultErrorLogger @ core.es5.js:1085
ErrorHandler.handleError @ core.es5.js:1145
next @ core.es5.js:4774
schedulerFn @ core.es5.js:3848
SafeSubscriber.__tryOrUnsub @ Subscriber.js:234
SafeSubscriber.next @ Subscriber.js:183
Subscriber._next @ Subscriber.js:125
Subscriber.next @ Subscriber.js:89
Subject.next @ Subject.js:55
EventEmitter.emit @ core.es5.js:3834
NgZone.triggerError @ core.es5.js:4205
onHandleError @ core.es5.js:4166
ZoneDelegate.handleError @ zone.js:338
Zone.runGuarded @ zone.js:142
_loop_1 @ zone.js:557
drainMicroTaskQueue @ zone.js:566
ZoneTask.invoke @ zone.js:424
Unclear error messages
Tests - React
+ Enzyme
Snapshot testing
No need for a browser
Easy to manipulate props and state
Hard to mock business functions
describe('Super test case', () => {
beforeEach(() => {
wrapper = shallow(<MyComponent {...props} />);
});
it('should be a great test', () => {
// Set state
wrapper.setState({ name: 'bar' })
// Call component Class methods
wrapper.instance().handleClick();
// check children components props
expect(image.prop('src')).toBe('url');
// Simulate events
wrapper.find(MyOtherComponent).simulate('click');
});
});
Simple API
Tests - React vs Angular
Architecture
Angular Modularity
Architecture - Angular
Core module
Heroes module
Shared module
App module
On one side the doc offers a solution for everything...
source: https://angular.io/guide/styleguide#overall-structural-guidelines
A complete style guide
One proposed organisation
One way of naming files
Architecture - React
... on the other side everything is up to you
.js vs .jsx
File naming
Business logic files
Organizing by Page
It is extremely rare to find two React projects with the same architecture
Styling
Typing
Function vs Class
shared components
Difference of philosophy
Angular is a Framework
React is a library
Difference of philosophy
Both can make to a good recipe...
...or end up in spaghetti-code
How to choose ?
How to choose - Team Background
Typed / Object Oriented
How to choose - Team Background
How to choose - Team Size
How to choose - Project
How to choose - Targeting Mobile
How to choose - Real Time
How to choose - Personal Preference
You prefer to choose for yourself / tailor your stack to your needs
You prefer everything to be decided for you
How to choose - Personal Preference
Our Feeling
is more structured but not as fun as
Our Tips
Working with helped us with component architecture and state management
Working with helped us to structure our projects and stay closer to HTML
How to choose
How is the Team ?
How is the Project ?
Thank you
React vs Angular ParisJS
By Loïc Carbonne
React vs Angular ParisJS
- 2,010