我使用Angular 5和NgRX。
我有一个组件,用于定义用于编辑用户设置的表单。 加载表单时,我想在输入字段中设置设置的当前值。
目前,每当加载组件时,我都会调度一个事件来从后端检索这些设置。
class SettingsComponent implements OnInit {
settings$ = this.store.select(fromSettings.getSettings);
constructor(private store: Store<fromSettings.State>) {
}
ngOnInit() {
this.store.dispatch(new SettingsActionTypes.RetrieveSettings());
}
}
采取此行动的效果:
@Effect()
findSettings$ = this.actions$
.ofType(SettingsActionTypes.RetrieveSettings)
.exhaustMap(() =>
this.settingsService
.getSettings() // http call
.map((settings: Settings) => {
return new SettingsFound({settings});
})
.catch(error => of(new SettingsNotFound()))
);
这非常有效,除了每当用户导航到我的SettingsComponent时,都会调用后端。我想在前端完全使用这种状态管理,并且由于后端的状态只会通过前端改变,所以我不想多次检索我的设置。
我知道我可以在我的Angular App组件中调度RetrieveSettings操作,但这意味着无论用户是否需要检索他/她的设置,都会调用后端。
我也可以缓存后端调用,但我希望状态存储中的这个功能......
那么首次加载SettingsComponent时如何执行后端调用?
答案 0 :(得分:2)
如果已经提取了数据,您可以检查ngrx存储并忽略该操作。如果您对数据有某种过期,那么您可以检查它而不仅仅是现有数据。
这样的事情:
@Effect()
findSettings$ = this.actions$
.ofType(SettingsActionTypes.RetrieveSettings)
.withLatestFrom(store.select(...)) // select your state
.filter(([action, state]) => state == null) // if not initialized
.exhaustMap(([action]) => ...); // initialize state
因此,无论何时需要存在状态,您都会继续调度操作。您也可以使用路由解析器或类似的东西来执行调度,而不是将其放入组件中。我喜欢这样做,因为我在同一父路线下有许多需要数据的组件,或者当我需要存在多个状态部分并且不想在许多组件中重复发送时。
或者,您可以创建一个具有对存储的内部引用的服务,并将每个状态片作为可观察对象公开。在访问或订阅时,它将确保状态是水合的。这可能是一个更好的选择,而不是在你的应用程序周围传播关于你的状态是如何补充的逻辑。我发现,如果我将其展开,那么我有时会忘记对其进行初始化,并且我会得到这些奇怪的错误,根据我之前访问过的页面,我可能会在页面上获得所需的数据。
有很多可能解决这个问题的方法,而我个人并没有解决这个问题。我发现订阅状态并发送一个动作以保湿它类似于将我的晚餐订单交给服务员,然后告诉厨房在商店购买哪些食材。它创造了一个概念上的紧密耦合,我作为消费者必须知道我正在消费的是如何组成的。在理想的世界中,仅仅订阅(给出我的订单)的行为将告诉生产者他们在没有消费者/订户的进一步指示的情况下需要做什么。
在ngrx中,制作人似乎在设计上与消费者脱节。类似于消息总线。这有助于支持影响同一片状态的多个演员(api,客户端......)。如果您只有一个远程actor(api)作为生产者,那么将数据缓存在api(shareReplay(1)
)中可能更有意义。
答案 1 :(得分:0)
我有类似的要求,并且能够使用包含state和Observables的服务类来实现它。
当我的组件启动并调用getRegions
加载下拉列表的选项时,我的组件第一次调用getRegions
服务类将转到服务器但是第二次服务被称为它不会去服务器,而是返回存储的值。
<强> 代码 强>
import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { of } from "rxjs/observable/of";
import { Observable } from "rxjs/Observable";
import { DropdownOption } from "./dropdown-option";
const httpOptions = {
headers: new HttpHeaders({
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
})
};
@Injectable()
export class ConfigService {
regions: Array<DropdownOption>;
constructor(private httpClient: HttpClient) {}
getRegions(): Observable<Array<DropdownOption>> {
if (this.regions) {
return of(this.regions);
} else {
return Observable.create(observer => {
this.httpClient
.get<Array<DropdownOption>>("config/regions", httpOptions)
.subscribe(
data => {
this.regions = data;
observer.next(this.regions);
},
err => {
//Handle error
},
() => {
observer.complete();
}
);
});
}
}
}
答案 2 :(得分:0)
使用bygrace的回答让我考虑在组件的初始化中应用相同的原理。
ngOnInit() {
this.settings$
.do(settings => {
if (!settings) {
this.store.dispatch(new SettingsActionTypes.RetrieveSettings());
}
})
.take(1)
.subscribe()
}
虽然我仍然觉得应该有一个更优雅的解决方案来解决这个问题。