Component Architecture
Our previous CounterComponent
example is called a smart component - it knew about Redux, the structure of the state and the actions itneeded to call. In theory you can drop this component into any area of your application and just let it work, but it will be tightly bound to that specific slice of state and those specific actions.
For example, what if we wanted to have multiple counters tracking different things on the page? Or how about counting the number of red clicks vs blue clicks?
To help make components more generic and reusable, it's worth trying to separatethem into container components and presentational components.
Container Components | Presentational Components | |
---|---|---|
Location | ||
——- | ||
Top level, route handlers | Middle and leaf components | |
Aware of Redux | ||
——- | ||
Yes | No | |
To read data | ||
——- | ||
Subscribe to Redux state | Read state from @Input properties | |
To change data | ||
——- | ||
Dispatch Redux actions | Invoke callbacks from @Output properties |
Keeping this in mind, let's refactor our CounterComponent
to be a presentational component.
Modifying AppComponent to become a smart component
First, let's modify our top-level application component to use the CounterService
and CounterActions
, just as CounterComponent
did:
app/app.component.ts
import {Component} from '@angular/core';
import {Observable} from 'rxjs';
import {Counter} from '../../models/counter';
import {CounterService} from '../../services/counter.service';
import {CounterActions} from '../../store/counter/counter.actions';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
counter$: Observable<Counter>;
constructor(
counterService: CounterService,
public actions: CounterActions
) {
this.counter$ = counterService.getCounter();
}
}
Now our AppComponent
is a smart-component, because it's aware of Redux, it's presence in the application state and the underlying services. As with previous examples, we can use the async
pipe to obtain the most recent counter
value and pass it along to other components within the template.
And while we haven't looked at the @Output()
's on CounterComponent
just yet, we'll want to delegate those events to our action creators in CounterActions
.
app/app.component.html
<counter [counter]="counter$ | async"
(onIncrement)="actions.increment()"
(onDecrement)="actions.decrement()"
(onReset)="actions.reset()">
</counter>
Modifying CounterComponent to become a presentation component
In turn, we need to make the CounterComponent
from a smart component into a dumb component. For this, we will pass the data into the component using @Input
properties and click events using @Output()
properties, removing the use of CounterService
and CounterActions
entirely.
app/counter/counter.component.ts
import {Component, Input, EventEmitter, Output} from '@angular/core';
import {Counter} from '../../models/counter';
@Component({
selector: 'counter',
templateUrl: './counter.component.html'
})
export class CounterComponent {
@Input()
counter: Counter;
@Output()
onIncrement: EventEmitter<void> = new EventEmitter<void>();
@Output()
onDecrement: EventEmitter<void> = new EventEmitter<void>();
@Output()
onReset: EventEmitter<void> = new EventEmitter<void>();
}
Our child components become much simpler and testable, because we don't have to use the async
pipe to work with our state, which removes a lot of pain when dealing with lots of @Input
's or the need to use complex expressions with Observable
's.
We can also now simply use core Angular features to emit values whenever a click event happens:
app/counter/counter.component.html
<p>
Clicked: {{counter.currentValue}} times
<button (click)="onIncrement.emit()">+</button>
<button (click)="onDecrement.emit()">-</button>
<button (click)="onReset.emit()">Reset</button>
</p>
We now have a nicely-reusable presentational component with no knowledge ofRedux or our application state.
原文: https://angular-2-training-book.rangle.io/handout/state-management/ngrx/component_architecture.html