Credits: Sitepoint
Modern Javascript Base
Angular
React Dev Tool
Augury
// 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';
}
<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
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)
<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>
Sometimes displaying similar APIs...
...sometimes very different ones...
Overall both frameworks are performant, React more for massive amount of data
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
"You can think of an observable as an array whose items arrive asynchronously over time." Angular docs
@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
DOM
LISTENERS
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
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
...
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
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
+ 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
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
... 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
Angular is a Framework
React is a library
Both can make to a good recipe...
...or end up in spaghetti-code
Typed / Object Oriented
You prefer to choose for yourself / tailor your stack to your needs
You prefer everything to be decided for you
is more structured but not as fun as
Working with helped us with component architecture and state management
Working with helped us to structure our projects and stay closer to HTML