2016-12-06 45 views
4

我目前正在使用Redux(ngrx)和RxJS(主要用于学习目的)构建Angular 2应用,但它仍然有点(说至少)让我感到困惑。Angular 2使用ngrx + RxJS订阅多个组件/路由来填充商店

我试图实现一个“/项目”的路线,以及一个“/项目/:ID”的路线。在这两种情况下,我的行为都是通过HTTP请求获取所需的数据。

目前,如果我导航到“项目”(通过导航的URL或ajax调用),它将从服务器获取所有15个左右的项目并将其添加到Redux上的“项目”存储中。现在,如果我现在尝试从一个特定的项目获得(来自浏览器的搜索栏 - >“本地主机:3000 /项目/ 2”,例如)只会取一个,这就是我想要的东西,并把它放在但是如果我从那里导航到“项目”部分,它将只打印位于商店中的一个项目。

我想要完成的任务如下:

  • 如果我进入“/项目”,然后再取出,并把所有的结果在商店。
  • 如果上述情况满足,我浏览到一个特定的项目从那里,使用链接标签,我想检查商店对于具有特定ID并返回该项目。
  • 如果我从获得“/项目/:id为”直接,我只想获取特定项目,并将其放置在店内。
  • 如果紧接着上述观点发生了,我希望能够浏览到“/项目”,通过我的菜单或其他任何链接,获取的所有项目,并更新我的“项目”存储所有的项目(不只是一个已经从先前的点)
  • 存在,我可能会丢失尊重上述

我想做到这一点的一种高效,高性能和优雅的方式,任何其他逻辑方案。

我现在,我相信,至少从两个地方订阅了相同的Observable,我不认为这是正确的方法。最重要的是,我仍然没能得到我想要,如果我从一开始在:第一条路线,然后定位到“/项目”路线“/项目/ ID”。

这里的,我认为相关的代码:

projects.directive.ts

import { Component, OnInit } from '@angular/core'; 
import { ProjectsService } from '../shared/services/projects.service'; 
import { Observable } from 'rxjs/Observable'; 
import { Project } from '../../models/project.model'; 

@Component({ 
    selector: 'projects', 
    templateUrl: './projects.html' 
}) 
export class Projects implements OnInit { 
    private projects$: Observable<Project[]> 

    constructor(private projectsService: ProjectsService) {} 

    ngOnInit() { 
    this.projectsService.findProjects(); 
    } 
} 

projectOne.directive.ts

import { Component, OnInit } from '@angular/core'; 
import { Params, ActivatedRoute } from '@angular/router'; 
import { Observable } from 'rxjs/Observable'; 
import { ProjectsService } from '../../shared/services/projects.service'; 
import { Project } from '../../../models/project.model'; 

@Component({ 
    selector: 'projectOne', 
    templateUrl: './projectOne.html' 
}) 
export class ProjectOneComponent implements OnInit { 
    private projects$: Observable<Project[]> 

    constructor(private route: ActivatedRoute, private projectsService: ProjectsService) {} 

    ngOnInit() { 
    this.route.params.subscribe((params: Params) => { 
     this.projectsService.findProjects(params['id']) 
    }); 
    } 
} 

*有些东西这里要注意:我正在订阅this.route.params,它订阅了另一个Observable,我是否需要将它平坦化?这个概念还是甘拜下风

projects.html

<section> 
    <article *ngFor="let project of projectsService.projects$ | async"> 
    <p>{{project?._id}}</p> 
    <p>{{project?.name}}</p> 
    <img src="{{project?.img}}" /> 
    <a routerLink="{{project?._id}}">See more</a> 
    </article> 
</section> 

*在这里,我想作出这样我还使用projectsService笔记。项目$ |异步打印上我很积极也影响迭代的结果...

projects.service.ts

import { Http } from '@angular/http'; 
import { Injectable } from '@angular/core'; 
import { Observable } from 'rxjs/Observable'; 
import 'rxjs/add/operator/map'; 
import { Store } from '@ngrx/store'; 
import { Project } from '../../../models/project.model'; 
import { AppStore } from '../../app.store'; 
import { ADD_PROJECTS } from '../../../reducers/projects.reducer'; 

@Injectable() 
export class ProjectsService { 
    public projects$: Observable<Project[]>; 

    constructor(private _http: Http, private store: Store<AppStore>){ 
    this.projects$ = store.select<Project[]>('projects'); 
    } 

    fetchProjects(id) { 
    return this._http.get(`/api/projects?id=${id}`) 
    .map(res => res.json()) 
    .map(({projectsList}) => ({ type: ADD_PROJECTS, payload: projectsList })) 
    .subscribe(action => this.store.dispatch(action)); 
    } 

    findProjects(id: Number = 0) { 
    this.projects$.subscribe(projects => { 
     if (projects.length) { 
     if (projects.length === 1) { 
      return this.fetchProjects(); 
     } 
     } else { 
     return this.fetchProjects(id ? id : '') 
     } 
    }) 
    } 
} 

*我猜每次我称之为“findProjects时间“函数我正在订阅Observable。不好,是吧?

*另外,在该电流设置每当我直接进入“/项目/:ID”这似乎是两次执行fetchProjects功能(我想,备受控制台日志记录)。从本质上讲,内findProjects的this.projects $认购跳跃和获取与相应的ID项目,但随后再次进入并获取所有其他项目,最后它只是“消失”? 它为什么会自己调用,或者第二个调用来自哪里?

projects.reducer.ts

import { Project } from '../models/project.model'; 
import { ActionReducer, Action } from '@ngrx/store'; 

export const ADD_PROJECTS = 'ADD_PROJECTS'; 

export const projects: ActionReducer<Project[]> = (state: Project[] = [], action: Action) => { 
    switch (action.type) { 
    case ADD_PROJECTS: 
     return action.payload; 
    default: 
     return state; 
    } 
}; 

*这是所有的减速有暂时因为我仍然超贴在休息。

不管怎么说,我要感谢大家提前。如果有什么不清楚或您需要更多信息,请告诉我。我知道这不仅涵盖了一件事,而且可能超级简单,或者根本没有,但是我非常渴望获得尽可能多的帮助,因为我真的被困在这里......再次感谢!

回答

4

一般而言,您的代码看起来“没问题”。有几件事情,我已经注意到,虽然:

  • 你正在做的东西,如检查项目长度等。可能会保存一个休息电话 - 根据我的经验,我会说,如果有大量数据传输或涉及繁重的服务器计算,或者如果您的应用程序有几千个用户,这种类型的东西是值得的你真的希望优化您的服务器性能的每一个最后一位,其他明智的:只是再次读取该数据OR你分割你的店为allProjects: Projects[]这是不重新取出并selectedProject: Project如果没有内allProjects
  • 发现这是只取
  • 至于文件命名而言,尽量避免和转换为大写命名部件组件而不是指令
  • 由于您使用ngrx为您的商店,你可能想看看ngrx/effects作为替代从服务中的调度操作 - >这部分将是很随意的,但在完美 NGRX-应用数据服务甚至不知道有商店。

话虽这么说,这里有一些代码,改进使之更制定一个面向NGRX - 应用程序 - 但我还是建议你看一看官方ngrx-example-app这是很好

projects.component。TS

@Component({ 
    selector: 'projects', 
    templateUrl: './projects.html' 
}) 
export class Projects { 
    private projects$: Observable<Project[]> = his.store 
     .select<Project[]>('projects') 
     .map(projects => projects.all) 

    constructor(private store: Store<AppStore>) { 
     store.dispatch({type: ProjectActions.LOAD_ALL}); 
    } 
} 

projects.component.html

<section> 
    <article *ngFor="let project of projects$ | async"> 
    <!-- you don't need to use the questionmark here (project?.name) if you have something like "undefined" or "null" in your array, then the problem lies somewhere else --> 
    <p>{{project._id}}</p> 
    <p>{{project.name}}</p> 
    <img src="{{project.img}}" /> 
    <a routerLink="{{project._id}}">See more</a> 
    </article> 
</section> 

project.component.ts

@Component({ 
    selector: 'projectOne', 
    templateUrl: './projectOne.html' 
}) 
export class ProjectOneComponent implements OnInit { 
    // project$ is only used with the async-pipe 
    private project$: Observable<Project[]> = this.route.params 
     .map(params => params['id']) 
     .switchMap(id => this.store 
      .select<Project[]>('projects') 
      .map(projects => projects.byId[id]) 
      .filter(project => !!project) // filter out undefined & null 
    ) 
     .share(); // sharing because it is probably used multiple times in the template 

    constructor(private route: ActivatedRoute, 
       private store: Store<AppStore>) {} 

    ngOnInit() { 
    this.route.params 
     .take(1) 
     .map(params => params['id']) 
     .do(id => this.store.dispatch({type: ProjectActions.LOAD_PROJECT, payload: id}) 
     .subscribe(); 
    } 
} 

project.service.ts =>不知道关于商店

@Injectable() 
export class ProjectsService { 
    constructor(private _http: Http){} 

    fetchAll() { 
     return this._http.get(`/api/projects`) 
      .map(res => res.json()); 
    } 

    fetchBy(id) { 
    return this._http.get(`/api/projects?id=${id}`) 
     .map(res => res.json()); 
    } 
} 

project.effects.ts

@Injectable() 
export class ProjectEffects { 
    private projects$: Observable<Project[]> = his.store 
     .select<Project[]>('projects') 
     .map(projects => projects.all); 

    constructor(private actions$: Actions, 
       private store: Store<AppStore>, 
       private projectsService: ProjectsService){} 

    @Effect() 
    public loadAllProjects$: Observable<Action> = this.actions$ 
    .ofType(ProjectActions.LOAD_ALL) 
    .switchMap(() => this.projectsService.fetchAll() 
     .map(payload => {type: ProjectActions.ADD_PROJECTS, payload}) 
); 

    @Effect() 
    public loadSingleProject$: Observable<Action> = this.actions$ 
    .ofType(ProjectActions.LOAD_PROJECT) 
    .map((action: Action) => action.payload) 
    .withLatestFrom(
     this.projects$, 
     (id, projects) => ({id, projects}) 
    ) 
    .flatMap({id, projects} => { 
     let project = projects.find(project => project._id === id); 
     if (project) { 
      // project is already available, we don't need to fetch it again 
      return Observable.empty(); 
     } 

     return this.projectsService.fetchBy(id); 
    }) 
    .map(payload => {type: ProjectActions.ADD_PROJECT, payload}); 
} 

projects.reducer.ts

export interface ProjectsState { 
    all: Project[]; 
    byId: {[key: string]: Project}; 
} 

const initialState = { 
    all: [], 
    byId: {} 
}; 

export const projects: ActionReducer<ProjectsState> = (state: ProjectsState = initialState, action: Action) => { 
    switch (action.type) { 
    case ADD_PROJECTS: 
     const all: Project[] = action.payload.slice(); 
     const byId: {[key: string]: Project} = {}; 
     all.forEach(project => byId[project._id] = project); 

     return {all, byId}; 
    case ADD_PROJECT: 
     const newState: ProjectState = { 
      all: state.slice(), 
      byId: Object.assing({}, state.byId) 
     }; 
     const project: Project = action.payload; 
     const idx: number = newState.all.findIndex(p => p._id === project._id); 
     if (idx >= 0) { 
      newState.all.splice(idx, 1, project); 
     } else { 
      newState.all.push(project); 
     } 
     newState.byId[project._id] = project; 

     return newState; 
    default: 
     return state; 
    } 
}; 

正如你可以看到这个mightbe稍多的代码,但只有在中央的地方,代码可以很容易地重用 - 组件变得更加精简。

在一个理想的应用程序,你也将有一个附加层ProjectsComponentProjectOneComponent,像ProjectsRouteComponentSingleProjectRoute,这将只包含这样的一个模板:<projectOne project="project$ | async"></projectOne>这会从商店或任何其他的任何知识释放ProjectOneComponent ,它只是包含一个简单的输入:

@Component({ 
    selector: 'projectOne', 
    templateUrl: './projectOne.html' 
}) 
export class ProjectOneComponent implements OnInit { 
    @Input("project") 
    project: Project; 
} 
+0

这看起来很神奇!非常感谢您花时间回复。让我看看它并按原样进行测试或修改它,如果对此有任何疑问,我会通知您。我很想将你的答案标记为正确的,因为这看起来像我正在寻找的东西(我还会检查**效果**),但让我稍微玩一下吧,了解这些变化。 – deathandtaxes