¿Cómo paso datos a componentes enrutados angulares?

268

En una de las plantillas de mis rutas de Angular 2 ( FirstComponent ) tengo un botón

first.component.html

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

Mi objetivo es lograr:

Haga clic en el botón -> diríjase a otro componente mientras conserva los datos y sin usar el otro componente como directiva.

Esto es lo que probé ...

1er ENFOQUE

En la misma vista, estoy almacenando la recopilación de los mismos datos en función de la interacción del usuario.

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

Normalmente me dirigiría a SecondComponent por

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

eventualmente pasando los datos por

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

mientras que la definición del enlace con parámetros sería

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

El problema con este enfoque es que supongo que no puedo pasar datos complejos (por ejemplo, un objeto como propiedad3) en la URL;

2º ENFOQUE

Una alternativa sería incluir SecondComponent como directiva en FirstComponent.

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

Sin embargo, quiero enrutar a ese componente, ¡no incluirlo!

3er ENFOQUE

La solución más viable que veo aquí sería usar un Servicio (por ejemplo, FirstComponentService) para

  • almacenar los datos (_firstComponentService.storeData ()) en routeWithData () en FirstComponent
  • recuperar los datos (_firstComponentService.retrieveData ()) en ngOnInit () en SecondComponent

Si bien este enfoque parece perfectamente viable, me pregunto si esta es la forma más fácil / elegante de lograr el objetivo.

En general, me gustaría saber si me faltan otros enfoques potenciales para pasar los datos entre componentes, particularmente con la menor cantidad posible de código

dragonmnl
fuente
66
3 formas simples de compartir datos a través de la ruta - angulartutorial.net/2017/12/…
Prashobh
2
gracias @Prashobh. Pass data using Query Parameterses lo que estaba buscando Tu enlace me salvó el día.
Raj
Angular 7.2 ahora tiene una nueva función para pasar datos entre rutas utilizando stateel PR para obtener más detalles. Alguna información útil aquí
AzizKapProg
@Prashobh Muchas gracias. El enlace que ha compartido es muy útil
vandu

Respuestas:

193

actualización 4.0.0

Ver documentos angulares para más detalles https://angular.io/guide/router#fetch-data-before-navigating

original

Usar un servicio es el camino a seguir. En los parámetros de ruta, solo debe pasar los datos que desea que se reflejen en la barra de URL del navegador.

Ver también https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

El enrutador enviado con RC.4 reintroduce 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();
  }
}

Vea también el Plunker en https://github.com/angular/angular/issues/9757#issuecomment-229847781

Günter Zöchbauer
fuente
44
¿Esta respuesta sigue siendo válida para Angular 2.1.0?
smartmouse
9
Los datos del enrutador RC.4 son solo para datos estáticos. No puede enviar datos diferentes a la misma ruta, siempre tiene que ser la misma información, ¿estoy equivocado?
aycanadal
1
No, use un servicio compartido para este caso de uso.
Günter Zöchbauer
8
En Angular 5, de todos modos, deberías poder ... ngOnInit() { this.myVar = this.route.snapshot.data['some_data']; }
Roger
55
Si puede usar Angular v7.2, permite pasar el estado ahora en el enrutador usando el NavigationExtras- stackoverflow.com/a/54879389/1148107
mtpultz
67

Creo que dado que no tenemos el tipo de cosa $ rootScope en angular 2 como en angular 1.x. Podemos usar el servicio / clase angular 2 mientras que en ngOnDestroy pase los datos al servicio y, después del enrutamiento, tomemos los datos del servicio en la función ngOnInit :

Aquí estoy usando DataService para compartir el objeto héroe:

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

Pase el objeto del primer componente de la página:

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

Tomar objeto del componente de la segunda página:

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

Aquí hay un ejemplo: plunker

Utpal Kumar Das
fuente
Esto es hermoso, pero ¿qué tan común en la comunidad Ng2 es esto? No recuerdo haberlo leído en los documentos ...
Alfa Bravo
1
En comparación con otra opción como parámetros de URL u otro almacenamiento del navegador, esto me parece mejor. Tampoco vi en ninguna documentación para trabajar así.
Utpal Kumar Das
2
¿Funciona cuando el usuario abre una nueva pestaña y copia y pega la ruta del segundo componente? ¿Puedo ir a buscar this.hero = this.dataService.hero? ¿Obtendré los valores?
De hecho, esto es muy simple y todo desarrollador Angular lo sabe, pero el problema es una vez que actualiza los datos sueltos en los servicios. El usuario tendrá que hacer todo de nuevo.
Santosh Kadam
@SantoshKadam la pregunta es "¿Cómo paso datos a componentes enrutados angulares?" así que pasar datos por las funciones ngOnDestroy y ngOnInit es una forma, y ​​siempre es lo mejor. Si el usuario necesita obtener datos después de la recarga, entonces debe guardar los datos en un almacenamiento permanente y leer nuevamente desde ese almacenamiento.
Utpal Kumar Das
28
<div class="button" click="routeWithData()">Pass data and route</div>

bueno, la forma más fácil de hacerlo en angular 6 u otras versiones, espero es simplemente definir su ruta con la cantidad de datos que desea pasar

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

Como puede ver en la definición de mis rutas, he agregado el /:idsoporte para los datos que quiero pasar al componente a través de la navegación del enrutador. Por lo tanto, su código se verá así

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

para leer idel componente, solo importa ActivatedRoute como

import { ActivatedRoute } from '@angular/router'

y en el ngOnInites donde recuperas los datos

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

Puedes leer más en este artículo https://www.tektutorialshub.com/angular-passing-parameters-to-route/

Daniel Charles Mwangila
fuente
12
¿Qué pasa si quiero enviar un objeto complejo? no quiero hinchar mis rutas a tonterías
imposibles de mantener
@cmxl Use un servicio compartido entonces.
Stanislasdrg Restablece a Monica el
Exactamente lo que estaba buscando. ¡Gracias!
Akhil
21

Angular 7.2.0 introdujo una nueva forma de pasar los datos al navegar entre componentes enrutados:

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

O:

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

Para leer el estado, puede acceder a la window.history.statepropiedad una vez que haya finalizado la navegación:

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
fuente
44
no funciona para mí, window.history.statedevuelve algo como en {navigationId: 2}lugar de devolver el objeto que pasé.
Louis
@ Louis, ¿qué versión angular estás usando?
Washington Soares Braga
Estoy usando la versión angular 8.1.0
Louis
Estoy viendo lo mismo que Louis, con una versión más baja que la suya pero aún lo suficientemente alta como para que tenga esa característica.
WindowsWeenie
como parte del objeto devuelto, navigationIdcon un valor agregado. Si no se agregan datos, solo navigationIdse mostrará el. Observe los datos pasados, regrese o actualice su aplicación y vuelva a activar la acción que agrega los datos a la ruta y navega a la siguiente ruta (por ejemplo, hacer clic en el botón). Esto agregará sus datos al objeto.
Alf Moh
12

Una persona súper inteligente (tmburnell) que no soy yo sugiere reescribir los datos de la ruta:

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

Como se ve aquí en los comentarios.

Espero que alguien encuentre esto útil

Terary
fuente
77
acabo de enterar de esto y me siento como que necesito algunos puntos stackoverflow :)
Tim.Burnell
11

Este otro enfoque no es bueno para este problema. Creo que el mejor enfoque es Query-Parameter by Routerangular que tiene 2 vías:

Pasando el parámetro de consulta directamente

Con este código puede navegar urlpor paramsen su código html:

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

Pasando el parámetro de consulta por Router

Debe inyectar el enrutador dentro de su constructorgusto:

constructor(private router:Router){

}

Ahora use eso como:

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

Ahora, si desea leer Routeren otro Component, debe usar ActivatedRoutecomo:

constructor(private activateRouter:ActivatedRouter){

}

y subscribeque:

  ngOnInit() {
    this.sub = this.route
      .queryParams
      .subscribe(params => {
        // Defaults to 0 if no query param provided.
        this.page = +params['serviceId'] || 0;
      });
  }
AmirReza-Farahlagha
fuente
this.router.navigate (['/ product-list'], {queryParams: {serviceId: serviceId}}); se puede reemplazar con this.router.navigate (['/ product-list'], {queryParams: {serviceId}});
Ramakant Singh
10

Es 2019 y muchas de las respuestas aquí funcionarían, dependiendo de lo que quieras hacer. Si desea pasar en algún estado interno no visible en la URL (parámetros, consulta) puede usar statedesde 7.2 (como he aprendido hoy :)).

Desde el blog (créditos Tomasz Kula): navegas a la ruta ...

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

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

Y para recogerlo en el componente de destino:

constructor(public activatedRoute: ActivatedRoute) {}

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

Tarde, pero espero que esto ayude a alguien con Angular reciente.

PeS
fuente
6

Solución con ActiveRoute (si desea pasar un objeto por ruta, use JSON.stringfy / JSON.parse):

Prepare el objeto antes de enviarlo:

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

}

Reciba su objeto en el componente de destino:

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

}
escorpión
fuente
1
Eso funciona, pero ¿qué pasa si no quiero exponer todos los datos en la URL?
mpro
Puede cifrar los datos y ponerlos en parámetros, después de eso cifrar en el componente de destino.
escorpión
He creado el servicio para compartir datos.
mpro
¿Para qué es eso super.ngOnInit();?
Andrea Gherardi
5

El tercer enfoque es la forma más común de compartir datos entre componentes. puede inyectar el servicio del elemento que desea usar en un componente relacionado.

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
fuente
3
El servicio se instancia al cambiar de ruta. Entonces pierdes datos
Jimmy Kane
1
@JimmyKane Hablas específicamente sobre cuándo se actualiza la página, pero si no se actualiza, la memoria aún se guarda en un servicio. Este debería ser el comportamiento predeterminado, ya que ahorrará la carga muchas veces.
Aaron Rabinowitz
1
@AaronRabinowitz a la derecha. Perdón por la confusion. Y perdón por el voto negativo. Ojalá pudiera deshacerlo ahora. Demasiado tarde. Era nuevo en angular 2 y mi problema al intentar su enfoque fue que tenía el servicio proporcionado a muchos componentes y no a través del módulo de la aplicación.
Jimmy Kane
3

Pase usando JSON

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

fuente
77
Esta sería una mejor respuesta si pudiera mostrar también cómo se consume el parámetro en el componente receptor, toda la ruta que toma. Es decir, si alguien no sabe cómo pasar un parámetro, tampoco sabrá cómo usar este parámetro en el componente receptor. :)
Alfa Bravo
Una desventaja de esto es que hay una limitación de tamaño para la cadena de consulta y, a veces, no desea que las propiedades del objeto sean visibles en la barra de direcciones.
CM
3

use un servicio compartido para almacenar datos con un índice personalizado. luego envíe ese índice personalizado con queryParam. Este enfoque es más 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
fuente
0

di que tienes

  1. component1.ts
  2. componente1.html

y desea pasar datos a component2.ts .

  • en component1.ts es una variable con datos dicen

      //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
fuente
0

Puede usar BehaviorSubject para compartir datos entre componentes enrutados. Un BehaviorSubject tiene un valor. Cuando se suscribe, emite el valor inmediatamente. Un sujeto no tiene un valor.

En el servicio.

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

En el componente puede suscribirse a este BehaviorSubject.

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

Nota: El asunto no funcionará aquí, solo necesita usar el Asunto de comportamiento

sushil suthar
fuente