[英]Angular 2 using ngrx + RxJS subscribing on multiple components/routes to populate store
我目前正在使用Redux(ngrx)和RxJS(主要用于学习目的)构建一个Angular 2应用程序,但它仍然有点(至少可以说)令我困惑。
我正在尝试实现“/ projects”路由,以及“/ projects /:id”路由。 在这两个方面,行为是我发出HTTP请求来获取所需的数据。
目前,如果我导航到“项目”(通过URL或通过导航调用ajax),它将从服务器返回所有15个左右的项目,并将它们添加到Redux上的“项目”存储中。 现在,如果我目前尝试从特定项目(例如,从浏览器的搜索栏 - >“localhost:3000 / projects / 2”)进入,它将只获取一个,这是我想要的并将其放入然后,如果我从那里导航到“项目”部分,它将只打印位于商店中的一个项目。
我想要完成的是以下内容:
我希望以高效,高效和优雅的方式实现这一目标。
我相信,我目前至少从两个地方订阅了同一个Observable,我认为这不是正确的做法。 最重要的是,如果我先从“/ projects:/ id”路线进入,然后导航到“/ projects”路线,我仍然无法得到我想要的结果。
这是我认为相关的代码:
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 $ | async在迭代上打印结果,我非常积极也影响...
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。 不好,对吧?
*此外,每当我直接进入“/ projects /:id”时,使用此当前设置,它似乎执行了两次fetchProjects函数(我通过控制台日志记录得到了很多)。 本质上, findProjects中的this.projects $ subscription跳转并使用相应的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;
}
};
*这是减速机暂时拥有的所有因为我仍然超级坚持其余部分。
无论如何,我想提前感谢你们。 如果有任何不清楚或您需要更多信息,请告诉我。 我知道这不仅包括一件事,而且可能非常容易或者根本没有,但我真的很想获得尽可能多的帮助,因为我真的被困在这里......再次感谢!
通常,您的代码看起来“没问题”。 有一些事情,我注意到了:
allProjects: Projects[]
,它不被重新获取和selectedProject: Project
仅在未找到时获取在allProjects
ngrx
,您可能希望查看ngrx / effects作为从服务中调度操作的替代方法 - >这部分将是非常可选的,但在完美的 ngrx-app数据中 -服务甚至不知道有商店。 话虽这么说,这里有一些代码改进使它更多地面向面向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;
}
};
正如您所看到的,这可能只是稍微多一点的代码,但只能在中心位置,代码可以轻松地重用 - 组件变得更加精简。
在一个理想的应用程序中,您还可以为ProjectsComponent
和ProjectOneComponent
添加一个额外的层,例如ProjectsRouteComponent
和SingleProjectRoute
,它们只包含这样的模板: <projectOne project="project$ | async"></projectOne>
这将释放ProjectOneComponent
来自商店或其他任何知识,它只包含一个简单的输入:
@Component({
selector: 'projectOne',
templateUrl: './projectOne.html'
})
export class ProjectOneComponent implements OnInit {
@Input("project")
project: Project;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.