2016-12-06 92 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