Angular - Ngrx - 来自多个来源的更新和组件渲染
Angular - Ngrx - Updates from multiple sources and component rendering
我遇到了以下问题,我确实需要一些建议来最好地解决这个问题。
在我们的应用程序中,我们确实有一个 容器组件,如下所示:
ArchiveAllComponent
export class ArchiveAllComponent implements OnInit {
archiveAllViewModel$: Observable<ArchiveAllViewModel>
constructor(
private store: Store<ApplicationState>,
private votingService: VotingService
) { }
ngOnInit() {
// Subscribe to any changes in the category and voting entities
this.archiveAllViewModel$ = this.store.pipe(
select(selectCategoriesWithVoting),
map((categories: Category[]) => mapToArchiveAllViewModel(categories))
)
// Load all categories
this.votingService.getCategories().subscribe((categories: Category[]) => {
this.store.dispatch(new LoadCategoryAction(categories));
});
// Load all votings
this.votingService.getVotings().subscribe((votings: Voting[]) => {
this.store.dispatch(new LoadVotingAction(votings));
});
}
}
呈现此组件后,将向不同的 API 执行两个 HTTP GET 请求。
对于每个请求,一个 action 被 dispatched 到 store。
减速器
// Reducer function
export function entityReducer(currentState: Entities, action: EntityActionsUnion): Entities {
switch (action.type) {
case EntityActionTypes.LOAD_CATEGORIES:
return merge({}, currentState, {categories : action.categories});
case EntityActionTypes.LOAD_VOTINGS:
return merge({}, currentState, {votings : action.votings});
default:
return currentState;
}
}
选择器
export function selectCategoryEntity(state: ApplicationState) {
return state.entities.categories;
}
export function selectVotingEntity(state: ApplicationState) {
return state.entities.votings;
}
export const selectCategoriesWithVoting = createSelector(
selectCategoryEntity,
selectVotingEntity,
(categoryEntities: Category[], votingEntities: Voting[]) => {
if (categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0) {
let categories = categoryEntities.slice();
votingEntities.forEach(voting => {
if (voting.categoryId) {
let category = categories.find(x => x.id == voting.categoryId);
if(!category.votings)
{
category.votings = [];
}
category.votings.push(voting);
}
});
return categories;
}
return [];
}
);
然后将 archiveAllViewModel$
可观察对象传递给一些子组件以相应地呈现 HTMl。
乍一看这似乎有效,即使您进行刷新,也会执行以下操作:
- 页面刷新
getCategories()
+ 动作/reducer 被触发
getVotings()
+ Action/reducer 被触发
<archiveElement>
+ 第一个子元素被正确渲染
<archiveElement>
+ 第二个子元素被正确渲染
<archiveElement>
+ 第三个子元素被正确渲染
一旦我开始离开组件并通过客户端站点路由返回到同一路由,问题就开始出现了。
<a routerLink="/someotherpage" routerLinkActive="active" mat-button>Other Page</a>
返回同一组件:
<a routerLink="/archiveAll" routerLinkActive="active" mat-button>Archive</a>
现在与整页刷新相比,所有内容都呈现两次:
- 导航离开
- 向后导航
<archiveElement>
<archiveElement>
<archiveElement>
getCategories()
+ 动作/reducer 被触发
<archiveElement>
<archiveElement>
<archiveElement>
getVotings()
+ Action/reducer 被触发
<archiveElement>
<archiveElement>
<archiveElement>
因此 每个子组件现在在页面上出现两次。
疑难解答
createSelector
现在对每个 http 请求执行一次,因为谓词 categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0
现在 不再仅对第二个 http 请求 有效。
我的问题
- 解决此类问题的最佳做法是什么?
- 有没有接线员可以轻松帮我解决这个问题?
这里的问题是 this.votingService.getCategories()
和 this.votingService.getVotings()
上的订阅没有被销毁。这意味着每次发出请求时它都会重新加载。
要解决此问题,您可以销毁 ngDestroy
上的订阅,但与其这样做,我建议您查看 @ngrx/effects 来处理所有副作用(例如 http 请求).
与Angular无关。经过进一步调试后,我意识到问题出在选择器函数中。
我重构了它,现在似乎可以工作了!
export const selectCategoriesWithVoting = createSelector(
selectCategoryEntity,
selectVotingEntity,
(categoryEntities: Category[], votingEntities: Voting[]) => {
if (categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0) {
let categories = categoryEntities.slice();
votingEntities.forEach(newVoting => {
if (newVoting.categoryId) {
let category = categories.find(x => x.id == newVoting.categoryId);
if(!category.votings)
{
category.votings = [];
}
let oldVotingIndex = category.votings.findIndex(x => x.id == newVoting.id);
if(oldVotingIndex != -1)
{
category.votings[oldVotingIndex] = newVoting;
} else {
category.votings.push(newVoting);
}
}
});
return categories;
}
return [];
}
);
我遇到了以下问题,我确实需要一些建议来最好地解决这个问题。
在我们的应用程序中,我们确实有一个 容器组件,如下所示:
ArchiveAllComponent
export class ArchiveAllComponent implements OnInit {
archiveAllViewModel$: Observable<ArchiveAllViewModel>
constructor(
private store: Store<ApplicationState>,
private votingService: VotingService
) { }
ngOnInit() {
// Subscribe to any changes in the category and voting entities
this.archiveAllViewModel$ = this.store.pipe(
select(selectCategoriesWithVoting),
map((categories: Category[]) => mapToArchiveAllViewModel(categories))
)
// Load all categories
this.votingService.getCategories().subscribe((categories: Category[]) => {
this.store.dispatch(new LoadCategoryAction(categories));
});
// Load all votings
this.votingService.getVotings().subscribe((votings: Voting[]) => {
this.store.dispatch(new LoadVotingAction(votings));
});
}
}
呈现此组件后,将向不同的 API 执行两个 HTTP GET 请求。
对于每个请求,一个 action 被 dispatched 到 store。
减速器
// Reducer function
export function entityReducer(currentState: Entities, action: EntityActionsUnion): Entities {
switch (action.type) {
case EntityActionTypes.LOAD_CATEGORIES:
return merge({}, currentState, {categories : action.categories});
case EntityActionTypes.LOAD_VOTINGS:
return merge({}, currentState, {votings : action.votings});
default:
return currentState;
}
}
选择器
export function selectCategoryEntity(state: ApplicationState) {
return state.entities.categories;
}
export function selectVotingEntity(state: ApplicationState) {
return state.entities.votings;
}
export const selectCategoriesWithVoting = createSelector(
selectCategoryEntity,
selectVotingEntity,
(categoryEntities: Category[], votingEntities: Voting[]) => {
if (categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0) {
let categories = categoryEntities.slice();
votingEntities.forEach(voting => {
if (voting.categoryId) {
let category = categories.find(x => x.id == voting.categoryId);
if(!category.votings)
{
category.votings = [];
}
category.votings.push(voting);
}
});
return categories;
}
return [];
}
);
然后将 archiveAllViewModel$
可观察对象传递给一些子组件以相应地呈现 HTMl。
乍一看这似乎有效,即使您进行刷新,也会执行以下操作:
- 页面刷新
getCategories()
+ 动作/reducer 被触发getVotings()
+ Action/reducer 被触发<archiveElement>
+ 第一个子元素被正确渲染<archiveElement>
+ 第二个子元素被正确渲染<archiveElement>
+ 第三个子元素被正确渲染
一旦我开始离开组件并通过客户端站点路由返回到同一路由,问题就开始出现了。
<a routerLink="/someotherpage" routerLinkActive="active" mat-button>Other Page</a>
返回同一组件:
<a routerLink="/archiveAll" routerLinkActive="active" mat-button>Archive</a>
现在与整页刷新相比,所有内容都呈现两次:
- 导航离开
- 向后导航
<archiveElement>
<archiveElement>
<archiveElement>
getCategories()
+ 动作/reducer 被触发<archiveElement>
<archiveElement>
<archiveElement>
getVotings()
+ Action/reducer 被触发<archiveElement>
<archiveElement>
<archiveElement>
因此 每个子组件现在在页面上出现两次。
疑难解答
createSelector
现在对每个 http 请求执行一次,因为谓词 categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0
现在 不再仅对第二个 http 请求 有效。
我的问题
- 解决此类问题的最佳做法是什么?
- 有没有接线员可以轻松帮我解决这个问题?
这里的问题是 this.votingService.getCategories()
和 this.votingService.getVotings()
上的订阅没有被销毁。这意味着每次发出请求时它都会重新加载。
要解决此问题,您可以销毁 ngDestroy
上的订阅,但与其这样做,我建议您查看 @ngrx/effects 来处理所有副作用(例如 http 请求).
与Angular无关。经过进一步调试后,我意识到问题出在选择器函数中。
我重构了它,现在似乎可以工作了!
export const selectCategoriesWithVoting = createSelector(
selectCategoryEntity,
selectVotingEntity,
(categoryEntities: Category[], votingEntities: Voting[]) => {
if (categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0) {
let categories = categoryEntities.slice();
votingEntities.forEach(newVoting => {
if (newVoting.categoryId) {
let category = categories.find(x => x.id == newVoting.categoryId);
if(!category.votings)
{
category.votings = [];
}
let oldVotingIndex = category.votings.findIndex(x => x.id == newVoting.id);
if(oldVotingIndex != -1)
{
category.votings[oldVotingIndex] = newVoting;
} else {
category.votings.push(newVoting);
}
}
});
return categories;
}
return [];
}
);