Como passo dados para componentes roteados angulares?

268

Em um dos modelos de minhas rotas Angular 2 ( FirstComponent ), tenho um botão

first.component.html

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

Meu objetivo é alcançar:

Clique no botão -> rotear para outro componente enquanto preserva os dados e sem usar o outro componente como diretiva.

Isto é o que eu tentei ...

1ª ABORDAGEM

Na mesma visão, estou armazenando a coleta dos mesmos dados com base na interação do usuário.

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, eu direcionaria para o SecondComponent por

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

eventualmente passando os dados por

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

Considerando que a definição da ligação com os parâmetros seria

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

O problema dessa abordagem é que acho que não posso transmitir dados complexos (por exemplo, um objeto como property3) dentro do URL;

2ª ABORDAGEM

Uma alternativa seria incluir o SecondComponent como diretiva no FirstComponent.

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

No entanto, eu quero rotear para esse componente, não incluí-lo!

3ª ABORDAGEM

A solução mais viável que vejo aqui seria usar um Serviço (por exemplo, FirstComponentService) para

  • armazene os dados (_firstComponentService.storeData ()) em routeWithData () em FirstComponent
  • recuperar os dados (_firstComponentService.retrieveData ()) em ngOnInit () em SecondComponent

Embora essa abordagem pareça perfeitamente viável, pergunto-me se essa é a maneira mais fácil / elegante de atingir a meta.

Em geral, eu gostaria de saber se estou perdendo outras abordagens em potencial para passar os dados entre componentes, principalmente com a menor quantidade possível de código

dragonmnl
fonte
6
3 maneiras simples para compartilhar dados através de rota - angulartutorial.net/2017/12/...
Prashobh
2
obrigado @Prashobh. Pass data using Query Parametersé o que eu estava procurando. seu link salvou meu dia.
Raj
O Angular 7.2 agora possui um novo recurso para passar dados entre rotas usando o statecheque PR para mais detalhes. Algumas informações úteis aqui
AzizKapProg 18/04/19
@Prashobh Muito obrigado. O link que você compartilhou é muito útil
vandu 24/01

Respostas:

193

atualização 4.0.0

Consulte Documentos angulares para obter mais detalhes https://angular.io/guide/router#fetch-data-before-navigating

original

Usar um serviço é o caminho a percorrer. Nos parâmetros de rota, você só deve passar os dados que deseja que sejam refletidos na barra de URL do navegador.

Consulte também https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

O roteador enviado com o RC.4 reintroduz 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();
  }
}

Consulte também o Plunker em https://github.com/angular/angular/issues/9757#issuecomment-229847781

Günter Zöchbauer
fonte
4
Esta resposta ainda é válida para o Angular 2.1.0?
Smartmouse
9
Os dados do roteador RC.4 são apenas para dados estáticos. Você não pode enviar dados diferentes para a mesma rota, sempre tem que ser os mesmos dados, estou errado?
aycanadal
1
Não, use um serviço compartilhado para este caso de uso.
Günter Zöchbauer
8
Em Angular 5, de qualquer maneira, você deve ser capaz de ... ngOnInit() { this.myVar = this.route.snapshot.data['some_data']; }
Roger
5
Se você é capaz de usar v7.2 angular que permite a passagem do estado agora no roteador usando o NavigationExtras- stackoverflow.com/a/54879389/1148107
mtpultz
67

Acho que já que não temos o tipo de coisa $ rootScope no angular 2 como no angular 1.x. Podemos usar o serviço / classe angular 2 compartilhada enquanto estiver no ngOnDestroy, passar dados para o serviço e, após o roteamento, extrair os dados do serviço na função ngOnInit :

Aqui estou usando o DataService para compartilhar o objeto herói:

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

Passe o objeto do componente da primeira página:

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

Pegue o objeto do componente da segunda página:

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

Aqui está um exemplo: plunker

Utpal Kumar Das
fonte
Isso é lindo, mas quão comum na comunidade Ng2 é isso? Não me lembro de lê-lo nos docs ...
Alfa Bravo
1
Comparando com outra opção, como parâmetros de URL ou outro armazenamento do navegador, isso me parece melhor. Eu também não vi em nenhuma documentação funcionar assim.
Utpal Kumar Das 10/07
2
Funciona quando o usuário abre uma nova guia e copia e cola a segunda rota do componente? Posso buscar this.hero = this.dataService.hero? Vou receber os valores?
Isso é realmente muito simples e todo desenvolvedor Angular sabe, mas o problema é quando você atualiza os dados perdidos nos serviços. O usuário terá que fazer todos os animais novamente.
Santosh Kadam
@SantoshKadam, a pergunta é "Como transmito dados para componentes roteados Angular?" portanto, passar dados pelas funções ngOnDestroy e ngOnInit é uma maneira, e sempre simples é o melhor. Se o usuário precisar obter dados após o recarregamento, será necessário salvar os dados em um armazenamento permanente e ler novamente a partir desse armazenamento.
Utpal Kumar Das
28
<div class="button" click="routeWithData()">Pass data and route</div>

bem, a maneira mais fácil de fazer isso em versões angulares 6 ou outras, espero é simplesmente definir seu caminho com a quantidade de dados que você deseja passar

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

Como você pode ver na minha definição de rotas, eu adicionei o /:idsuporte aos dados que eu quero passar para o componente através da navegação do roteador. Portanto, seu código será parecido com

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

para ler ido componente, importe ActivatedRoute como

import { ActivatedRoute } from '@angular/router'

e no ngOnInité onde você recupera os dados

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

você pode ler mais neste artigo https://www.tektutorialshub.com/angular-passing-parameters-to-route/

Daniel Charles Mwangila
fonte
12
e se eu quiser enviar um objeto complexo? Eu não quero inchar minhas rotas para um absurdo
impossível de manter
@cmxl Use um serviço compartilhado então.
Stanislasdrg Restabelecer Monica
Exatamente o que eu estava procurando. Obrigado!
Akhil
21

O Angular 7.2.0 introduziu uma nova maneira de transmitir os dados ao navegar entre componentes roteados:

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

Para ler o estado, você pode acessar a window.history.statepropriedade depois que a navegação terminar:

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
fonte
4
não funciona para mim, window.history.stateretorna algo como, em {navigationId: 2}vez de retornar o objeto que eu passei.
Louis
@ Louis, qual versão Angular você está usando?
Washington Soares Braga
Estou usando a versão angular 8.1.0
Louis
Estou vendo a mesma coisa que Louis, com uma versão mais baixa que a dele, mas ainda alta o suficiente para ter esse recurso.
WindowsWeenie
como parte do objeto retornado, navigationIdcom um valor adicionado. Se nenhum dado for adicionado, apenas o navigationIdserá mostrado. Veja os dados passados, volte ou atualize seu aplicativo e reative a ação que adiciona os dados à rota e navega para a próxima rota (por exemplo, clique no botão). Isso adicionará seus dados ao objeto.
quer
12

Alguma pessoa super inteligente (tmburnell) que não sou eu sugere reescrever os dados da rota:

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

Como visto aqui nos comentários.

Espero que alguém ache isso útil

terário
fonte
7
só descobri sobre isso e eu sinto que eu preciso de alguns pontos stackoverflow :)
Tim.Burnell
11

Eu esta a outra abordagem não é boa para esta questão. Eu acho que a melhor abordagem é Query-Parameter por Routerangular que tem 2 vias:

Passando diretamente o parâmetro de consulta

Com este código você pode navegar para urlpor paramsem seu código html:

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

Passando o parâmetro de consulta por Router

Você deve injetar o roteador dentro do seu constructorgosto:

constructor(private router:Router){

}

Agora use isso como:

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

Agora, se você quiser ler Routerem outro, Componentvocê deve usar o seguinte ActivatedRoute:

constructor(private activateRouter:ActivatedRouter){

}

e subscribeque:

  ngOnInit() {
    this.sub = this.route
      .queryParams
      .subscribe(params => {
        // Defaults to 0 if no query param provided.
        this.page = +params['serviceId'] || 0;
      });
  }
AmirReza-Farahlagha
fonte
this.router.navigate (['/ product-list'], {queryParams: {serviceId: serviceId}}); pode ser substituído por this.router.navigate (['/ product-list'], {queryParams: {serviceId}});
Ramakant Singh 22/08/19
10

É 2019 e muitas das respostas aqui funcionariam, dependendo do que você deseja fazer. Se você deseja passar em algum estado interno não visível na URL (parâmetros, consulta), você pode usar statedesde o 7.2 (como eu aprendi hoje :)).

No blog (créditos Tomasz Kula) - você navega para rotear ....

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

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

E para buscá-lo no componente de destino:

constructor(public activatedRoute: ActivatedRoute) {}

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

Tarde, mas espero que isso ajude alguém com Angular recente.

PeS
fonte
6

Solução com o ActiveRoute (se você deseja passar o objeto por rota - use JSON.stringfy / JSON.parse):

Prepare o objeto antes de enviar:

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

}

Receba seu objeto no 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ão
fonte
1
Isso funciona, mas e se eu não quiser expor todos os dados no URL?
Mpr
Você pode criptografar os dados e colocá-los em parâmetros, depois criptografar no componente de destino.
Scorpion
Eu criei o serviço para compartilhamento de dados.
Mpro
Para que é isso super.ngOnInit();?
Andrea Gherardi
5

A terceira abordagem é a maneira mais comum de compartilhar dados entre componentes. você pode injetar o serviço de item que deseja usar no 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
fonte
3
O serviço é instanciado ao alternar rotas. Então você perder dados
Jimmy Kane
1
@ JimmyKane Você está falando especificamente quando a página é atualizada, mas se ela não for atualizada, a memória ainda será salva em um serviço. Esse deve ser o comportamento padrão, pois ele economizará o carregamento várias vezes.
Aaron Rabinowitz
1
@AaronRabinowitz certo. Desculpe pela confusão. E desculpe pelo voto negativo. Gostaria de poder desfazer isso agora. Muito tarde. Era novo no angular 2 e meu problema ao tentar sua abordagem foi que eu tinha o serviço fornecido para muitos componentes e não fornecido pelo módulo de aplicativo.
Jimmy Kane
3

Passe usando JSON

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

fonte
7
Essa seria uma resposta melhor se você pudesse mostrar como o parâmetro também é consumido no componente receptor - toda a rota que ele leva. Ou seja, se alguém não souber como passar um parâmetro, ele também não saberá como usar esse parâmetro no componente de recebimento. :)
Alfa Bravo
Uma desvantagem disso é que há uma limitação de tamanho para a string de consulta e, às vezes, você não deseja que as propriedades do objeto sejam visíveis na barra de endereço.
CM
3

use um serviço compartilhado para armazenar dados com um índice personalizado. em seguida, envie esse índice personalizado com queryParam. essa abordagem é mais flexível .

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

diga que você tem

  1. component1.ts
  2. component1.html

e você deseja passar dados para component2.ts .

  • em component1.ts é uma variável com dados digamos

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

Você pode usar o BehaviorSubject para compartilhar dados entre componentes roteados. Um BehaviorSubject mantém um valor. Quando está inscrito, emite o valor imediatamente. Um Assunto não possui um valor.

No serviço.

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

No componente, você pode se inscrever neste BehaviorSubject.

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

Nota: O assunto não funcionará aqui. É necessário usar apenas o Assunto do comportamento.

sushil suthar
fonte