9

Continuing from this question Angular 4^ : How to have more than one child of a component with each child targeting its own router outlet, I'm able to have some child components injected into multiple parent components, now I want to pass data from those parents, async, to child. Tried @Input, can't seem to win.

Child

export class UserheaderComponent implements OnInit, AfterViewInit, OnChanges {
  loading;
  @Input() data;
  user = {
    name: '______________',
    icon: '',
    username: '_________',
    uid: '________'
  };
  constructor(private router: Router) {
  }

  goToUser(uid) {
    this.router.navigate(['user'], { queryParams: { uid: uid } });
  }

  ngOnInit() {
    this.user = this.data;
    console.log(this.data);
  }

  ngAfterViewInit() {
    console.log(this.data);
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log(changes);
  }
}

Parent Html

  <router-outlet name='userprofile-userhead' [data]="currentUser"></router-outlet>

Parent TS

export class UserprofileComponent {
  public currentUser;

  constructor(
    private userFactory: UserFactory,
    private router: Router,
    private snack: MatSnackBar) {
    this.userFactory.checkSession(exists => {
      if (!exists) {
        return;
      }
      this.userFactory.getSessionUser((uid, user) => {
        this.currentUser = user;
      });
    });
  }
}

AND ROUTING

path: '', component: UserprofileComponent, outlet: 'userprofile', children: [
          { path: '', component: UserheaderComponent, outlet: 'userprofile-userhead' },
        ]

Nothing at all gets passed to child, is this possible with this kind of arrangement or I'm missing something?

Can't use a shared service.

Every component should use this with its own Id. Imagine this is in a timeline of posts like context, like a social media timeline, and this is a head of the post, you know, where the user icon, name...username is. So a 'post' component will inject this as a child, pass it a user object:{name:'...',username:'...'}, so I don't see how a service will do here.

Now while we at that, somewhere on the app, a profile component, a search component might call this...

If you still think a service will do, please elaborate.

Relm
  • 7,923
  • 18
  • 66
  • 113
  • create a `subscription` and subscribe to it in your child components – Tony Roczz Apr 18 '18 at 10:17
  • @TonyRoczz Imagine this Userhead component is in a post, among other posts, on a timeline, that won't work. – Relm Apr 18 '18 at 10:20
  • You have to pass to route parameter a required id or something by which you are getting user or userhead and from the child make a request based on the route parameters. If you will have only one childer you can use directly. – Stefan Apr 18 '18 at 10:42
  • @Stefan i don't get that clearly, mind making it a detaild answer? – Relm Apr 18 '18 at 10:45
  • You can add the parameter to your route of UserheaderComponent like that path: 'userHead/:userId' And then when you init that " this.activatedRoute.params.subscribe((params) => { const userHead= params['userId']; }" And finally, based on that id you can get that user which you are trying to pass to the child component – Stefan Apr 18 '18 at 10:53
  • 1
    If I understand you right you might try to use a shared service. Have a look at this answer: https://stackoverflow.com/questions/41451375/passing-data-into-router-outlet-child-components-angular-2?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qahttps://stackoverflow.com/questions/41451375/passing-data-into-router-outlet-child-components-angular-2?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa – tom van green Apr 18 '18 at 11:01
  • See my comment above why a shared service won't do here. – Relm Apr 20 '18 at 16:16
  • Edited the question for those who're suggesting a service. – Relm Apr 20 '18 at 16:22
  • @Relm did you try my answer? – tatsu Apr 26 '18 at 08:12
  • As router-outlet does not take the input with name as data,so it will not pass on to the chid, router-outlet is having the attribute name @Attribute('name') , where you can mention the name of the outlet [marking a place where to put the component instance].It will emit an instance of the component (here it is child), you can assign the data to the child in parent on emitting by calling a method as i have mentioned it in answer and For better understanding see this router-outlet : https://github.com/angular/angular/blob/master/packages/router/src/directives/router_outlet.ts – Ampati Hareesh Apr 27 '18 at 05:22
  • Updated the answer for passing of data async from parent to child !!! – Ampati Hareesh Apr 27 '18 at 07:45

5 Answers5

4

I get you, but I think shared service is still the answer of this question

You could try implementing a pub/sub service where you can assign which data will it subscribe and broadcast.

try this:

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

/**
 * Publisher/Subscriber Service
 */
@Injectable()
export class PubSubService {

    private events: any = {};

    constructor() { }

    /**
     * Subscribes the instance of the assigned event name
     * @param eventName
     * Event name of the delegate
     */
    public On(eventName: PubSubEvents): Observable<any> {

        if (typeof this.events[eventName] === 'undefined') {
            this.events[eventName] = new Subject<any>();
        }

        return this.events[eventName].asObservable();
    }

    /**
     * Broadcast data to the specified event channel
     * @param eventName
     * Event name of the delegate
     * @param eventArgs
     * Arguments to pass through to the connected channel
     */
    public Broadcast(eventName: PubSubEvents, eventArgs: any) {
        if (!this.events[eventName]) {
            return;
        }

        this.events[eventName].next(eventArgs);
    }

}

//Your events
export declare type PubSubEvents =
    "OnChild1" | "OnChild2";

In parent component you have subscribe all of the events base on your needs.

Parent

constructor(private pubsub: PubSubService){
  this.pubsub.On("OnChild1").subscribe((res) =>{ //Child 1 data}));
  this.pubsub.On("OnChild2").subscribe((res) =>{ //Child 2 data}));
}

while in child component you have to do this

Child 1

constructor(private pubsub: PubSubService){
  this.pubsub.Broadcast("OnChild1", "your_data")
}

Child 2

constructor(private pubsub: PubSubService){
  this.pubsub.Broadcast("OnChild2", "your_data")
}
John Velasquez
  • 3,421
  • 2
  • 18
  • 23
1

I think in this case one valid option is to add some parameter to the routing of your UserheaderComponent and then get it when the component is initing and get expected data from service.

First, you need to add it to route path of your component

path: 'userHead/:userId' 

Then when you redirecting to that you need to set this parameter and then when the component is initializing you can get this

this.activatedRoute.params.subscribe((params) => { const userHead= 
params['userId']; }

Finally based on that paremeter you can get expected data form service.

In the case when you will have a single child consider using component directly like that

<userprofile-userhead name='userprofile-userhead' [data]="currentUser"> 
</userprofile-userhead>

instead of the router-outlet.

Stefan
  • 1,431
  • 2
  • 17
  • 33
1

You can make your router path as -

{ path: 'userHead/:Id' }

Navigate on the component using function or directly:

  navigateUser(id) {
    this.router.navigate(['/userHead', id]);
  }

In your child component you can get the id by subscribing on the id of route param :

 ngOnInit() {
    this.ActivatedRoute.params.subscribe(params => {
      this.id = +params['id']; // (+) converts string 'id' to a number
      if (!isNaN(this.id)) {
       // logic here to check if id is something and put logic accordingly
    });
  }
  • Second way is to use BehaviorSubjects. Let me know if you need to know about them, will try to provide code for same.
abhinav3414
  • 946
  • 3
  • 9
  • 31
1

You can define the custom data to the route like this:

[
   {path: 'inbox', data: {name: 'foo', age: 23}},
]

and read like this:

class ConversationCmp {
    constructor(r: ActivateRoute) {
         r.data.subscribe((p) => {
              console.log(p);
         });
    }
}

It's defined on the Route interface:

export interface Route {
  path?: string;
  ...
  data?: Data;
}

credit here : https://stackoverflow.com/a/45450982/4770754

tatsu
  • 2,316
  • 7
  • 43
  • 87
0

I am assuming that you are looking for the following flow : When some path matched , you want the router to render parent(where parent.ts is having the data or fetches the data through http(s)) and child. Now once after the rendering of the child, you want to pass the data from parent to child...if this is the requirement of yours, this might help you,

ParentComponent.ts

onActivate(event){
      this.yourService.getData().subscribe(x=>{
        event.data = x; // this is where you are assigning the data to child
      })

ParentComponet.html

<router-outlet (activate)="onActivate($event)" name="child"></router-outlet>

ChildComponent.ts

export class Child1Component implements OnInit {
data
  constructor() { }
  ngOnInit() {
  }
}

ChildComponent.html

 <div *ngFor="let e of data">      {{e.username}}    </div>
   // this is for an example to display the data from parent in child

Edited: Async without service -using formcontrol

Here , what i am doing is that whenever the parent value changes, which you are passing to child, we need to update the child value async, so using the ValueChanges in parent where it sets the value of child, now even if you edit the values in parent variable , it gets reflected into the child. Now i believe this completes the requirement of yours (without the shared service communication between parent and child :) )

In parent.ts

export class ParentComponent  {
  parentdata: any = [{name: 'hello', username: 'world'}];
  myForm: FormGroup;
  event: any;
  constructor(private ds: DataService,private formBuilder: FormBuilder) {
    this.myForm = this.formBuilder.group({
      pdata: [[]]
    });
    this.myForm.get('pdata').valueChanges.subscribe(val => {
      if(this.event!=undefined)
        {
          this.event.data =  this.myForm.get('pdata').value;
        }
      })
   }
  ngAfterViewInit() {
       //this is for just for playing around with data of parent,
       // to check whether child receives the data or not
    setTimeout(function(myForm){

    let d = myForm.get('pdata').value;
    console.log(myForm.get('pdata').value);
    d.push({name: 'hello', username: 'world'});

  },3000,this.myForm)
  }
  onActivate(event){
    this.event = event;
    this.ds.getData().subscribe(x=>{
        this.myForm.get('pdata').setValue(x);
      })
  }

}

Hope this helps !!!

Ampati Hareesh
  • 1,852
  • 1
  • 15
  • 20