Reputation: 39484
Using Angular 8 I have a few components as follows:
Parent 1 > Child 1 > ... > N Grandchild 1
Parent 2 > Child 2 > ... > N Grandchild 2
Between Child X
and N Grandchild X
might have other components. So it can be more than 3 levels.
Objective
1. Set a Value in N Grandchild 1
and use it in Parent 1
.
2. Set a Value in N Grandchild 2
and use it in Parent 2
.
I am considering a few options:
Using an EventEmitter (StackBlitz example)
Problem: Not working between Grandchild and Parent.
It seems to work only one level up ... It is possible to make it work N levels up?
export class GrandchildComponent implements OnInit {
@Output() changeEvent = new EventEmitter<string>();
ngOnInit() {
this.changeEvent.emit('Hello');
}
}
Using a Service
In this case I inject the Service in N Grandchild X
to set the value and in Parent X
to read the value.
Problem:
How to be sure that Parent 1
reads the value set by N GrandChild 1
and Parent 2
reads the value set by N GrandChild 2
? Is this possible?
Upvotes: 5
Views: 8045
Reputation: 73377
Like in another answer, which provides solution to use a Subject of some kind, but you face the issue that that subject and its value is shared across the app, if it's provided at root level.
One option is to provide the service at the parent level, which means that the parent and all its descendants have the same instance of the service, so therefore, if grandchild1
emits a value for the subject, only parent1
will receive that value, not parent2
.
So what you would do, as mentioned, provide the service at the highest level you want, in this case I would assume it would be the parent
:
import { MyService } from './my-service';
@Component({
selector: 'parent',
template: `
Parent:
<p>Value from grandchild: {{value$ | async}}</p>
<child></child>
`,
providers: [MyService] // <<<<<<<<<< add this (only in parent)!
})
export class ParentComponent {
value$;
constructor(private myService: MyService) {
this.value$ = this.myService.subj$;
}
}
and service could have:
private subj = new Subject();
public subj$ = this.subj.asObservable();
setValue(value) {
this.subj.next(value)
}
And in the grandchild when you want to emit value, just call setValue()
with the new value.
Upvotes: 4
Reputation: 441
You could try using a BehaviorSubject in a injected service in both components, one component listening on the BehaviorSubject. And the other emiting new values for the subject.
@Injectable({
providedIn: 'root'
})
export class TestService {
subject: BehaviorSubject<any>;
constructor() {
this.subject = new BehaviorSubject(null);
}
getObservable() {
return this.subject.asObservable();
}
updateValue(value) {
this.subject.next(value);
}
}
Upvotes: 0
Reputation: 728
First Way: You have to emit event on every level of chain, then only you will be able to get the event to the Parent component.
Parent 1(received) > Child 1(emit) > Child 2(emit) > N Grandchild 1(emit);
Second Way: Also for service you can create a message-bus kind of service, when you can specify from and to. So using those flag you can validate from where and for whom the data should be consumed by.
I personally think that you should go with the first approach.
Upvotes: 0