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 ?

seidme
  • 12,543
  • 5
  • 36
  • 40
LHIOUI
  • 3,287
  • 2
  • 24
  • 37

3 Answers3

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
seidme
  • 12,543
  • 5
  • 36
  • 40
  • 1
    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 ! – foufrix Jul 04 '17 at 10:53
  • 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? – Skywalker Nov 02 '17 at 15:00
  • 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: https://stackoverflow.com/questions/34364880/expression-has-changed-after-it-was-checked – seidme Nov 02 '17 at 15:23
  • @seidme 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](https://plnkr.co/edit/5P2zWvpgMPz8e450xETD?p=preview). 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. – Skywalker Nov 03 '17 at 08:53
  • @Skywalker, As can be seen from the plunkr provided, you're using `Subject`instead 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: https://stackoverflow.com/questions/39494058/behaviorsubject-vs-observable – seidme Nov 03 '17 at 15:54
  • @seidme 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. – Skywalker Nov 03 '17 at 16:10
  • @Skywalker 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. – seidme Nov 03 '17 at 16:25
  • @seidme thank you so much for your help! I really appreciate it! I've opened up a question [here](https://stackoverflow.com/questions/47061525/angular-4-expression-has-changed-after-it-was-checked-error-while-using-ng-i) and any help will be greatly appreciated! – Skywalker Nov 03 '17 at 16:50
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.

Maxime Gélinas
  • 2,202
  • 2
  • 18
  • 35
  • 1
    But this documentation you posted: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service 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.. – test_124 Jul 18 '17 at 10:58
  • @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. – Maxime Gélinas Jul 20 '17 at 04:44
-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)

Wildhammer
  • 2,017
  • 1
  • 27
  • 33
  • 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 be – LHIOUI Feb 21 '20 at 10:03
  • 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. – Wildhammer Feb 21 '20 at 14:46