child - Angular 2: obtener RouteParams del componente padre
router outlet (12)
Como mencionó Günter Zöchbauer, utilicé el comentario en
github.com/angular/angular/issues/6204#issuecomment-173273143
para abordar mi problema.
angular2/core
clase
Injector
de
angular2/core
para obtener los parámetros de ruta del padre.
Resulta que angular 2 no maneja rutas profundamente anidadas.
Quizás agreguen eso en el futuro.
constructor(private _issueService: IssueService,
private _injector: Injector) {}
getIssues() {
let id = this._injector.parent.parent.get(RouteParams).get(''id'');
this._issueService.getIssues(id).then(issues => this.issues = issues);
}
¿Cómo obtengo los RouteParams de un componente principal?
App.ts
:
@Component({
...
})
@RouteConfig([
{path: ''/'', component: HomeComponent, as: ''Home''},
{path: ''/:username/...'', component: ParentComponent, as: ''Parent''}
])
export class HomeComponent {
...
}
Y luego, en
ParentComponent
, puedo obtener fácilmente mi parámetro de nombre de usuario y establecer las rutas secundarias.
Parent.ts
:
@Component({
...
})
@RouteConfig([
{ path: ''/child-1'', component: ChildOneComponent, as: ''ChildOne'' },
{ path: ''/child-2'', component: ChildTwoComponent, as: ''ChildTwo'' }
])
export class ParentComponent {
public username: string;
constructor(
public params: RouteParams
) {
this.username = params.get(''username'');
}
...
}
Pero entonces, ¿cómo puedo obtener este mismo parámetro ''nombre de usuario'' en esos componentes secundarios? Hacer el mismo truco que el anterior, no lo hace. ¿Porque esos parámetros están definidos en ProfileComponent o algo así?
@Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(
public params: RouteParams
) {
this.username = params.get(''username'');
// returns null
}
...
}
Con
Observable.combineLatest
de RxJS, podemos obtener algo cercano al manejo idiomático de parámetros:
import ''rxjs/add/operator/combineLatest'';
import {Component} from ''@angular/core'';
import {ActivatedRoute, Params} from ''@angular/router'';
import {Observable} from ''rxjs/Observable'';
@Component({ /* ... */ })
export class SomeChildComponent {
email: string;
id: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
Observable.combineLatest(this.route.params, this.route.parent.params)
.forEach((params: Params[]) => {
this.id = params[0][''id''];
this.email = params[1][''email''];
});
}
}
En FINAL, con poca ayuda de RXJS , puede combinar ambos mapas (del niño y del padre):
(route) => Observable
.zip(route.params, route.parent.params)
.map(data => Object.assign({}, data[0], data[1]))
Otras preguntas que uno podría tener:
- ¿Es realmente una buena idea usar lo anterior, debido al acoplamiento (componente de la pareja de niños con los parámetros de los padres, no en el nivel de API, acoplamiento oculto),
- ¿Es un enfoque adecuado en términos de RXJS (requeriría comentarios de usuario de RXJS incondicional;)
Encontré una solución fea pero funcional, solicitando el inyector principal (precisamente el segundo antepasado) y obteniendo los
RouteParams
desde aquí.
Algo como
@Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(injector: Injector) {
let params = injector.parent.parent.get(RouteParams);
this.username = params.get(''username'');
}
}
No debe intentar usar
RouteParams
en su
ChildOneComponent
.
Utilice
RouteRegistry
, en su lugar!
@Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(registry: RouteRegistry, location: Location) {
route_registry.recognize(location.path(), []).then((instruction) => {
console.log(instruction.component.params[''username'']);
})
}
...
}
ACTUALIZACIÓN: a partir de esta solicitud de extracción (angular beta.9): https://github.com/angular/angular/pull/7163
Ahora puede acceder a la instrucción actual sin
recognize(location.path(), [])
.
Ejemplo:
@Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(_router: Router) {
let instruction = _router.currentInstruction();
this.username = instruction.component.params[''username''];
}
...
}
Aún no lo he probado
Más detalles aquí:
https://github.com/angular/angular/blob/master/CHANGELOG.md#200-beta9-2016-03-09 https://angular.io/docs/ts/latest/api/router/Router-class.html
ACTUALIZACIÓN 2: Un pequeño cambio a partir de angular 2.0.0.beta15:
Ahora
currentInstruction
ya no es una función.
Además, debe cargar el enrutador
root
.
(gracias a @ Lxrd-AJ por informar)
@Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(_router: Router) {
let instruction = _router.root.currentInstruction;
this.username = instruction.component.params[''username''];
}
...
}
Pasar la instancia del inyector al constructor en el componente hijo puede no ser bueno si desea escribir pruebas unitarias para su código.
La forma más fácil de solucionar esto es tener una clase de servicio en el componente principal, en la que guarde los parámetros requeridos.
@Component({
template: `<div><router-outlet></router-outlet></div>`,
directives: [RouterOutlet],
providers: [SomeServiceClass]
})
@RouteConfig([
{path: "/", name: "IssueList", component: IssueListComponent, useAsDefault: true}
])
class IssueMountComponent {
constructor(routeParams: RouteParams, someService: SomeServiceClass) {
someService.id = routeParams.get(''id'');
}
}
Luego, simplemente inyecta el mismo servicio a los componentes secundarios y accede a los parámetros.
@Component({
template: `some template here`
})
class IssueListComponent implements OnInit {
issues: Issue[];
constructor(private someService: SomeServiceClass) {}
getIssues() {
let id = this.someService.id;
// do your magic here
}
ngOnInit() {
this.getIssues();
}
}
Tenga en cuenta que debe ampliar dicho servicio a su componente principal y sus componentes secundarios utilizando "proveedores" en el decorador de componentes principal.
Recomiendo este artículo sobre DI y ámbitos en Angular 2: http://blog.thoughtram.io/angular/2015/08/20/host-and-visibility-in-angular-2-dependency-injection.html
Puede hacerlo en la instantánea con lo siguiente, pero si cambia, su propiedad de
id
no se actualizará.
Este ejemplo también muestra cómo puede suscribirse a todos los cambios de parámetros ancestrales y buscar el que le interesa fusionando todos los parámetros observables. Sin embargo, tenga cuidado con este método porque podría haber múltiples antepasados que tengan la misma clave / nombre de parámetro.
import { Component } from ''@angular/core'';
import { ActivatedRoute, Params, ActivatedRouteSnapshot } from ''@angular/router'';
import { Observable } from ''rxjs/Observable'';
import { Subscription } from ''rxjs/Subscription'';
import ''rxjs/add/observable/merge'';
// This traverses the route, following ancestors, looking for the parameter.
function getParam(route: ActivatedRouteSnapshot, key: string): any {
if (route != null) {
let param = route.params[key];
if (param === undefined) {
return getParam(route.parent, key);
} else {
return param;
}
} else {
return undefined;
}
}
@Component({ /* ... */ })
export class SomeChildComponent {
id: string;
private _parameterSubscription: Subscription;
constructor(private route: ActivatedRoute) {
}
ngOnInit() {
// There is no need to do this if you subscribe to parameter changes like below.
this.id = getParam(this.route.snapshot, ''id'');
let paramObservables: Observable<Params>[] =
this.route.pathFromRoot.map(route => route.params);
this._parametersSubscription =
Observable.merge(...paramObservables).subscribe((params: Params) => {
if (''id'' in params) {
// If there are ancestor routes that have used
// the same parameter name, they will conflict!
this.id = params[''id''];
}
});
}
ngOnDestroy() {
this._parameterSubscription.unsubscribe();
}
}
Puede tomar el componente de la ruta principal dentro del componente secundario del inyector y luego obtener cualquiera del componente secundario. En tu caso como este
@Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(
public params: RouteParams
private _injector: Injector
) {
var parentComponent = this._injector.get(ParentComponent)
this.username = parentComponent.username;
//or
this.username = parentComponent.params.get(''username'');
}
...
}
Terminé escribiendo este tipo de truco para Angular 2 rc.1
import { Router } from ''@angular/router-deprecated'';
import * as _ from ''lodash'';
interface ParameterObject {
[key: string]: any[];
};
/**
* Traverse route.parent links until root router and check each level
* currentInstruction and group parameters to single object.
*
* e.g.
* {
* id: [314, 593],
* otherParam: [9]
* }
*/
export default function mergeRouteParams(router: Router): ParameterObject {
let mergedParameters: ParameterObject = {};
while (router) {
let currentInstruction = router.currentInstruction;
if (currentInstruction) {
let currentParams = currentInstruction.component.params;
_.each(currentParams, (value, key) => {
let valuesForKey = mergedParameters[key] || [];
valuesForKey.unshift(value);
mergedParameters[key] = valuesForKey;
});
}
router = router.parent;
}
return mergedParameters;
}
Ahora a la vista,
RouteParams
parámetros a la vista en lugar de leer
RouteParams
, solo los obtengo a través del enrutador:
@Component({
...
})
export class ChildishComponent {
constructor(router: Router) {
let allParams = mergeRouteParams(router);
let parentRouteId = allParams[''id''][0];
let childRouteId = allParams[''id''][1];
let otherRandomParam = allParams.otherRandomParam[0];
}
...
}
ACTUALIZAR:
Ahora que Angular2 final se lanzó oficialmente, la forma correcta de hacerlo es la siguiente:
export class ChildComponent {
private sub: any;
private parentRouteId: number;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.sub = this.route.parent.params.subscribe(params => {
this.parentRouteId = +params["id"];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
ORIGINAL:
Así es como lo hice usando el paquete "@ angular / router": "3.0.0-alpha.6":
export class ChildComponent {
private sub: any;
private parentRouteId: number;
constructor(
private router: Router,
private route: ActivatedRoute) {
}
ngOnInit() {
this.sub = this.router.routerState.parent(this.route).params.subscribe(params => {
this.parentRouteId = +params["id"];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
En este ejemplo, la ruta tiene el siguiente formato: / parent /: id / child /: childid
export const routes: RouterConfig = [
{
path: ''/parent/:id'',
component: ParentComponent,
children: [
{ path: ''/child/:childid'', component: ChildComponent }]
}
];
En RC6, enrutador 3.0.0-rc.2 (probablemente también funciona en RC5), puede tomar parámetros de ruta desde la URL como una instantánea en caso de que los parámetros no cambien, sin observables con este único revestimiento:
this.route.snapshot.parent.params[''username''];
No olvides inyectar ActivatedRoute de la siguiente manera:
constructor(private route: ActivatedRoute) {};
RC5 + @ angular / router ":" 3.0.0-rc.1 SOLUCIÓN:
Parece que
this.router.routerState.queryParams
ha quedado en desuso.
Puede obtener los parámetros de ruta principal de esta manera:
constructor(private activatedRoute: ActivatedRoute) {
}
this.activatedRoute.parent.params.subscribe(
(param: any) => {
let userId = param[''userId''];
console.log(userId);
});