Interdependent component which need to communicate each other - javascript

I am in a situation like i have 5 interdependent component which need to communicate each other. For example if i click on a button A on all other 4 component need to listen to the click and alert something. The same way button in other component also listened by all other 4. Need a best solution on how to achieve this.
here is my code snippet
import { Component, OnInit } from '#angular/core';
import { CommonService } from 'broadcast-recive/service/common-service';
#Component({
selector: 'app-broadcaster',
templateUrl: './broadcaster.component.html',
styleUrls: ['./broadcaster.component.css']
})
export class BroadcasterComponent implements OnInit {
constructor(private commonservice: CommonService) { }
ngOnInit() {
}
broadCastMe(): void
{
this.commonservice.btnClickedInBroadCasterComponent((<HTMLButtonElement>event.target).id);
}
}
import { Component, OnInit } from '#angular/core';
import { CommonService } from 'broadcast-recive/service/common-service';
#Component({
selector: 'app-listener1',
templateUrl: './listener1.component.html',
styleUrls: ['./listener1.component.css']
})
export class Listener1Component implements OnInit {
constructor(private commonservice: CommonService) { }
ngOnInit() {
this.commonservice.clickStatusForBroadCastercomponentBtn.subscribe((id: string) => {
alert('alert from listner 1');
})
}
}
import { Component, OnInit } from '#angular/core';
import { CommonService } from 'broadcast-recive/service/common-service';
#Component({
selector: 'app-listener2',
templateUrl: './listener2.component.html',
styleUrls: ['./listener2.component.css']
})
export class Listener2Component implements OnInit {
constructor(private commonservice: CommonService) { }
ngOnInit() {
this.commonservice.clickStatusForBroadCastercomponentBtn.subscribe((id: string) => {
alert('from listner 2');
});
}
}
Here am always getting alert box "from listener 2 " , My requirement is its should trigger both the listener. Please help me refactoring the code. blow is my service where am using rx js for subscribing.
import {Subject} from 'rxjs/Subject';
import { Injectable } from '#angular/core';
#Injectable()
export class CommonService {
public clickStatusForBroadCastercomponentBtn = new Subject<string>();
public clickStatusForBcomponentBtn = new Subject<string>();
btnClickedInBroadCasterComponent(btnId: string): void {
this.clickStatusForBroadCastercomponentBtn.next(btnId);
}
btnClickedInComponentB(btnId: string): void {
this.clickStatusForBcomponentBtn.next(btnId);
}
}

You can do this using rxjs Subject declared in a service. Lets say, you have a service named AService:
import {BehaviorSubject} from 'rxjs/BehaviorSubject;
#Injectable()
export class AService {
public clickStatusForAcomponentBtn = new BehaviorSubject<string>('');
public clickStatusForBcomponentBtn = new BehaviorSubject<string>('');
btnClickedInComponentA(btnId: string): void {
this.clickStatusForAcomponentBtn.next(btnId);
}
btnClickedInComponentB(btnId: string): void {
this.clickStatusForAcomponentBtn.next(btnId);
}
}
Now, you can use this service in all your components those need to communicate with each other like this:
export class AComponent implement OnInit {
constructor(private aService: AService){}
ngOnInit(){
this.aService.clickStatusForBcomponentBtn .subscribe((clickedBtnId:string)=> {
// whenever button with id clickedBtnId clicked in Component B this observer
// will be get executed.So, do your necessary operation here.
}
}
btnClickListenerForA(event:Event){ /* in this component template you'll bind this listener with your button click event */
this.aService.btnClickedInComponentA((<HTMLButtonElement>event.target).id);
}
}
export class BComponent implement OnInit {
constructor(private aService: AService){}
ngOnInit(){
this.aService.clickStatusForAcomponentBtn .subscribe((clickedBtnId:string)=> {
// whenever button with id clickedBtnId clicked in Component A this observer
// will be get executed.So, do your necessary operation here.
}
}
btnClickListenerForB(event:Event){ /* in this component template you'll bind this listener with your button click event */
this.aService.btnClickedInComponentB((<HTMLButtonElement>event.target).id);
}
}
If you review the code, you'll understand two subjects are used to pass communication between two component. This way, you'll able to communicate between any number of components.
Thus, you can declare a rxjs subject for every button and for listening any button's click event you've to subscribe that buttons subject in other components where you want to listen that buttons event.
Hope this will guide you in a right direction.

You should use a shared service with a BehaviorSubject to emit any changes to any component listing to it please take a look at my answer Here I posted it like a few seconds ago on a similar question.

Related

How do I pass data from one component to another (New Browser Tab) in angular?

I'm new to angular and I don't know how to pass data between two components using routers. This is my first component view,
when I press view report button I need to call another component with the first component data. This is my first component view report click button code.
<button type="button" (click)="onFuelViewReport()" class="btn btn-success ">
<b>view Report</b>
</button>
when clicking the button it calls onFuelViewReport() function in the first component and using this function it opens the second component view with a new browser window (tab). What I want is to pass data from the first component to the second component from here. Please help me to do this.
onFuelViewReport() {
this.router.navigate([]).then(result => {
window.open("/pages/view-report", "_blank");
});
}
If you want to share data from child component to parent component, you can use #Output event emitter or if your are trying to share data within unrelated components, you can use BehaviourSubject (This also works in case of parent to child component communication and vice versa).
Child to Parent: Sharing Data via Output() and EventEmitter
parent.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
child.component.ts
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "Hola Mundo!"
#Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
Unrelated Components: Sharing Data with a Service
data.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject('default message');
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
parent.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-parent',
template: `
{{message}}
`,
styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
}
sibling.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-sibling',
template: `
{{message}}
<button (click)="newMessage()">New Message</button>
`,
styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
newMessage() {
this.data.changeMessage("Hello from Sibling")
}
}
The window.open looks absolutely awful. Use this.router.navigate(['/heroes']);.
So if I understand correctly you have a list of items and when you click on one of the items, the details page of that item should open?
Best practice is to allow the detail route to have a property to set. the Angular Routing & Navigation page is very complete. It shows that you should use :id - { path: 'hero/:id', component: HeroDetailComponent }. When you open the detail page, you get the id variable and then get the data for it.

Angular 8: send event from one component to sibling component

I have a sidebar component and a page component.
The sidebar component has a #ViewChild which is an ngbAccordion from Angular Boostrap. I want to trigger its collapseAll method from the page component.
So the sidebar has
#ViewChild('webAccordion', { static: false })
webAccordion: NgbAccordion;
#ViewChild('pageAccordion', { static: false })
pageAccordion: NgbAccordion;
collapseAllAccordions() {
this.webAccordion.collapseAll();
this.pageAccordion.collapseAll();
}
When the "page" component loads, I want to emit an event to the "sidebar" component that triggers my collapseAllAccordions function.
I know how to do this with parent/child components, and most of the stuff I can find with Google and here on SO discusses parent/child situations. Except in my case they are sibling components. I'm not sure how to hand siblings.
You can use a service:
Inject a service into two sibling components.
Add an emitter or an Observable to the service.
Add a function in the service to change the value of the Observable / emit a new value if your using an emitter.
Use the function in your "page" component.
Subscribe to the emitter or the Observable in your "sidebar" component and trigger collapseAllAccordions.
You could use intermediate singleton service between these components and share the data/actions. For example,
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss']
})
export class SideBarComponent implements OnInit {
constructor(private service: AppService) { }
onClick() {
this.service.collapse();
}
ngOnInit(): void { }
}
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss']
})
export class PageComponent implements OnInit {
constructor(private service: AppService) {
// Create a observable object which listens to service and
// change the behaviour of current page component and vice versa
}
ngOnInit(): void { }
}
If you require further assistance please create stackblitz or codesandbox to replicate this issue.

Angular4 - let multiple unrelated components notify each other of the problem of updating data, and whether there is a cleaner coding method?

I have encountered a project in progress, let multiple unrelated components notify each other of the update data, is there a cleaner coding method?
There are 3 components (more likely later) and a common-data component. They have no parent-child relationship with each other and only show on the same screen.
The desired effect is to press the button of any component, update the contents of common-data, and notify yourself and other components to fetch new messages from common-data.
At present, my approach is to use Rx's Observable and Subscription, but they must be imported in the component.ts and service.ts files of each component, and a lot of duplicate code appears, it is very messy, I don't know what is better. practice?
Thanks!
My code :
The sample name is test-a-comp (a.b.c and so on, the code is the same)
test-a-comp.html
<p>
{{ownMessage}}
</p>
<button (click)="sendChange()">update</button>
test-a-comp.component
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { CommonData } from '../common-data/common-data';
import { TestACompService } from './test-a-comp.service';
import { TestBCompService } from '../test-b-comp/test-b-comp.service';
import { TestCCompService } from '../test-c-comp/test-c-comp.service';
#Component({
selector: 'app-test-a-comp',
templateUrl: './test-a-comp.component.html',
styleUrls: ['./test-a-comp.component.css']
})
export class TestACompComponent implements OnInit {
subscription: Subscription;
ownMessage;
constructor(
private testAService: TestACompService,
private testBService: TestBCompService,
private testCService: TestCCompService,
) {
this.subscription = this.testAService.getMessage()
.subscribe((test) => {
CommonData.message = test;
});
this.subscription = this.testBService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
this.subscription = this.testCService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
}
ngOnInit() {
}
sendChange() {
this.testAService.sendMessage();
}
}
test-a-comp.service:
import { Injectable } from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
#Injectable()
export class TestACompService {
subscription: Subscription;
private subject = new Subject<any>();
constructor() {
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
sendMessage(): void {
this.subject.next('update message from A');
}
}
As far as i understand & you've mentioned in the above, there is a button in one of the component (test-a-component.html). If you update the button, you need to send message to other components which are subscribed.
The Components which have no Parent-Child relationship can communicate via a service:
Create a single service file (In your case: test-a-comp.service)
Create a Subject on what data you need to communicate via this service:
export class testMessageService {
constructor() {}
// Observable string sources
private message = new Subject<string>();
//Observable string streams
testMessage$ = this.message.asObservable();
constructor() {}
// Method to send message when a button is clicked
sendMessage(message: string) {
this.message.next(message);
}
/* You don't need "getMessage()" method as you've already subscribed to
the observables. There subscribed Observable string streams are
injected in your components (As below point 3) to display / do other
operation on the message. */
}
In your other Components, where you want to receive messages, do the following:
export class TestComponent 1 {
myMessage1: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage1 = message;
});
}
export class TestComponent 2 {
myMessage2: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage2 = message;
});
}
export class TestComponent 3 {
myMessage3: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage3 = message;
});
}
For more information/guidance refer Component interaction via a common
service: https://angular.io/guide/component-interaction
Hope this helps!

How to use EventManager to listen to window.resize events in angular?

I'm borrowing some code from this stackoverflow: Angular window resize event
The author of the answer says I should be using the EventManager if I want to listen for window events from a service and not break Angular Universal. That being said, is this answer still true? If so, could someone show me why when I subscribe to the onResize$ Observable logs nothing when the window is resized?
import { EventManager } from '#angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Injectable } from '#angular/core';
#Injectable()
export class ResizeService {
get onResize$(): Observable<Window> {
return this.resizeSubject.asObservable().filter(_ => !_);
}
private resizeSubject: Subject<Window>;
constructor(private eventManager: EventManager) {
this.resizeSubject = new Subject();
this.eventManager.addGlobalEventListener('window', 'resize', this.onResize.bind(this));
}
private onResize(event: UIEvent) {
this.resizeSubject.next(<Window>event.target);
}
}
my-component.ts
export class MenuContainer implements OnInit {
constructor(private resizeService : ResizeService ) {
}
public ngOnInit() {
this.resizeService.onResize$.subscribe(console.log); // never logs when I resize
}
}

Angular2 component doesn't detect routing parameter updates (Router 3.0)

I've got a small Plunk I'm using for playing around with the new Router 3.0 alpha currently available in Angular 2. It works well in general, but the issue is that once I click on a link that routes to the 'detail' component with a particular ID, it never changes when I click on a different link with a different ID. The component is never being reinstantiated, so it only ever shows what it was passed the very first time it is loaded.
Here's the component in question:
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { ContactsService } from './contacts.service';
#Component({
selector: 'contacts-detail',
template: `
<h2>{{contact.name}}</h2>
`
})
export class ContactsDetailComponent implements OnInit {
constructor(private contactsService: ContactsService, private route: ActivatedRoute) {
}
ngOnInit() {
this.contact = this.contactsService.getContact(this.route.snapshot.params.id);
console.log('Fetching user', this.route.snapshot.params.id);
}
}
Here is the Plunk demonstrating the problem. Click on one author name and then another to see it not change.
In your ContactsDetailComponent, change the OnInit to this:
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = +params['id'];
this.contact = this.contactsService.getContact(id);
});
}
Worked for me in your Plunk.
There appear to be multiple lifeCycle hooks that could possibly be used for this. I managed to get the desired behavior using the DoCheck interface and implementing the associated ngDoCheck() method in the component class, as seen below.
import { Component, DoCheck } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { ContactsService } from './contacts.service';
#Component({
selector: 'contacts-detail',
template: `
<h2>{{contact.name}}</h2>
`
})
export class ContactsDetailComponent implements AfterViewChecked, DoCheck {
constructor(private contactsService: ContactsService, private route: ActivatedRoute) {
}
ngDoCheck() {
this.contact = this.contactsService.getContact(this.route.snapshot.params.id);
}
}
Here's a plunk with the updated code.
I'm not convinced this is the best/correct lifecycle hook to use, though. Perhaps there is some sort of hook available from the Router that would serve this better.
Another way to do this:
ngOnInit() {
this.route.params.forEach((params: Params) => {
let id = +params['id'];
this.contact = this.contactsService.getContact(id);
});
}
Here retrieve the route params from an Observable. The advantage of using an Observable over Snapshot is to reuse the component without instantiating it again. Looks like this is the recommended way of doing this as per Angular 2.0 final documentation.

Categories

Resources