Angular-Ngrx-来自多个来源的更新和组件渲染

时间:2018-08-11 09:55:43

标签: javascript angular rxjs ngrx ngrx-store

我遇到了以下问题,我确实需要一些建议来解决这一问题。

在我们的应用程序上,我们有一个容器组件,如下所示:

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请求。

对于每个请求,操作都会发送商店

减速器

// 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$观测值传递给一些子组件,以相应地呈现HTM1。

乍一看似乎可行,即使您进行刷新,也会执行以下操作:

  1. 页面刷新
  2. getCategories() +动作/减速器被触发
  3. getVotings() +动作/减速器被触发
  4. <archiveElement> +第一个子元素正确呈现
  5. <archiveElement> +第二个子元素正确呈现
  6. <archiveElement> +第三个子元素正确呈现

一旦我开始离开该组件并通过客户端站点路由回到相同的路由,就会开始出现问题。

<a routerLink="/someotherpage" routerLinkActive="active" mat-button>Other Page</a>

返回相同的组件:

<a routerLink="/archiveAll" routerLinkActive="active" mat-button>Archive</a>

现在,与整页刷新相比,所有内容都呈现两次:

  1. 远航
  2. 向后导航
  3. <archiveElement>
  4. <archiveElement>
  5. <archiveElement>
  6. getCategories() +动作/减速器被触发
  7. <archiveElement>
  8. <archiveElement>
  9. <archiveElement>
  10. getVotings() +动作/减速器被触发
  11. <archiveElement>
  12. <archiveElement>
  13. <archiveElement>

结果,每个子组件现在在页面上出现两次

问题排查

createSelector现在对每个http请求都执行一次,因为谓词categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0现在不再仅对第二个http请求有效

我的问题

  • 解决此类问题的最佳做法是什么?
  • 有没有能为我轻松解决这个问题的运营商?

2 个答案:

答案 0 :(得分:1)

这里的问题是this.votingService.getCategories()this.votingService.getVotings()上的订阅没有被销毁。这意味着它将在每次发出请求时重新加载。

要解决此问题,您可以销毁ngDestroy上的订阅,但建议您查看@ngrx/effects来处理所有副作用(例如http请求),而不要这样做。

答案 1 :(得分:0)

它与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 [];
    }
);