Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular4 : How to pass data from parent to child for children added through router

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.

like image 389
Relm Avatar asked Apr 18 '18 10:04

Relm


People also ask

How do you pass data from parent to child in router outlet?

Using @Input() decorator We use the @Input() decorator to pass data from the parent to the child component via the template of the child component.

How do I transfer data from parent to child?

While there is no direct way to pass data from the child to the parent component, there are workarounds. The most common one is to pass a handler function from the parent to the child component that accepts an argument which is the data from the child component. This can be better illustrated with an example.

How do you pass data between two child components?

By adding the state isOpen to the parent, we can facilitate communication between the two sibling components. When the Button component is clicked it emits an event that updates the isOpen variable. That variable is then passed down to the Toggle component as a prop.


2 Answers

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")
}
like image 117
John Velasquez Avatar answered Nov 14 '22 15:11

John Velasquez


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.

like image 42
Stefan Avatar answered Nov 14 '22 14:11

Stefan