Angular2 - Interaction between components using a service
Asked Answered
M

3

6

I have two component A and B, where component A contains a button. I wish when user click on this button, fire a function on component B

<A></A>
<router-outlet></router-outlet>

And the component B is rendered using routing.I am considering using a service with an observable boolean that indicate if the button in A is clicked. Is this the right way to achieve it ?

Magdau answered 31/1, 2017 at 13:27 Comment(1)
angular.io/docs/ts/latest/cookbook/…Nedneda
B
19

Shared service is a common way of communication between non-related components. Your components need to use a single instance of the service, so make sure it's provided at the root level.

An example using the BehaviorSubject as a data delegate:

Shared service:

@Injectable()
export class SharedService {

    isVisibleSource: BehaviorSubject<boolean> = new BehaviorSubject(false);

    constructor() { }
}

Component 1:

export class Component1 {

    isVisible = false;

    constructor(private sharedService: SharedService) { }

    onClick(): void {
        this.isVisible = !this.isVisible;
        this.sharedService.isVisibleSource.next(this.isVisible);
    }
}

Component 2:

export class Component2 {

    constructor(private sharedService: SharedService) { }

    ngOnInit(): void {
        this.sharedService.isVisibleSource.subscribe((isVisible) => {
            console.log('isVisible: ', isVisible); // => true/false
        });
    }
}

It is worth mentioning that BehaviorSubject upon a subscription returns the last value it holds, therefore the component from the example above will be updated with the most recent value immediately after the instantiation.

BehaviorSubject also allows to get its most recent value without even subscribing to it:

this.sharedService.isVisibleSource.getValue(); // => true/false
Bowse answered 31/1, 2017 at 14:25 Comment(8)
Thanks for this, very clean and work out of the box in angular 4 don't forget to import : import {BehaviorSubject} from 'rxjs/BehaviorSubject'; in the service file. Helped me a lot !Goddamn
Hi Seidme I've tried using the technique you explained above but I keep getting the Expression has changed after it was checked. Previous value: true. Current Value: false error. Do you know what could be causing this?Mickeymicki
Hi Skywalker, are you using this approach for parent-child component communication? If yes, then the problem might be there, this approach is intended for communication between non-related components Can you please provide plunker? Also, take a look at: #34365380Bowse
@Bowse thank you so much for replying. Its not for the parent child approach. Its more of a sibling communication (components of different modules communicating with each other). The only way I could fix the issue was by placing the subscribe and the call to the observable in the components constructor. Im not sure whether thats correct approach. Here's the plunkr. To see the error check the browser console. When the app loads a boolean value of true should be displayed but I get the error in the console.Mickeymicki
@Skywalker, As can be seen from the plunkr provided, you're using Subjectinstead of BehaviorSubject, there is difference in behaving between the two. Try using BehaviorSubject as demonstrated in the answer and see if error persists. Please check: #39494558Bowse
@Bowse I have actually tried the BehaviourSubject but unfortunately it gives the same error. I can't seem to figure this out. I've tried almost everything I can find on the net.Mickeymicki
@Mickeymicki Interesting, I'm using this implementation across my all projects and didn't encounter such issues. Will try to investigate what's causing your problem.Bowse
@Bowse thank you so much for your help! I really appreciate it! I've opened up a question here and any help will be greatly appreciated!Mickeymicki
D
0

Angular Service

You have to use a service to communicate between your two components.

See https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Your service has a property event. So the component A can emit the event and the component B can subscribe to it.

Use RxJS to emit and subscribe to your event.

If my answer does not satisfy you. Please tell me and I will work on it.

Dressing answered 31/1, 2017 at 13:41 Comment(2)
But this documentation you posted: angular.io/docs/ts/latest/cookbook/… talks explicitly about 'Parent and children communicate via a service' and not sibling to sibling!.. What if I want something like sibling to sibling communication.. Ex: If A and B are two siblings at same node level..Updating something in A via B and same updating something in B via A..Gayegayel
@ShashankGaurav the documented example show a communication between a parent and a children, but this relationship is not necessary. A service can establish a communication between 2 or more compconents no matter their relationship. See the accepted answer.This
L
-2

STOP USING SERVICES FOR COMPONENTS INTERACTION!!!

Service is a stateless concept in programming, it can only rely on the input and other injected services to produce its output. Storing data inside service(although works) is counter-pattern (since your service is now stateful).

You can achieve what you need with binding @Input() and @Output() of components:

//main container html
<A></A>
<router-outlet (activate)="onRouterOutletActivate($event)"></router-outlet>

//main container ts
@ViewChild(ComponentA, {static: false}) a : ComponentA;
onRouterOutletActivate(event: ContainerOfB): void {
    this.activeRouteComponent = event;
    // make sure doStuff method is defined public in ComponentB
    // clickOutput is an @Output() on ComponentA which propagates click event
    this.a.clickOutput.subscribe(() => this.activeRouteComponent.b.doStuff());
}

//ContainerOfB, the container that has B in it
@ViewChild(ComponentB, {static: false}) b : ComponentB;

//ComponentA html
<button (click)="callback()">button</button>

//ComponentA ts
@Output() clickOutput: EventEmitter<void> = new EventEmitter<void>()
callback() { this.clickOutput.emit(); }

You would also achieve asynchronicity and reactiveness which is strongly emphasized by having rxjs in the core of Angular (whereas with service approach you won't).

I get that sharing service for component communication is less complex than the above approach but just because it works it doesn't mean you should do it. If you're locked out of your Mercedes, what would you rather do: breaking the window glass and unlocking the door or calling the locksmith to come over and unlock it.

p.s. Angular is dope hence the analogy(Mercedes)

Libove answered 20/2, 2020 at 19:15 Comment(2)
Thanks for your answer, but can you explain why angular services shouldn't be stateful? I know that they are often stateless but there is no documentation stating that they should beMagdau
Service is not an Angular term, it's a paradigm in software engineering that Angular uses. Services contain the repeated business logic that does not belong to a certain component (sharing data is not business logic rather a workaround to overcome a programming issue). If you want your service to save its output then you should inject persistence (local storage, db, apollo, etc.) as context to that service. Besides, this variable in your shared service is accessible to any piece of your application without any hierarchical constraints, very similar to global variable pattern.Libove

© 2022 - 2024 — McMap. All rights reserved.