W3P Kafi #3, 02.11.2016
Mathis Hofer, hofer@puzzle.ch
This is not about Flux* architectures etc.
Desktop GUI Widgets
Component-based software engineering (CBSE)
Smart vs. Dumb Components
Properties flow down; actions flow up
Unidirectional Dataflow
Context where the model is stored so that
controllers, directives and expressions
can access it.
Two-way databinding
Everything global!
let scope1 = $rootScope.$new(),
scope2 = scope1.$new();angular.module('blog', [])
.component('articleList', () => {
return {
scope: true
};
});Access parent's properties/functions
No encapsulation!
angular.module('blog', [])
.component('articleList', () => {
return {
scope: {}
};
});Default for 1.5+ Components
How to input/output?
1-way binding '<' (1.5+)
2-way binding '='
<contacts-list
contacts="vm.myContacts">
</contacts-list>angular.module('app')
.component('contactsList', {
bindings: {
contacts: '<'
},
...
});function ContactPanelController($scope) {
$scope.$watch('contact', function(newContact, oldContact) {
initFormModel();
});
}better: Component API $onChanges
<contacts-toolbar
add="vm.createContact()">
</contacts-toolbar>angular.module('app')
.component('contactsToolbar', {
bindings: {
add: '&'
},
...
});
function ToolbarController() {
var vm = this;
vm.addAction = function() {
vm.add();
}
}Open in new tab
Reload (or reopen browser)
Share link
Bookmark
…
Doesn't work:
Input/Output Bindings (<, =, &)
Works:
State in URL Params
Scope Events
Require Controller
State in Shared Service
$scope.$broadcast()
$scope.$emit()
Access parent component's controller:
angular.module('app')
.component('contactForm', {
require: '^^contacts'
...
});
function ContactFormController($element) {
var contactsController = $element.controller('contacts');
}Holds data/state
How to handle async?
Use Scope Events
or require controller
No $rootScope
No "Scope inheritance"
No "Scope Events"
<button (click)="timer.start()">Start</button>
<button (click)="timer.stop()">Stop</button>
<div class="seconds">{{timer.seconds}}</div>
<countdown-timer #timer></countdown-timer>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();
}
}RxJS 5
@Component({ ... })
export class ContactsListComponent {
@Input() contacts: ContactModel[];
}<contacts-list
[contacts]="myContacts">
</contacts-list>$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:
@Component({ ... })
export class ContactsToolbarComponent {
@Output() add = new EventEmitter();
addAction() {
this.add.emit();
}
}<contacts-toolbar
(add)="createContact()">
</contacts-toolbar>Banana in a box:
Convention: contactChange($event)
[(contact)]="myContact"Don't work:
@Input() and @Output()
Element Reference
@ViewChild()
Works:
State in URL Params
State in Shared Service
EventEmitters in 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
@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() {...}
}ng-redux
ngrx
...
Attribution: Mir Shuttle diagram by Orionist (CC BY-SA 3.0)