change - router events angular 6
Angular 2 usando ngrx+RxJS suscribiéndose en múltiples componentes/rutas para poblar la tienda (1)
En general, su código se ve "bien". Sin embargo, hay algunas cosas que he notado:
- Estás haciendo cosas como comprobar el ect de longitud del proyecto. probablemente guarde una llamada de descanso; desde mi experiencia, diría que este tipo de cosas solo valen la pena si se transfieren muchos datos o si hay cálculos pesados del servidor o si su aplicación tiene un par de miles de usuarios y realmente está buscando optimizar hasta el último bit del rendimiento de su servidor, de otro modo: simplemente busque los datos O divide su tienda en
allProjects: Projects[]
que no se vuelven a recopilar yselectedProject: Project
que solo se obtiene si no se encuentra dentro de todos losallProjects
- en lo que se refiere a la nomenclatura de archivos, trate de evitar mayúsculas y nombre el componente de sus componentes y no la directiva .
- Dado que está utilizando
ngrx
para su tienda, es posible que desee echar un vistazo a ngrx / effects como alternativa a las acciones de envío desde su servicio -> esta parte sería muy opcional, pero en la aplicación ngrx perfecta , la información el servicio ni siquiera sabría que hay una tienda.
Dicho esto, aquí hay algunas mejoras de código que lo acercan más a una aplicación orientada a ngrx; sin embargo, aún sugiero que eche un vistazo a la aplicación oficial ngrx-example, que es muy buena :
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 => no sabe sobre la tienda
@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;
}
};
Como puede ver, este podría ser un poco más de código, pero solo en lugares centrales, donde el código se puede reutilizar fácilmente: los componentes se volvieron mucho más sencillos.
En una aplicación ideal, también tendría una capa adicional para ProjectsComponent
y ProjectOneComponent
, algo así como ProjectsRouteComponent
y SingleProjectRoute
, que contendría solo una plantilla como esta: <projectOne project="project$ | async"></projectOne>
esto liberaría la ProjectOneComponent
de cualquier conocimiento de la tienda o cualquier otra cosa, y solo contendría una entrada simple:
@Component({
selector: ''projectOne'',
templateUrl: ''./projectOne.html''
})
export class ProjectOneComponent implements OnInit {
@Input("project")
project: Project;
}
Actualmente estoy compilando una aplicación Angular 2 usando Redux (ngrx) y RxJS (principalmente para fines de aprendizaje), sin embargo, sigue siendo un poco (por decir lo menos) confusa para mí.
Estoy intentando implementar una ruta "/ projects", así como una ruta "/ projects /: id". En ambos casos, el comportamiento es que realizo una solicitud HTTP para obtener los datos requeridos.
Actualmente, si navego a "proyectos" (ya sea por URL o llamada ajax a través de navegación) obtendrá los 15 proyectos más o menos del servidor y los agregará a la tienda de "proyectos" en Redux. Ahora, si actualmente intento ingresar desde un proyecto específico (desde la barra de búsqueda del navegador -> "localhost: 3000 / projects / 2", por ejemplo), solo obtendrá el que sea, que es lo que quiero y lo coloque en el almacenar, sin embargo, si navego a la sección "proyectos" desde allí, solo se imprimirá el proyecto que se encuentra en la tienda.
Lo que quiero lograr es lo siguiente:
- Si entro en "/ projects" primero, entonces busque y coloque todos los resultados en la tienda.
- Si se cumple el caso anterior y navego a un proyecto específico desde allí, utilizando una etiqueta de enlace, quiero verificar en la tienda el artículo que tiene ese ID específico y devolverlo.
- Si ingreso directamente desde "/ projects /: id", quiero buscar ese proyecto específico solo y colocarlo en la tienda.
- Si ocurre el punto inmediatamente anterior, quiero poder navegar a "/ projects", a través de mi menú o cualquier otro enlace, buscar todos los artículos y actualizar mi tienda de "proyectos" con todos los artículos (no solo el que ya existía) del punto anterior)
- Cualquier otro escenario lógico que pueda estar perdiendo respetando lo anterior
Quiero lograr esto de una manera eficiente, performante y elegante.
Actualmente estoy, creo, suscrito al mismo observable desde al menos dos lugares y no creo que ese sea el enfoque correcto. Además de eso, todavía no puedo obtener los resultados que quiero si entro primero de la ruta "/ projects: / id" y luego navego hacia la ruta "/ projects".
Aquí está el código que considero relevante:
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''])
});
}
}
* Algunas cosas para tener en cuenta aquí: me suscribo a this.route.params , que se suscribe a otro Observable, ¿necesito aplanar eso en absoluto o no? El concepto todavía me gana
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>
* Aquí me gustaría señalar que también estoy usando projectsService.projects $ | asincrónico para imprimir los resultados en la iteración que estoy bastante seguro también afecta ...
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 : '''')
}
})
}
}
* Supongo que cada vez que llamo a esa función "findProjects" me suscribo al Observable. No está bien, ¿eh?
* Además, con esta configuración actual cada vez que voy directamente a "/ projects /: id", parece que está ejecutando la función fetchProjects dos veces (eso me pareció mucho por el registro de la consola). Básicamente, la suscripción de this.projects $ dentro de findProjects salta y obtiene el proyecto con la identificación correspondiente, pero luego vuelve a entrar y recupera todos los demás proyectos y finalmente "desaparece". ¿Por qué se está llamando a sí mismo o de dónde viene la segunda llamada?
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;
}
};
* Esto es todo lo que el reductor tiene por el momento porque todavía estoy súper estancado en el resto.
De todos modos, me gustaría agradecerles a todos por adelantado. Si algo no está claro en absoluto o si necesita más información, hágamelo saber. Sé que esto cubre más de una cosa y podría ser súper fácil o nada, pero estoy realmente ansioso por obtener tanta ayuda como sea posible porque estoy realmente atrapado aquí ... ¡Gracias de nuevo!