Angular 2: получение RouteParams из родительского компонента

Как получить RouteParams из родительского компонента?

App.ts:

@Component({
  ...
})

@RouteConfig([
  {path: '/', component: HomeComponent, as: 'Home'},
  {path: '/:username/...', component: ParentComponent, as: 'Parent'}
])

export class HomeComponent {
  ...
}

И затем, в ParentComponent, я могу легко получить свой параметр имени пользователя и установить дочерние маршруты.

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');
  }

  ...
}

Но тогда, как я могу получить этот же параметр "имя пользователя" в этих дочерних компонентах? Выполнение того же трюка, что и выше, не делает этого. Поскольку эти параметры определены в компоненте ProfileComponent или что-то??

@Component({
  ...
})

export class ChildOneComponent {

  public username: string;

  constructor(
    public params: RouteParams
  ) {
    this.username = params.get('username');
    // returns null
  }

  ...
}

Ответ 1

UPDATE:

Теперь, когда окончательный Angular2 был официально выпущен, правильный способ сделать это:

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:

Вот как я это сделал, используя "@ 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();
    }
}

В этом примере маршрут имеет следующий формат:/parent/: id/child/: childid

export const routes: RouterConfig = [
    {
        path: '/parent/:id',
        component: ParentComponent,
        children: [
            { path: '/child/:childid', component: ChildComponent }]
    }
];

Ответ 2

Вы не должны пытаться использовать RouteParams в ChildOneComponent.

Используйте RouteRegistry!

@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']);
    })
  }


  ...
}

ОБНОВЛЕНИЕ: Как из этого запроса на растяжение (angular beta.9): https://github.com/angular/angular/pull/7163

Теперь вы можете получить доступ к текущей инструкции без recognize(location.path(), []).

Пример:

@Component({
  ...
})

export class ChildOneComponent {

  public username: string;

  constructor(_router: Router) {
    let instruction = _router.currentInstruction();
    this.username = instruction.component.params['username'];
  }

  ...
}

Я еще не пробовал, но

Подробнее здесь:

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

ОБНОВЛЕНИЕ 2: Небольшое изменение от angular 2.0.0.b15:

Теперь currentInstruction больше не является функцией. Кроме того, вам нужно загрузить маршрутизатор root. (спасибо @Lxrd-AJ за отчетность)

@Component({
  ...
})

export class ChildOneComponent {

  public username: string;

  constructor(_router: Router) {
    let instruction = _router.root.currentInstruction;
    this.username = instruction.component.params['username'];
  }

  ...
}

Ответ 3

Как упоминал Гюнтер Зёчбауэр, я использовал комментарий https://github.com/angular/angular/issues/6204#issuecomment-173273143 для решения моей проблемы. Я использовал класс Injector из angular2/core, чтобы получить параметры маршрутизации родителя. Выключается angular 2 не обрабатывает глубоко вложенные маршруты. Возможно, они добавят это в будущем.

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);
}

Ответ 4

Я нашел уродливое, но рабочее решение, запросив инжектор родителя (точно второго предка) и получив RouteParams отсюда.

Что-то вроде

@Component({
  ...
})
export class ChildOneComponent {
  public username: string;

  constructor(injector: Injector) {
    let params = injector.parent.parent.get(RouteParams);

    this.username = params.get('username');
  }
}

Ответ 5

RC5 + @ angular/router ":" 3.0.0-rc.1 РЕШЕНИЕ: Кажется, что this.router.routerState.queryParams устарел. Вы можете получить параметры родительского маршрута следующим образом:

constructor(private activatedRoute: ActivatedRoute) {
}    

this.activatedRoute.parent.params.subscribe(
  (param: any) => {
    let userId = param['userId'];
    console.log(userId);
  });

Ответ 6

Вы можете взять компонент родительского маршрута внутри дочернего компонента из инжектора, а затем получить любой из дочернего компонента. В этом случае

@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');
  }

  ...
}

Ответ 7

Передача экземпляра Injector в конструктор в дочернем компоненте может быть не очень хорошей, если вы хотите написать модульные тесты для вашего кода.

Самый простой способ обойти это - создать класс обслуживания в родительском компоненте, в котором вы сохраните требуемые параметры.

@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');
    }
}

Затем вы просто вводите одну и ту же услугу дочерним компонентам и получаете доступ к параметрам.

@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();
    }
}

Обратите внимание, что вы должны обладать такой службой родительскому компоненту и его дочерним компонентам, используя "поставщиков" в декораторе родительских компонентов.

Я рекомендую эту статью о DI и областях в Angular 2: http://blog.thoughtram.io/angular/2015/08/20/host-and-visibility-in-angular-2-dependency-injection.html

Ответ 8

В конце концов я написал этот вид взлома для 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;
}

Теперь я вижу только данные, а не чтение RouteParams Я просто получаю их через маршрутизатор:

@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];
  }

  ...
}  

Ответ 9

В RC6, маршрутизатор 3.0.0-rc.2 (возможно, работает и в RC5), вы можете перенести параметры маршрута из URL-адреса в виде моментального снимка в случае, если параметры не изменятся, без наблюдаемых с этим одним вкладышем:

this.route.snapshot.parent.params['username'];

Не забудьте ввести ActivatedRoute следующим образом:

constructor(private route: ActivatedRoute) {};

Ответ 10

С RxJS Observable.combineLatest мы можем получить что-то близкое к обработке идиоматических параметров:

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'];
        });
  }
}

Ответ 11

В FINAL с небольшой помощью RXJS вы можете комбинировать обе карты (от дочерних и родительских):

(route) => Observable
    .zip(route.params, route.parent.params)
    .map(data => Object.assign({}, data[0], data[1]))

Другие вопросы, которые могут возникнуть:

  • Это действительно хорошая идея использовать выше - из-за связи (пара дочернего компонента с родительским параметром - не на уровне api - скрытая связь),
  • Это правильный подход в терминах RXJS (для этого потребуется обратная связь с пользователем Hardcore RXJS;)

Ответ 12

Вы можете сделать это на снимке со следующим, но если он изменится, ваше свойство id не будет обновлено.

В этом примере также показано, как вы можете подписаться на все изменения параметров предка и искать тот, который вас интересует, путем слияния всех наблюдаемых параметров. Однако будьте осторожны с этим методом, потому что могут быть несколько предков, имеющих один и тот же ключ/имя параметра.

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();
  }
}