angular 2 design patterns
TRANSCRIPT
Angular 2 Design Patterns
Component Communication
What is a component
@Component({ selector: 'myComp', template: `<div> Content: {{myVar}} </div>`})class MyComponent { myVar: string;
constructor() { this.myVar = 'hello'; }}
Output
Input<html> <myComp></myComp></html>
<html> <myComp> <div> Content: hello </div> </myComp></html>
Parent → Child
@Component({ selector: 'parent', template: `<div> Parent content <child [param]="myVar"></child> Parent content</div>`})class ParentComponent { myVar = 'hello';}
@Component({ selector: 'child', template: '<div>Child: {{param}}</div>'})class ChildComponent { @Input() param: string;}
<html> <parent> <div> Parent content <child> Child hello </child> Parent content </div> </parent></html>
Output
Input<html> <parent></parent></html>
@Input() Demo
Child → Parent
@Component({ selector: 'parent', template: `<div> <child (childEvent)="handelChildEvent($event)"></child></div>`})class ParentComponent { handelChildEvent(message) { console.log(message); }}
@Component({ selector: 'child', template: ` <button (click)="childEvent.emit('clicked')">Click me</button>`})class ChildComponent { @Output() childEvent = new EventEmitter();}
@Output() Demo
Sibling → Sibling
@Component({ selector: 'sibling2', template: `<button (click)="myService.increment()"> Increment</button>`})class Sibling2Component { constructor(public myService: MyService) {
}}
@Component({ selector: 'sibling1', template: `{{myService.counter}}`})class Sibling1Component { constructor(public myService: MyService) {
}}
class MyService { counter: number = 0;
increment() { this.counter++; }}
Sibling Demo
user.service.tsexport class UserService { users: User[] = [];}
user.service.tsexport class UserService { private users$ = new BehaviorSubject([]);}
user.service.tsexport class UserService { private users$ = new BehaviorSubject([]);
addUser(user) { let users = [user, ...this.users$.getValue()]; this.users$.next(users); }}
user.service.tsexport class UserService { private users$ = new BehaviorSubject([]);
addUser(user) { let users = [user, ...this.users$.getValue()]; this.users$.next(users); }
removeUser(user) { let users = this.users$.getValue().filter(u => u !== user); this.users$.next(users); }}
user.service.tsexport class UserService { private users$ = new BehaviorSubject([]);
addUser(user) { let users = [user, ...this.users$.getValue()]; this.users$.next(users); }
removeUser(user) { let users = this.users$.getValue().filter(u => u !== user); this.users$.next(users); }
getUsers() { return this.users$.asObservable(); }}
userSearch.component.ts@Component({ selector: 'user-search', template: `...<div *ngFor="let user of users | async"> ...</div>`})export class UserSearchComponent { users: Observable<User[]>;
constructor(public userService: UserService) { this.users = userService.getUsers(); }}
userSearch.component.ts@Component({ selector: 'user-search', template: `...First Name:<input #firstName (keyup)="firstNameSearch.next(firstName.value)">...`})export class UserSearchComponent { firstNameSearch = new BehaviorSubject('');}
userSearch.component.ts@Component({ selector: 'user-search', template: `...`})export class UserSearchComponent { users: Observable<User[]>; firstNameSearch = new BehaviorSubject('');
constructor(private userService: UserService) { this.users = Observable.combineLatest( userService.getUsers(), this.firstNameSearch, (users, search) => { return users.filter(user => user.firstName.includes(search)); } ); }}
userSearch.component.ts@Component({ selector: 'user-search', template: `...`})export class UserSearchComponent { users: Observable<User[]>;
constructor(private userService: UserService) { ... }
removeUser(user: User) { this.userService.removeUser(user); }}
Redux Demo
Takeaways
- Everything is a component!!!
Takeaways - Parent → Child
- Use an @Input() binding on child component
- Use es6 setters or ngOnChanges() to handle changes
- When @Input() doesn’t work, inject a @ViewChild()
Takeaways - Child → Parent
- Use an @Output() binding on child component
- Pass events to parent through an EventEmitter()@Output() doThing = new EventEmitter();doThing.emit('some event');
- In the parent, get the payload of the event with $event<child (doThing)="handelThing($event)"></child>
- If you can’t use an @Output() binding you can inject the parent component directly into the child
Takeaways - Sibling → Sibling
- Use a service to communicate between siblings
- Try to avoid sharing mutable state- Use observables to push events out
from a service to componentsprivate state$ = new BehaviorSubject<>({});doSomething(thing) { this.state$.next(thing);}
Takeaways - ngrx/store (Redux)
- ngrx/store library brings redux like approach to Angular 2
- Centralized state- One way data flow
Further ReadingAngular CookbookComponent Interaction
rxjs/storegithub.com/ngrx/store
egghead.ioBuilding Angular 2 Components
egghead.ioStep-by-Step Async JavaScript with RxJS
Rob McDiarmidSlides: tinyurl.com/ng2-components
@robianmcd