Practical
Component
Communication
with AngularJS & Angular 2
W3P Kafi #3, 02.11.2016
Mathis Hofer, hofer@puzzle.ch
Disclaimer
This is not about Flux* architectures etc.
UI Components
Good ol' components
Desktop GUI Widgets
Component-based software engineering (CBSE)

Arrived to Web Frontend Dev
Component Architecture
Smart vs. Dumb Components
Properties flow down; actions flow up
Unidirectional Dataflow

AngularJS (1.x)
Scope
What is it?
Context where the model is stored so that
controllers, directives and expressions
can access it.
$rootScope
Two-way databinding
Everything global!
let scope1 = $rootScope.$new(),
scope2 = scope1.$new();Scope Inheritance
angular.module('blog', [])
.component('articleList', () => {
return {
scope: true
};
});Access parent's properties/functions
No encapsulation!
Scope Isolation
angular.module('blog', [])
.component('articleList', () => {
return {
scope: {}
};
});Default for 1.5+ Components
How to input/output?
Component Input Binding
1-way binding '<' (1.5+)
2-way binding '='
<contacts-list
contacts="vm.myContacts">
</contacts-list>angular.module('app')
.component('contactsList', {
bindings: {
contacts: '<'
},
...
});Input Changes
function ContactPanelController($scope) {
$scope.$watch('contact', function(newContact, oldContact) {
initFormModel();
});
}better: Component API $onChanges
Component Output Binding
<contacts-toolbar
add="vm.createContact()">
</contacts-toolbar>angular.module('app')
.component('contactsToolbar', {
bindings: {
add: '&'
},
...
});
function ToolbarController() {
var vm = this;
vm.addAction = function() {
vm.add();
}
}Let's build a component hierarchy!
Routing
Why using URLs?
Open in new tab
Reload (or reopen browser)
Share link
Bookmark
…
Difficulties with Routing
Doesn't work:
Input/Output Bindings (<, =, &)
Works:
State in URL Params
Scope Events
Require Controller
State in Shared Service
Scope Events
$scope.$broadcast()
$scope.$emit()
Require Controller
Access parent component's controller:
angular.module('app')
.component('contactForm', {
require: '^^contacts'
...
});
function ContactFormController($element) {
var contactsController = $element.controller('contacts');
}Shared Service
Holds data/state
How to handle async?
Outputs
Use Scope Events
or require controller
Let's do some routing!
Angular 2
No "Scope" anymore!
No $rootScope
No "Scope inheritance"
No "Scope Events"
Element Reference
<button (click)="timer.start()">Start</button>
<button (click)="timer.stop()">Stop</button>
<div class="seconds">{{timer.seconds}}</div>
<countdown-timer #timer></countdown-timer>ViewChild(ren)
export class ParentComponent implements AfterViewInit {
@ViewChild(CountdownTimerComponent)
private timerComponent: CountdownTimerComponent;
ngAfterViewInit() {
// Timer component is available from now on
console.log(this.timeComponent.seconds);
}
start() {
this.timerComponent.start();
}
}Observables
RxJS 5
Component Bindings
Input Binding
@Component({ ... })
export class ContactsListComponent {
@Input() contacts: ContactModel[];
}<contacts-list
[contacts]="myContacts">
</contacts-list>Input Changes
$scope.$watch(...) from Angular 1
@Component({ ... })
export class MyComponent {
_name: string;
@Input() set name(name: string) {
this._name = (name && name.trim());
}
get name() {
return this._name;
}
}@Component({ ... })
export class ContactFormComponent
implements OnChanges {
@Input() contact: ContactModel;
ngOnChanges(changes) {
if (changes.contact) {
this.initFormModel();
}
}
}Setter:
Lifecycle hooks:
Output Binding
@Component({ ... })
export class ContactsToolbarComponent {
@Output() add = new EventEmitter();
addAction() {
this.add.emit();
}
}<contacts-toolbar
(add)="createContact()">
</contacts-toolbar>Two-way Binding
Banana in a box:
Convention: contactChange($event)
[(contact)]="myContact"Let's build a component hierarchy!
Routing
Don't work:
@Input() and @Output()
Element Reference
@ViewChild()
Works:
State in URL Params
State in Shared Service
EventEmitters in Shared Service
Shared Service
@Injectable()
export class ContactsService {
private contactsSource =
new BehaviorSubject<ContactModel[]>(undefined);
contacts$ = this.contactsSource.asObservable();
updateContacts(contacts: ContactModel[]) {
this.contactsSource.next(contacts);
}
}Holds data/state
Scope of service: parent component
BehaviorSubject caches result
Move Outputs to Service
@Injectable()
export class ContactsService {
destroy: EventEmitter<ContactModel> =
new EventEmitter<ContactModel>();
}@Component({...})
export class ContactPanelComponent {
contact: ContactModel;
constructor(private service: ContactsService) {}
destroy() {
this.service.destroy.emit(this.contact);
}
}@Component({...})
export class ContactsComponent {
constructor(service: ContactsService) {
service.destroy.subscribe(contact => {
this.destroy(contact);
})
}
destroy() {...}
}Let's do some routing!
So What?
Alternatives?
ng-redux
ngrx
...
Thanks!
Attribution: Mir Shuttle diagram by Orionist (CC BY-SA 3.0)
Practical Component Communication with AngularJS & Angular 2
By Mathis Hofer
Practical Component Communication with AngularJS & Angular 2
- 1,541