heliya rb
heliya rb

Reputation: 830

angular passing data from child to parent without child tag

I try to pass data from child to parent by emitting but I don't have any child tag and go to the child just with the route, so how can listen to emit data?

parent.html

<ul class="list-unstyled multi-steps">
    <li [ngClass]="{ ' is-active': status == 'first' }">
        <span routerLink="/room/specification"></span>
              step1
    </li>
    <li [ngClass]="{ ' is-active ': status == 'second' }">
         <span routerLink="/room/attributes" (click)="onClick()"></span>
              step2
    </li>
</ul>
<div class="content">
    <router-outlet></router-outlet>
</div>

specification-child.ts

 @Output("specificationData") specificationData = new EventEmitter();

     onSubmit() {
      const data = { this.specificationForm.value };
      this.specificationData.emit(data);
     }

as you see there is no child tag that I can use

<child (specificationData)="getData($event)"></child>

so what sould i do?

Upvotes: 3

Views: 1598

Answers (4)

Ashish Kumar Jaryal
Ashish Kumar Jaryal

Reputation: 822

You can use EventEmitter or Subject. Subject is recommended in such cases.

You can use service to share emitted values:

import {Injectable, EventEmitter} from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn : 'root'
})
export class AppService {
  constructor() {

  }

  public emitter: Subject<string> = new Subject<string>(); // Subject
 // public emitter : EventEmitter<string> = new EventEmitter(); // Event Emitter
 
  getEmittedValue() {
    return this.emitter;
  }

Your sample child template:

<button (click)="go('From Child')">Emit</button>

Your sample child component(Don't forget to inject the service I created above):

go(message: string) {
    console.log('Button Clicked ', message);
    this.appService.emitter.next(message); // Subject
   // this.appService.emitter.emit();  // Event Emitter
  }

Now time to use emitted output from child component anywhere. Just inject the service I created above anywhere you want the emitted value and subscribe to the Subject or Event Emitter:

constructor(private appsService: AppService) {
    this.appsService.getEmittedValue().subscribe(res => this.ekMethod(res));
  }

Upvotes: 0

Navitas28
Navitas28

Reputation: 777

In Angular 7.2.0+

parent.component.ts

    <ul class="list-unstyled multi-steps">
    <li [ngClass]="{ ' is-active': status == 'first' }">
        <span [routerLink]="/room/specification" [state]="{data: {...}}"></span>
              step1
    </li>
    <li [ngClass]="{ ' is-active ': status == 'second' }">
         <span [routerLink]="/room/attributes"  [state]="{data: {...}}"(click)="onClick()"></span>
              step2
    </li>
</ul>
<div class="content">
    <router-outlet></router-outlet>
</div>

specificatioon.child.ts

Getting the data is not so obvious at a first glance. Someone could expect that ActivatedRoute will contain it, but there is no attribute for the state. The state property was added to Navigation which is available through Router.getCurrentNavigation().extras.state. Problem is that getCurrentNavigation returns Navigation only during the navigation and returns null after the navigation ended. So the Navigation is no longer available in Component’s B onInit lifecycle hook. We need to read the data from the browser’s history object:

history.state.data

Upvotes: 0

vighnesh153
vighnesh153

Reputation: 5436

One solution to this problem would be to use Angular Services. Check the documentation to learn more about them.

And in the service, you can use EventEmitter but a convention is to use RxJS Subjects. You can create an instance of a subject in the service and use it to emit data from one component to any other component.

A sample service would look like:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class DataService {
  dataSubject = new Subject<TypeOfData>();

  constructor() { }

}

and then, in the child component, you can use Dependency Injection to access the service's instance and emit data from within.

  constructor(private dataService: DataService) {}

  onSubmit() {
    const data = { this.specificationForm.value };
    this.dataService.dataSubject.next(data);
  }

and then, just have a subscription for the subject in the parent component:

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.dataSubject.subscribe(data => {
      // Do something with the data
    });
  }

You can definitely extract out the emitting of data as a separate method, but the above is a basic example of how to use the service.

Upvotes: 0

Barremian
Barremian

Reputation: 31125

In this case instead of an EventEmitter you could use a singleton service that shares the variable between the parent and the child.

shared.service.ts

@Injectable({ providedIn: 'root' })
export class SharedService {
  private specDataSource = new ReplaySubject<any>(1);
  public specData$ = this.specDataSource.asObservable();

  public setSpecData(data: any) {
    this.specDataSource.next(data);
  }
}

I've used ReplaySubject(1) that can buffer/hold the last value and emit it immediately upon subscription. You could also use RxJS Subject or BehaviorSubject as per your requirement.

specification-child.ts

constructor(private shared: SharedService) { }

onSubmit() {
  this.shared.setSpecData({ this.specificationForm.value });
}

parent.ts

specData: any;
complete$ = new Subject<any>();

constructor(private shared: SharedService) { }

ngOnInit() {
  this.shared.specData$.pipe(
    takeUntil(this.complete$)   // <-- use to close the subscription
  ).subscribe(data => this.specData = data);
}

ngOnDestroy() {
  this.complete$.next();        // <-- close impending subscriptions
}

Upvotes: 1

Related Questions