Jak przekazać dane do komponentów routowanych Angular?


268

W jednym z moich szablonów tras Angular 2 ( FirstComponent ) mam przycisk

first.component.html

<div class="button" click="routeWithData()">Pass data and route</div>

Moim celem jest osiągnięcie:

Kliknij przycisk -> wybierz trasę do innego komponentu, zachowując dane i bez używania tego komponentu jako dyrektywy.

Właśnie tego próbowałem ...

1. PODEJŚCIE

W tym samym widoku przechowuję gromadzenie tych samych danych na podstawie interakcji użytkownika.

first.component.ts

export class FirstComponent {
     constructor(private _router: Router) { }

     property1: number;
     property2: string;
     property3: TypeXY; // this a class, not a primitive type

    // here some class methods set the properties above

    // DOM events
    routeWithData(){
         // here route
    }
}

Normalnie kierowałbym się do SecondComponent przez

 this._router.navigate(['SecondComponent']);

ostatecznie przekazując dane

 this._router.navigate(['SecondComponent', {p1: this.property1, p2: property2 }]);

natomiast definicja powiązania z parametrami byłaby

@RouteConfig([
      // ...
      { path: '/SecondComponent/:p1:p2', name: 'SecondComponent', component: SecondComponent} 
)]

Problem z tym podejściem polega na tym, że nie mogę przekazać złożonych danych (np. Obiektu takiego jak property3) do adresu URL;

2. PODEJŚCIE

Alternatywą byłoby włączenie SecondComponent jako dyrektywy w FirstComponent.

  <SecondComponent [p3]="property3"></SecondComponent>

Jednak chcę trasie do tego składnika, nie obejmują go!

3. PODEJŚCIE

Najbardziej realnym rozwiązaniem, jakie tu widzę, byłoby skorzystanie z usługi (np. FirstComponentService)

  • przechowuj dane (_firstComponentService.storeData ()) na routeWithData () w FirstComponent
  • odzyskać dane (_firstComponentService.retrieveData ()) w ngOnInit () w SecondComponent

Chociaż takie podejście wydaje się całkowicie wykonalne, zastanawiam się, czy jest to najłatwiejszy / najbardziej elegancki sposób na osiągnięcie celu.

Ogólnie chciałbym wiedzieć, czy brakuje mi innych potencjalnych podejść do przekazywania danych między komponentami, szczególnie przy mniejszej możliwej ilości kodu


6
3 proste sposoby udostępniania danych przez trasę - angulartutorial.net/2017/12/…
— Prashobh

2
dzięki @Prashobh. Pass data using Query Parameterstego szukałem. twój link uratował mi dzień.
— Raj

Angular 7.2 ma teraz nową funkcję przesyłania danych między trasami za pomocą stateSprawdź PR, aby uzyskać więcej szczegółów. Kilka przydatnych informacji tutaj
— AzizKapProg

@Prashobh Wielkie dzięki. Udostępniony link jest bardzo przydatny
— vandu

Odpowiedzi:


193

aktualizacja 4.0.0

Zobacz dokumentację Angular, aby uzyskać więcej informacji https://angular.io/guide/router#fetch-data-before-navigating

oryginalny

Korzystanie z usługi jest właściwą drogą. W parametrach trasy należy przekazywać tylko te dane, które mają być odzwierciedlone w pasku adresu URL przeglądarki.

Zobacz także https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Router dostarczany z RC.4 ponownie się wprowadza data

constructor(private route: ActivatedRoute) {}
const routes: RouterConfig = [
  {path: '', redirectTo: '/heroes', pathMatch : 'full'},
  {path : 'heroes', component : HeroDetailComponent, data : {some_data : 'some value'}}
];
class HeroDetailComponent {
  ngOnInit() {
    this.sub = this.route
      .data
      .subscribe(v => console.log(v));
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }
}

Zobacz także Plunker na https://github.com/angular/angular/issues/9757#issuecomment-229847781


4
Czy ta odpowiedź jest nadal ważna dla Angular 2.1.0?
— smartmouse

9
Dane routera RC.4 są tylko dla danych statycznych. Nie możesz wysłać różnych danych na tę samą trasę, zawsze muszą to być te same dane, czy się mylę?
— aycanadal

1
Nie, skorzystaj ze wspólnej usługi dla tego przypadku użycia.
— Günter Zöchbauer

8
W Angular 5 i tak powinieneś być w stanie ... ngOnInit() { this.myVar = this.route.snapshot.data['some_data']; }
— Roger,

5
Jeśli jesteś w stanie używać Angulara w wersji 7.2, pozwala on teraz przekazać stan w routerze za pomocą NavigationExtras- stackoverflow.com/a/54879389/1148107
— mtpultz

67

Myślę, że skoro nie mamy $ rootScope w Angular 2, jak w Angular 1.x. Podczas korzystania z ngOnDestroy możemy korzystać z wspólnej usługi / klasy angular 2 i przekazywać dane do usługi, a po routingu pobrać dane z usługi w funkcji ngOnInit :

Tutaj używam DataService do udostępniania obiektu bohatera:

import { Hero } from './hero';
export class DataService {
  public hero: Hero;
}

Przekaż obiekt z pierwszej strony:

 ngOnDestroy() {
    this.dataService.hero = this.hero; 
 }

Pobierz obiekt z komponentu drugiej strony:

 ngOnInit() {
    this.hero = this.dataService.hero; 
 }

Oto przykład: plunker


To jest piękne, ale jak powszechne jest to w społeczności Ng2? Nie przypominam sobie, żeby czytałem to w dokumentach ...
— Alfa Bravo,

1
W porównaniu z innymi opcjami, takimi jak parametry adresu URL lub inna pamięć przeglądarki, wydaje mi się to lepsze. Nie widziałem też w żadnej dokumentacji takiej pracy.
— Utpal Kumar Das

2
Czy to działa, gdy użytkownik otwiera nową kartę i kopiuje wklej trasę drugiego komponentu? Czy mogę pobrać this.hero = this.dataService.hero? Czy dostanę wartości?

Jest to rzeczywiście bardzo proste i każdy programista Angular wie, ale problem polega na tym, że po odświeżeniu tracisz dane w usługach. Użytkownik będzie musiał ponownie wykonać wszystkie czynności.
— Santosh Kadam,

@ SantoshKadam pytanie brzmi: „Jak przekazać dane do komponentów routowanych przez Angular?” więc przekazywanie danych przez funkcje ngOnDestroy i ngOnInit jest sposobem i zawsze proste jest najlepsze. Jeśli użytkownik musi uzyskać dane po ponownym załadowaniu, należy zapisać dane w pamięci trwałej i ponownie odczytać z tej pamięci.
— Utpal Kumar Das

28
<div class="button" click="routeWithData()">Pass data and route</div>

Cóż, najłatwiejszym sposobem, aby to zrobić w Angular 6 lub innych wersjach Mam nadzieję, że po prostu zdefiniujesz swoją ścieżkę za pomocą ilości danych, które chcesz przekazać

{path: 'detailView/:id', component: DetailedViewComponent}

jak widać z mojej definicji tras, dodałem /:idopcję „stand” do danych, które chcę przekazać do komponentu za pośrednictwem nawigacji routera. Dlatego twój kod będzie wyglądał

<a class="btn btn-white-view" [routerLink]="[ '/detailView',list.id]">view</a>

aby odczytać idskładnik, wystarczy zaimportować ActivatedRoute jak

import { ActivatedRoute } from '@angular/router'

i na miejscu, w ngOnInitktórym odzyskujesz dane

ngOnInit() {
       this.sub = this.route.params.subscribe(params => {
        this.id = params['id'];
        });
        console.log(this.id);
      }

możesz przeczytać więcej w tym artykule https://www.tektutorialshub.com/angular-passing-parameters-to-route/


12
co jeśli chcę wysłać złożony obiekt? nie chcę przesadzać z moją drogą do
— niemożliwego

@cmxl Skorzystaj wtedy z usługi współdzielonej.
— Stanislasdrg Przywróć Monikę

Dokładnie tego szukałem. Dzięki!
— Akhil

21

W Angular 7.2.0 wprowadzono nowy sposób przekazywania danych podczas nawigacji między trasowanymi komponentami:

@Component({
  template: `<a (click)="navigateWithState()">Go</a>`,
})
export class AppComponent  {
  constructor(public router: Router) {}
  navigateWithState() {
    this.router.navigateByUrl('/123', { state: { hello: 'world' } });
  }
}

Lub:

@Component({
  selector: 'my-app',
  template: `
  <a routerLink="/details" [state]="{ hello: 'world' }">Go</a>`,
})
export class AppComponent  {}

Aby odczytać stan, możesz uzyskać dostęp do window.history.statewłaściwości po zakończeniu nawigacji:

export class PageComponent implements OnInit {
  state$: Observable<object>;

  constructor(public activatedRoute: ActivatedRoute) {}

  ngOnInit() {
    this.state$ = this.activatedRoute.paramMap
      .pipe(map(() => window.history.state))
  }
}

4
nie działa dla mnie, window.history.statezwraca coś podobnego {navigationId: 2}zamiast zwracanego obiektu, który przekazałem.
— Louis

@Louis, której wersji Angular używasz?
— Washington Soares Braga

Używam wersji kątowej 8.1.0
— Louis

Widzę to samo, co Louis, z wersją niższą niż jego, ale wciąż wystarczająco wysoką, aby miała tę funkcję.
— WindowsWeenie,

jako część zwracanego obiektu navigationIdz wartością dodaną. Jeśli nie zostaną dodane żadne dane, navigationIdzostaną wyświetlone tylko te. Zobacz przekazane dane, wróć lub odśwież aplikację i ponownie uruchom działanie, które dodaje dane do trasy i nawiguje do następnej trasy (np. Kliknięcie przycisku). Spowoduje to dodanie twoich danych do obiektu.
— Alf Moh

12

Jakaś super inteligentna osoba (tmburnell), która nie jest mną, sugeruje ponowne zapisanie danych trasy:

let route = this.router.config.find(r => r.path === '/path');
route.data = { entity: 'entity' };
this.router.navigateByUrl('/path');

Jak widać tutaj w komentarzach.

Mam nadzieję, że ktoś uzna to za przydatne


7
właśnie się o tym dowiedziałem i wydaje mi się, że potrzebuję punktów
— przepełnienia stosu

11

Ja to drugie podejście nie jest dobre dla tego problemu. Wydaje mi się, że najlepszym podejściem jest parametr zapytania według Routerkąta, który ma 2 sposoby:

Bezpośrednie przekazywanie parametru zapytania

Z tym kodem można poruszać się urlprzez paramsw kodzie html:

<a [routerLink]="['customer-service']" [queryParams]="{ serviceId: 99 }"></a>

Przekazywanie parametru zapytania przez Router

Musisz wstrzyknąć router w constructortaki sposób, jak:

constructor(private router:Router){

}

Teraz użyj takich jak:

goToPage(pageNum) {
    this.router.navigate(['/product-list'], { queryParams: { serviceId: serviceId} });
}

Teraz, jeśli chcesz czytać Router w innym Component, musisz użyć ActivatedRoute:

constructor(private activateRouter:ActivatedRouter){

}

i subscribeże:

  ngOnInit() {
    this.sub = this.route
      .queryParams
      .subscribe(params => {
        // Defaults to 0 if no query param provided.
        this.page = +params['serviceId'] || 0;
      });
  }

this.router.navigate (['/ product-list'], {queryParams: {serviceId: serviceId}}); można zastąpić this.router.navigate (['/ product-list'], {queryParams: {serviceId}});
— Ramakant Singh,

10

Jest rok 2019 i wiele odpowiedzi tutaj zadziała, w zależności od tego, co chcesz zrobić. Jeśli chcesz przekazać w stanie wewnętrznym niewidocznym w adresie URL (parametry, zapytanie), możesz używać stateod wersji 7.2 (jak się dowiedziałem dzisiaj :)).

Z bloga (napisy Tomasz Kula) - nawigujesz do trasy ....

... z ts: this.router.navigateByUrl('/details', { state: { hello: 'world' } });

... z szablonu HTML: <a routerLink="/details" [state]="{ hello: 'world' }">Go</a>

I aby podnieść go w komponencie docelowym:

constructor(public activatedRoute: ActivatedRoute) {}

  ngOnInit() {
    this.state$ = this.activatedRoute.paramMap
      .pipe(map(() => window.history.state))
  }

Późno, ale mam nadzieję, że to pomoże komuś z ostatnim Angular.


6

Rozwiązanie z ActiveRoute (jeśli chcesz przekazać obiekt po trasie - użyj JSON.stringfy / JSON.parse):

Przygotuj obiekt przed wysłaniem:

export class AdminUserListComponent {

  users : User[];

  constructor( private router : Router) { }

  modifyUser(i) {

    let navigationExtras: NavigationExtras = {
      queryParams: {
          "user": JSON.stringify(this.users[i])
      }
    };

    this.router.navigate(["admin/user/edit"],  navigationExtras);
  }

}

Odbierz swój obiekt w komponencie docelowym:

export class AdminUserEditComponent  {

  userWithRole: UserWithRole;      

  constructor( private route: ActivatedRoute) {}

  ngOnInit(): void {
    super.ngOnInit();

      this.route.queryParams.subscribe(params => {
        this.userWithRole.user = JSON.parse(params["user"]);
      });
  }

}

1
To działa, ale co jeśli nie chcę ujawniać wszystkich danych w adresie URL?
— mpro

Możesz zaszyfrować dane, ustaw je w parametry, a następnie zaszyfruj w komponencie docelowym.
— skorpion

Mam stworzył serwis do udostępniania danych.
— mpro,

Po co to jest super.ngOnInit();?
— Andrea Gherardi

5

Trzecie podejście jest najczęstszym sposobem udostępniania danych między komponentami. możesz wstrzyknąć usługę produktu, której chcesz użyć w powiązanym komponencie.

import { Injectable } from '@angular/core';
import { Predicate } from '../interfaces'

import * as _ from 'lodash';

@Injectable()
export class ItemsService {

    constructor() { }


    removeItemFromArray<T>(array: Array<T>, item: any) {
        _.remove(array, function (current) {
            //console.log(current);
            return JSON.stringify(current) === JSON.stringify(item);
        });
    }

    removeItems<T>(array: Array<T>, predicate: Predicate<T>) {
        _.remove(array, predicate);
    }

    setItem<T>(array: Array<T>, predicate: Predicate<T>, item: T) {
        var _oldItem = _.find(array, predicate);
        if(_oldItem){
            var index = _.indexOf(array, _oldItem);
            array.splice(index, 1, item);
        } else {
            array.push(item);
        }
    }


    addItemToStart<T>(array: Array<T>, item: any) {
        array.splice(0, 0, item);
    }


    getPropertyValues<T, R>(array: Array<T>, property : string) : R
    {
        var result = _.map(array, property);
        return <R><any>result;
    }

    getSerialized<T>(arg: any): T {
        return <T>JSON.parse(JSON.stringify(arg));
    }
}



export interface Predicate<T> {
    (item: T): boolean
}

3
Usługa jest tworzona przy zmianie trasy. Więc tracisz dane
— Jimmy Kane

1
@ JimmyKane Mówisz o tym, kiedy strona jest odświeżana, ale jeśli się nie odświeża, pamięć jest nadal zapisywana w usłudze. To powinno być zachowanie domyślne, ponieważ zaoszczędzi wiele ładowań.
— Aaron Rabinowitz,

1
@AaronRabinowitz racja. Przepraszam za zamieszanie. I przepraszam za głosowanie w dół. Chciałbym móc to teraz cofnąć. Za późno. Był nowy w Angular 2, a moim problemem z wypróbowaniem twojego podejścia było to, że dostarczyłem usługę do wielu komponentów, a nie przez moduł aplikacji.
— Jimmy Kane,

3

Przekaż za pomocą JSON

  <a routerLink = "/link"
   [queryParams] = "{parameterName: objectToPass| json }">
         sample Link                   
  </a>

7
To byłaby lepsza odpowiedź, gdybyś mógł pokazać, w jaki sposób parametr jest również zużywany w komponencie odbierającym - całą trasę, którą obiera. Oznacza to, że jeśli ktoś nie wie, jak przekazać parametr, nie będzie również wiedział, jak używać tego parametru w elemencie odbierającym. :)
— Alfa Bravo,

Wadą tego jest ograniczenie wielkości kwerendy i czasami nie chcesz, aby właściwości obiektu były widoczne na pasku adresu.
— CM

3

użyj wspólnej usługi do przechowywania danych z niestandardowym indeksem. następnie wyślij ten niestandardowy indeks za pomocą queryParam. takie podejście jest bardziej elastyczne .

// component-a : typeScript :
constructor( private DataCollector: DataCollectorService ) {}

ngOnInit() {
    this.DataCollector['someDataIndex'] = data;
}

// component-a : html :
<a routerLink="/target-page" 
   [queryParams]="{index: 'someDataIndex'}"></a>

.

// component-b : typeScript :
public data;

constructor( private DataCollector: DataCollectorService ) {}

ngOnInit() {
    this.route.queryParams.subscribe(
        (queryParams: Params) => {
            this.data = this.DataCollector[queryParams['index']];
        }
    );
}

0

powiedz, że masz

  1. składnik1.ts
  2. składnik1.html

i chcesz przekazać dane do component2.ts .

  • w component1.ts jest zmienną z danymi powiedzmy

      //component1.ts
      item={name:"Nelson", bankAccount:"1 million dollars"}
    
      //component1.html
       //the line routerLink="/meter-readings/{{item.meterReadingId}}" has nothing to 
      //do with this , replace that with the url you are navigating to
      <a
        mat-button
        [queryParams]="{ params: item | json}"
        routerLink="/meter-readings/{{item.meterReadingId}}"
        routerLinkActive="router-link-active">
        View
      </a>
    
      //component2.ts
      import { ActivatedRoute} from "@angular/router";
      import 'rxjs/add/operator/filter';
    
      /*class name etc and class boiler plate */
      data:any //will hold our final object that we passed 
      constructor(
      private route: ActivatedRoute,
      ) {}
    
     ngOnInit() {
    
     this.route.queryParams
      .filter(params => params.reading)
      .subscribe(params => {
      console.log(params); // DATA WILL BE A JSON STRING- WE PARSE TO GET BACK OUR 
                           //OBJECT
    
      this.data = JSON.parse(params.item) ;
    
      console.log(this.data,'PASSED DATA'); //Gives {name:"Nelson", bankAccount:"1 
                                            //million dollars"}
       });
      }

0

Możesz użyć BehaviorSubject do udostępniania danych między routowanymi komponentami. Obiekt BehaviourSubject zawiera jedną wartość. Po zasubskrybowaniu natychmiast emituje wartość. Podmiot nie ma wartości.

W serwisie.

@Injectable({
  providedIn: 'root'
})
export class CustomerReportService extends BaseService {
  reportFilter = new BehaviorSubject<ReportFilterVM>(null);
  constructor(private httpClient: HttpClient) { super(); }

  getCustomerBalanceDetails(reportFilter: ReportFilterVM): Observable<Array<CustomerBalanceDetailVM>> {
    return this.httpClient.post<Array<CustomerBalanceDetailVM>>(this.apiBaseURL + 'CustomerReport/CustomerBalanceDetail', reportFilter);
  }
}

W komponencie możesz zasubskrybować ten BehaviorSubject.

this.reportService.reportFilter.subscribe(f => {
      if (f) {
        this.reportFilter = f;
      }
    });

Uwaga: Temat nie będzie tu działał, należy użyć wyłącznie Przedmiotu zachowania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.