Comment transmettre des données aux composants routés angulaires?

268

Dans l'un des modèles de mes routes Angular 2 ( FirstComponent ), j'ai un bouton

first.component.html

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

Mon objectif est d'atteindre:

Cliquez sur le bouton -> route vers un autre composant tout en préservant les données et sans utiliser l'autre composant comme directive.

C'est ce que j'ai essayé ...

1ÈRE APPROCHE

Dans la même vue, je stocke la collecte des mêmes données en fonction de l'interaction de l'utilisateur.

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

Normalement, j'acheminais vers SecondComponent par

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

éventuellement passer les données par

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

considérant que la définition du lien avec les paramètres serait

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

Le problème avec cette approche est que je suppose que je ne peux pas transmettre de données complexes (par exemple un objet comme property3) dans l'url;

2ÈME APPROCHE

Une alternative serait d'inclure SecondComponent comme directive dans FirstComponent.

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

Cependant, je veux router vers ce composant, ne pas l'inclure!

3ÈME APPROCHE

La solution la plus viable que je vois ici serait d'utiliser un service (par exemple FirstComponentService) pour

  • stocker les données (_firstComponentService.storeData ()) sur routeWithData () dans FirstComponent
  • récupérer les données (_firstComponentService.retrieveData ()) dans ngOnInit () dans SecondComponent

Bien que cette approche semble parfaitement viable, je me demande si c'est la façon la plus simple / la plus élégante d'atteindre l'objectif.

En général, je voudrais savoir si je manque d'autres approches potentielles pour transmettre les données entre les composants, en particulier avec le moins de code possible

dragonmnl
la source
6
3 façons simples de partager des données via l'itinéraire - angulartutorial.net/2017/12/…
Prashobh
2
merci @Prashobh. Pass data using Query Parametersest ce que je cherchais. votre lien a sauvé ma journée.
Raj
Angular 7.2 a désormais une nouvelle fonctionnalité pour transmettre des données entre les itinéraires en utilisant statele PR pour plus de détails. Quelques informations utiles ici
AzizKapProg
@Prashobh Merci beaucoup. Le lien que vous avez partagé est très utile
vandu

Réponses:

193

mise à jour 4.0.0

Voir la documentation angulaire pour plus de détails https://angular.io/guide/router#fetch-data-before-navigating

original

Utiliser un service est la voie à suivre. Dans les paramètres d'itinéraire, vous ne devez transmettre que les données que vous souhaitez refléter dans la barre d'URL du navigateur.

Voir également https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Le routeur livré avec RC.4 réintroduit 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();
  }
}

Voir également le Plunker sur https://github.com/angular/angular/issues/9757#issuecomment-229847781

Günter Zöchbauer
la source
4
Cette réponse est-elle toujours valable pour Angular 2.1.0?
smartmouse
9
Les données du routeur RC.4 sont uniquement pour les données statiques. Vous ne pouvez pas envoyer des données différentes sur le même itinéraire, il doit toujours s'agir des mêmes données, ai-je tort?
aycanadal
1
Non, utilisez un service partagé pour ce cas d'utilisation.
Günter Zöchbauer
8
Dans Angular 5, de toute façon, vous devriez pouvoir ... ngOnInit() { this.myVar = this.route.snapshot.data['some_data']; }
Roger
5
Si vous êtes capable d'utiliser Angular v7.2, il permet de passer l'état maintenant dans le routeur à l'aide de NavigationExtras- stackoverflow.com/a/54879389/1148107
mtpultz
67

Je pense que nous n'avons pas le genre de chose $ rootScope en angulaire 2 comme en angulaire 1.x. Nous pouvons utiliser angulaire 2 service / classe partagé pendant que dans ngOnDestroy passer les données au service et après le routage prendre les données du service dans la fonction ngOnInit :

Ici, j'utilise DataService pour partager un objet héros:

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

Passer l'objet du composant de première page:

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

Prendre l'objet du composant de la deuxième page:

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

Voici un exemple: plunker

Utpal Kumar Das
la source
C'est beau, mais à quel point est-ce courant dans la communauté Ng2? Je ne me souviens pas l'avoir lu dans la documentation ...
Alfa Bravo
1
Par rapport à d'autres options telles que les paramètres d'URL ou tout autre stockage de navigateur, cela me semble mieux. Je n'ai pas non plus vu dans aucune documentation de travailler comme ça.
Utpal Kumar Das
2
Cela fonctionne-t-il lorsque l'utilisateur ouvre un nouvel onglet et copie-colle la deuxième route des composants? Puis-je pouvoir aller chercher this.hero = this.dataService.hero? Vais-je obtenir les valeurs?
C'est en effet très simple et tout développeur Angular le sait, mais le problème est une fois que vous actualisez vos données perdues dans les services. L'utilisateur devra refaire tout.
Santosh Kadam
@SantoshKadam, la question est "Comment puis-je transmettre des données aux composants routés angulaires?" donc passer des données par les fonctions ngOnDestroy et ngOnInit est un moyen, et toujours simple est le meilleur. Si l'utilisateur doit obtenir des données après le rechargement, il doit enregistrer les données dans un stockage permanent et relire à partir de ce stockage.
Utpal Kumar Das
28
<div class="button" click="routeWithData()">Pass data and route</div>

eh bien la façon la plus simple de le faire dans angular 6 ou dans d'autres versions j'espère est de simplement définir votre chemin avec la quantité de données que vous voulez passer

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

comme vous pouvez le voir dans ma définition des itinéraires, j'ai ajouté le /:idsupport aux données que je souhaite transmettre au composant via la navigation du routeur. Par conséquent, votre code ressemblera à

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

afin de lire le idsur le composant, il suffit d'importer ActivatedRoute comme

import { ActivatedRoute } from '@angular/router'

et ngOnInitc'est là que vous récupérez les données

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

vous pouvez en lire plus dans cet article https://www.tektutorialshub.com/angular-passing-parameters-to-route/

Daniel Charles Mwangila
la source
12
que faire si je veux envoyer un objet complexe? je ne veux pas gonfler mes routes vers des bêtises incontrôlables :(
cmxl
@cmxl Utilisez alors un service partagé.
Stanislasdrg réintègre Monica
Exactement ce que je cherchais. Merci!
Akhil
21

Angular 7.2.0 a introduit une nouvelle façon de transmettre les données lors de la navigation entre les composants routés:

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

Ou:

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

Pour lire l'état, vous pouvez accéder à la window.history.statepropriété une fois la navigation terminée:

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
la source
4
ne fonctionne pas pour moi, window.history.staterenvoie quelque chose comme {navigationId: 2}au lieu de renvoyer l'objet que j'ai transmis.
Louis
@Louis quelle version angulaire utilisez-vous?
Washington Soares Braga
J'utilise la version angulaire 8.1.0
Louis
Je vois la même chose que Louis, avec une version inférieure à la sienne mais toujours assez élevée pour qu'elle soit censée avoir cette fonctionnalité.
WindowsWeenie
dans le cadre de l'objet renvoyé, navigationIdune valeur est ajoutée. Si aucune donnée n'est ajoutée, seul le navigationIdsera affiché. Consultez les données transmises, revenez en arrière ou actualisez votre application et relancez l'action qui ajoute les données à l'itinéraire et navigue vers l'itinéraire suivant (par exemple, un clic sur le bouton). Cela ajoutera ensuite vos données à l'objet.
Alf Moh
12

Une personne super intelligente (tmburnell) qui n'est pas moi suggère de réécrire les données de l'itinéraire:

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

Comme on le voit ici dans les commentaires.

J'espère que quelqu'un trouvera cela utile

terary
la source
7
viens de découvrir cela et j'ai l'impression d'avoir besoin de points de stackoverflow :)
Tim.Burnell
11

Je cette l'autre approche pas bon pour cette question. Je pense que la meilleure approche est Query-Parameter par Routerangular qui a 2 voies:

Passer directement le paramètre de requête

Avec ce code , vous pouvez naviguer urlpar paramsvotre code html:

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

Passage du paramètre de requête par Router

Vous devez injecter le routeur dans votre constructorcomme:

constructor(private router:Router){

}

Maintenant, utilisez cela comme:

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

Maintenant, si vous voulez lire Routerdans un autre, Componentvous devez utiliser ActivatedRoutecomme:

constructor(private activateRouter:ActivatedRouter){

}

et subscribeque:

  ngOnInit() {
    this.sub = this.route
      .queryParams
      .subscribe(params => {
        // Defaults to 0 if no query param provided.
        this.page = +params['serviceId'] || 0;
      });
  }
AmirReza-Farahlagha
la source
this.router.navigate (['/ product-list'], {queryParams: {serviceId: serviceId}}); peut être remplacé par this.router.navigate (['/ product-list'], {queryParams: {serviceId}});
Ramakant Singh
10

Nous sommes en 2019 et de nombreuses réponses ici fonctionneraient, selon ce que vous voulez faire. Si vous voulez passer dans un état interne non visible dans l'URL (paramètres, requête), vous pouvez utiliser statedepuis 7.2 (comme je l'ai appris aujourd'hui :)).

Depuis le blog (crédits Tomasz Kula) - vous accédez à l'itinéraire ....

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

... à partir du modèle HTML: <a routerLink="/details" [state]="{ hello: 'world' }">Go</a>

Et pour le récupérer dans le composant cible:

constructor(public activatedRoute: ActivatedRoute) {}

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

Tard, mais j'espère que cela aide quelqu'un avec Angular récent.

PeS
la source
6

Solution avec ActiveRoute (si vous voulez passer objet par route - utilisez JSON.stringfy / JSON.parse):

Préparez l'objet avant de l'envoyer:

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

}

Recevez votre objet dans le composant destination:

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

}
Scorpion
la source
1
Cela fonctionne, mais que se passe-t-il si je ne souhaite pas exposer toutes les données dans l'URL?
mpro
Vous pouvez chiffrer les données et les placer dans des paramètres, après cela, chiffrer dans le composant cible.
scorpion
J'ai créé le service de partage de données.
mpro
À quoi ça sert super.ngOnInit();?
Andrea Gherardi
5

La troisième approche est le moyen le plus courant de partager des données entre les composants. vous pouvez injecter le service d'élément que vous souhaitez utiliser dans le composant associé.

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
la source
3
Le service est instancié lors du changement d'itinéraire. Donc vous perdez des données
Jimmy Kane
1
@JimmyKane Vous parlez spécifiquement lorsque la page est actualisée, mais si elle ne s'actualise pas, la mémoire est toujours enregistrée dans un service. Cela devrait être le comportement par défaut car il sauvera le chargement plusieurs fois.
Aaron Rabinowitz
1
@AaronRabinowitz à droite. Désolé pour la confusion. Et désolé pour le vote négatif. J'aimerais pouvoir le défaire maintenant. Trop tard. Était nouveau sur angular 2 et mon problème avec votre approche était que j'avais le service fourni à de nombreux composants et non via le module d'application.
Jimmy Kane
3

Passer en utilisant JSON

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

la source
7
Ce serait une meilleure réponse si vous pouviez également montrer comment le paramètre est consommé dans le composant récepteur - tout le chemin qu'il prend. Cela signifie que si quelqu'un ne sait pas comment passer un paramètre, il ne saura pas non plus comment utiliser ce paramètre dans le composant récepteur. :)
Alfa Bravo
Un inconvénient est qu'il y a une limitation de taille à la chaîne de requête et parfois vous ne voulez pas que les propriétés des objets soient visibles dans la barre d'adresse.
CM
3

utiliser un service partagé pour stocker des données avec un index personnalisé. puis envoyez cet index personnalisé avec queryParam. cette approche est plus flexible .

// 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
la source
0

dis que tu as

  1. component1.ts
  2. component1.html

et vous souhaitez transmettre des données à component2.ts .

  • dans component1.ts est une variable avec des données dites

      //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
la source
0

Vous pouvez utiliser BehaviorSubject pour partager des données entre les composants routés. Un BehaviorSubject contient une valeur. Lorsqu'il est souscrit, il émet la valeur immédiatement. Un sujet n'a pas de valeur.

Dans le service.

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

Dans le composant, vous pouvez vous abonner à ce BehaviorSubject.

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

Remarque: le sujet ne fonctionnera pas ici, vous devez utiliser uniquement le sujet de comportement.

sushil suthar
la source