Skip to content

Bang TLDR

Chau Tran edited this page Jan 20, 2022 · 6 revisions

There are two approaches: Component and Component Provider

Component

  • Use state() to create your Component's state with a Proxy
  • Use snapshot() to read the state from Proxy
  • Use effect() to invoke and clean up side-effects on state changes.
  • Wrap your template with *stateful directive to make the Proxy aware of Change Detection and Component life-cycle.
  • RULE OF THUMB: Read from snapshot, write to state
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;
    }
}

Component Provider

  • Have your Injectable extending State<TState>
  • Different from Component approach, call super(initialState) to initialize the state
  • Provide the Injectable in the component's providers array
  • Inject the Injectable in the component to use
  • Wrap state from Injectable 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();
    }
}
Clone this wiki locally