-
Notifications
You must be signed in to change notification settings - Fork 6
Bang TLDR
Chau Tran edited this page Jan 20, 2022
·
6 revisions
There are two approaches: Component and Component Provider
- Use
state()
to create your Component's state with aProxy
- Use
snapshot()
to read the state fromProxy
- Use
effect()
to invoke and clean up side-effects on state changes. - Wrap your template with
*stateful
directive to make theProxy
aware of Change Detection and Component life-cycle. - RULE OF THUMB: Read from
snapshot
, write tostate
interface CounterState {
count: number;
incrementCount: number;
decrementCount: number;
}
@Component({
template: `
<ng-container *stateful="state; let snapshot">
<button (click)="onDecrement()">-</button>
<p>{{snapshot.count}}</p>
<button (click)="onIncrement()">+</button>
<p>You have clicked increment: {{snapshot.incrementCount}}</p>
<p>You have clicked decrement: {{snapshot.decrementCount}}</p>
</ng-container>
`
})
export class CounterComponent implements OnInit {
state = state<CounterState>({count: 0, incrementCount: 0, decrementCount: 0});
ngOnInit() {
effect(this.state, ['count'], () => {
// execute some side-effect when "count" changes
const sub = interval(1000)
.pipe(map(tick => tick + 1))
.subscribe((tick) => {
console.log(`It has been ${tick}s since the last time you changed "count"`);
});
return () => {
// clean up side-effect with the previous "count" value
// clean up function is invoked the last time when Component's destroyed
sub.unsubscribe();
}
});
}
onIncrement() {
const { count, incrementCount } = snapshot(this.state);
this.state.count = count + 1;
this.state.incrementCount = incrementCount + 1;
}
onDecrement() {
const { count, decrementCount } = snapshot(this.state);
this.state.count = count - 1;
this.state.decrementCount = decrementCount + 1;
}
}
- Have your
Injectable
extendingState<TState>
- Different from Component approach, call
super(initialState)
to initialize the state - Provide the
Injectable
in the component'sproviders
array - Inject the
Injectable
in the component to use - Wrap
state
fromInjectable
with*stateful
interface CounterState {
count: number;
incrementCount: number;
decrementCount: number;
}
@Injectable()
export class CounterStore {
state = state<CounterState>({count: 0, incrementCount: 0, decrementCount: 0})
setupInterval() {
effect(this.state, ['count'], () => {
const sub = interval(1000)
.pipe(map(tick => tick + 1))
.subscribe((tick) => {
console.log(`It has been ${tick}s since the last time you changed "count"`);
});
return () => {
sub.unsubscribe();
}
});
}
increment() {
const { count, incrementCount } = snapshot(this.state);
this.state.count = count + 1;
this.state.incrementCount = incrementCount + 1;
}
decrement() {
const { count, decrementCount } = snapshot(this.state);
this.state.count = count - 1;
this.state.decrementCount = decrementCount + 1;
}
}
@Component({
template: `
<ng-container *stateful="store.state; let snapshot">
<button (click)="store.decrement()">-</button>
<p>{{snapshot.count}}</p>
<button (click)="store.increment()">+</button>
<p>You have clicked increment: {{snapshot.incrementCount}}</p>
<p>You have clicked decrement: {{snapshot.decrementCount}}</p>
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [TodoStore]
})
export class CounterComponent implements OnInit {
constructor(public store: TodoStore) {
}
ngOnInit() {
this.store.setupInterval();
}
}