Angular1 veteran here, trying to learn angular2. I have a component with a state service (MyState
below) that gets injected into itself and its subcomponents. I want to watch for changes on the service's state, then update another member in the top-level component, as follows:
import { Component, Input } from '@angular/core';
class Item {
constructor(public name: string) {}
}
class MyState {
selectedItems: Item[] = [];
addItem(item: Item) {
this.selectedItems.push(item);
}
}
// subcomponent
@Component({
selector: '[item]',
template: `
<!-- state changed from subcomponent: -->
<button (click)="state.addItem(model)">
select item {{model.name}}
</button>
`
})
class ItemComponent {
@Input() model: Item;
constructor(private state: MyState){}
}
// main component
@Component({
selector: 'items',
template: `
<h1> Available Items: </h1>
<ul>
<li *ngFor="let item of items" item [model]="item"></li>
</ul>
<h1> Added Items: </h1>
<p>{{ addedItemsString }}</p>
`,
providers: [MyState],
directives: [ItemComponent]
})
class ListComponent {
@Input() items: Item[];
private addedItemsString: string = '';
constructor(private state: MyState) {
// listen for changes to this.state.selectedItems to
// call this.updateAddedItemsString
}
updateAddedItemsString () {
this.addItemsString = this.state.selectedItems.map(i => i.name).join(', ');
}
}
My question is: how should I implement the pseudo-code in the ListComponent constructor?
Granted, the addItemsString
member is contrived and could certainly be done in the template itself, but for the sake of my question assume that updateAddedItemsString
could do a lot more complicated stuff that updates a separate member of ListComponent
.
Thanks in advance!
Use a BehaviorSubject
<Item[]>
in your service, and have your ListComponent subscribe to changes to that observable array:
class MyState {
private _selectedItems: Item[] = [];
private _selectedItemsSource = new BehaviorSubject<Item[]>(this._selectedItems);
selectedItems$ = this._selectedItemsSource.asObservable();
addItem(item: Item) {
this._selectedItems.push(item);
this._selectedItemsSource.next(this._selectedItems);
}
}
@Component({
selector: 'items',
template: `<h1> Available Items: </h1>
<ul>
<li *ngFor="let item of items" item [model]="item"></li>
</ul>
<h1> Added Items: </h1>
<div>{{ addedItemsString }}</div>`,
providers: [MyState],
directives: [ItemComponent]
})
class ListComponent {
@Input() items: Item[];
private addedItemsString: string = '';
constructor(private state: MyState) {}
ngOnInit() {
this.subscription = this.state.selectedItems$.subscribe(
items => this.addedItemsString = items.map(i => i.name).join(', '));
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
}
I emit the entire array each time next()
is called. This ensures that any other component that subscribes to the BehaviorSubject later will get the full list. If you don't have that requirement, you could simply emit only new values when addItem()
is called.