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

dragonmnl
źródło
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

Günter Zöchbauer
źródło
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

Utpal Kumar Das
źródło
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/

Daniel Charles Mwangila
źródło
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))
  }
}
Washington Soares Braga
źródło
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

straszny
źródło
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;
      });
  }
AmirReza-Farahlagha
źródło
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.

PeS
źródło
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"]);
      });
  }

}
Skorpion
źródło
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
}
Ahankendi
źródło
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>

źródło
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']];
        }
    );
}
Amin Adel
źródło
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"}
       });
      }
Nelson Bwogora
źródło
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.

sushil suthar
źródło