I'm using Material with angular, and I want to toggle the Sidenav from another component, using viewchild, but i get undefined when i try to get the mat-sidenav element. Heres mi code
sidenav.component.html
<mat-sidenav-container class="example-container">
<mat-sidenav #sidenav class="example-sidenav">
Sidenav content goes here!
</mat-sidenav>
<div class="sidenav-container">
123
</div>
</mat-sidenav-container>
sidenav.component.ts
import { Component, OnInit, ViewChild } from '#angular/core';
import {MatSidenav} from '#angular/material/sidenav';
#Component({
selector: 'app-sidemenu',
templateUrl: './sidemenu.component.html',
styleUrls: ['./sidemenu.component.scss']
})
export class SidemenuComponent implements OnInit {
#ViewChild('sidenav') sidenav;
constructor() { }
ngOnInit() {
}
openSideNav(){
console.log(this.sidenav)
this.sidenav.open();
}
}
And this is the component from where i try to toggle the sidenav
header.component.ts
import { Component, OnInit } from '#angular/core';
import { SidemenuComponent } from '../sidemenu/sidemenu.component';
#Component({
providers:[SidemenuComponent],
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
constructor(private sm: SidemenuComponent) { }
ngOnInit() {
}
openSideNav(){
this.sm.openSideNav()
}
}
header.component.html
<mat-toolbar color="primary">
<mat-toolbar-row>
<div class="navbar-brand">
<img class="img-fluid" src="./assets/icons/logo_femsa_blanco.png"/>
</div>
<button class="buton-menu" mat-icon-button (click)="openSideNav()">
<img class="img-fluid menu-icon" src="./assets/icons/icons8-menu-filled-50.png"/>
</button>
<span class="toolbar-spacer"></span>
<div class="font-weight-light text-right user-name">
Hola Mundo!
</div>
</mat-toolbar-row>
</mat-toolbar>
Hope someone can help. I's starting with angular. Sorry for bad english
base on your sample, i have create Service to manage communication between component.
Goal is to have, from anywhere on your code, API to ask menu to open / close / toggle.
Functional step :
1/ From anywhere (button click, custom things), you want to make action on your side-nav. You call public function from your service, exemple : this.menuService.open()
2/ if needed, your service will next new menu state by Observable.
3/ Component who manage your sidenav will subscribe to any change on this state and do "open/close" if needed.
State is manage by Subject and internal service flag
export class MenuService {
private menuIsOpen$ : Subject<boolean>;
private menuIsOpen: boolean = false;
constructor() {
this.menuIsOpen$ = new Subject<boolean>();
}
/**
* If menu is open, let close it
**/
public open() {
if(!this.menuIsOpen) {
this.menuIsOpen = true;
this.menuIsOpen$.next(false);
}
}
/**
* Both silence open and close is use by navbar output, to silence switch internal flag.
**/
public silenceOpen() {
this.menuIsOpen = true;
}
public silenceClose() {
this.menuIsOpen = false;
}
/**
* If menu is close, let open it
**/
public close() {
if(this.menuIsOpen) {
this.menuIsOpen = false;
this.menuIsOpen$.next(false);
}
}
public toggle() {
this.menuIsOpen = !this.menuIsOpen;
this.menuIsOpen$.next(this.menuIsOpen);
}
public asObservable()
{
return this.menuIsOpen$.asObservable();
}
}
then your Component who embed sidenav :
export class SidemenuComponent implements OnInit {
#ViewChild('sidenav') sidenav: MatSidenav;
constructor(private menuService: MenuService)
{
}
ngOnInit() {
/**
When you reveive order to open / close sidenav.
**/
this.menuService.asObservable().subscribe((isOpen: boolean) => {
if(isOpen) {
this.sidenav.close();
}
else{
this.sidenav.open();
}
});
}
onOpenedChange() {
this.menuService.silenceOpen();
}
onClosedChange() {
this.menuService.silenceClose();
}
}
Html side :
Sample : https://stackblitz.com/edit/angular-2faa8z
Related
I'm using class binding to add and remove classes based on click of sidebar button to minimize and maximize the sidebar.
I need same functionality to add and remove class in footer component also to maximize and minimize width of the footer based on click made in sidebar component.
sidebar.component.html
<nav class="sidebar-container" [ngClass]="minimize ? 'minimize' : ''">
<ul>
<li class="sidebar-minimize">
<a (click)="clickEvent()">Minmax</a>
</li>
</ul>
</nav>
sidebar.component.ts
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent implements OnInit {
minimize: boolean = false;
ngOnInit(): void {
}
clickEvent() {
this.minimize = !this.minimize;
}
}
But i need same actions to be performed in footer component to add another CSS class through class binding followed by same clickEvent() method because in my situation sidebar component and footer components are interdependent on this clickEvent() function
For eg,
<footer (click)="clickEvent()" [ngClass]="minimize ? 'minimize':''">
<p>footer works</p>
</footer>
Is it possible to use same clickEvent() function in footer component for class binding
So in this case you can use a service. For example in your service you have a property like below:
#Injectable({
providedIn: 'root'
})
export class TestService {
private minimize: boolean;
public minimizeSubject: Subject<void>;
constructor() {
this.minimizeSubject = new Subject<void>();
this.minimize = false;
}
set setMinimize(minimize: boolean) {
this.minimize = minimize;
this.minimizeSubject.next();
}
Sidebar component should look like below:
export class SidebarComponent{
public minimize: boolean;
constructor(private testService: TestService) {
this.minimize = false;
}
public onClicked(): void {
this.minimize = !this.minimize;
this.testService.setMinimize = this.minimize;
}
In the end the footer component:
export class FooterComponent implements OnInit, OnDestroy {
private subscription: Subscription;
constructor(private testService: TestService) {
this.subscription = new Subscription();
}
ngOnInit(): void {
this.subscription = this.testService.minimizeSubject.subscribe(minimize => {
this.minimize = minimize;
});
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
I have a parent component HeaderComponent with two children, NavComponent and BurgerComponent! At the momemnt there is a class that toggles the active inactive state of the burger when clicked on it! Below the first image when the burger is inactive, the second with active burger and NavComponent visible.
When clicked on the NavComponent area I have managed that the NavComponent closes.
The issue: The the NavComponent is closed by click on nav area, but I also need the burger to go to the inactive state(image 1).
What I have so far:
NavCompoent HTML with click event
<nav class="nav-menu {{ menuStatus }}" (click)="collapseMenu($event)">
NavComponent TS:
export class NavComponent implements OnInit {
title: string;
closeMenu: boolean;
#Output() sendTitle = new EventEmitter < string > ();
#Output() menuClose = new EventEmitter<any>();
#Input() menuStatus: boolean;
active = false;
constructor() {}
ngOnInit() {}
getText(event) {
this.title = event.target.innerText;
this.sendTitle.emit(this.title)
console.log("title sent", this.title);
}
collapseMenu($event) {
this.menuStatus = false;
this.menuClose.emit(this.menuStatus);
}
}
HeaderComponent HTML (this is the parent component)
<header class="sticky">
<div class="header-container">
<div class="header-left">
<h1>{{pageTitle}}</h1>
</div>
<div class="header-right">
<app-burger (opened)="burgerStatus($event)" [menuCloseBurger]="menuClose"></app-burger>
</div>
</div>
</header>
<app-nav (sendTitle)="getTitle($event)" [menuStatus]="burger" (menuClose)="sendingMenuClose($event)"></app-nav>
Header Component TS:
export class HeaderComponentComponent implements OnInit {
route: string;
pageTitle: string;
burger: string;
menuClose: string;
constructor(location: Location, router: Router) {
router.events.subscribe(val => {
this.pageTitle = location.path();
this.pageTitle = this.pageTitle.substring(1);
});
}
ngOnInit() {
this.pageTitle = this.route;
console.log(this.pageTitle);
}
getTitle($event) {
console.log(this.route);
this.pageTitle = $event;
}
burgerStatus($event) {
this.burger = $event;
console.log($event);
}
sendingMenuClose($event) {
this.menuClose = $event;
console.log("menu close at parent", this.menuClose);
}
}
BurgerComponent TS:
export class BurgerComponent implements OnInit {
active: boolean;
#Output() opened = new EventEmitter<any>();
#Input() menuCloseBurger: string;
constructor() { }
ngOnInit() {
this.active = false;
}
onBurgerClicked() {
this.active = !this.active;
this.opened.emit(this.active);
}
}
BurgerComponent HTML:
<div class="burger-menu" [class.active]="active" (click)="onBurgerClicked()">
<div class="burger-container">
<div class="burger-inner"></div>
</div>
</div>
collapseMenu() send a boolean value false, I need to get this into the BurgerCompnent class somehow so the value of that is false and the burger close. I am so close, I can't figure out the last step!
As per #PitchBlackCat's recommendation I have create a service NavStatusService!
import { Injectable } from '#angular/core';
import { EventEmitter } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class NavStatusService {
constructor() { }
public readonly isNavCollapsed$ = new EventEmitter<boolean>();
}
I have implemented it in the BurgerComponent:
onBurgerClicked() {
this.active = !this.active;
this.state.isNavCollapsed$.emit(this.active);
}
Now I am stuck as to how the communication between in the two component, Burger and Nav supposed to work!
You need a separate service to manage the shared variables.
You could try a framework like ngrx or create your own service along the lines of the example provided below.
Notice how the application-state.service has become the owner of the data that is shared between multiple components. This way, components that live in seperate parts of your layout can share data, whithout knowing about eachother.
application-state.service
export class ApplicationStateService implements OnInit {
public isNavCollapsed: boolean = false;
}
burger.component
<button class="burger" [class.active]="!state.isNavCollapsed" (click)="onBurgerClicked()">
</button>
import { Component, OnInit } from '#angular/core';
import { ApplicationStateService } from '../application-state.service';
#Component({
selector: 'app-burger',
templateUrl: './burger.component.html',
styleUrls: ['./burger.component.css']
})
export class BurgerComponent implements OnInit {
constructor(public state: ApplicationStateService) { }
ngOnInit() {
}
onBurgerClicked() {
this.state.isNavCollapsed = !this.state.isNavCollapsed;
}
}
nav.component
<ul *ngIf="!state.isNavCollapsed">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
<li>
<button (click)="state.isNavCollapsed = true">close</button>
</li>
</ul>
import { Component, OnInit } from '#angular/core';
import { ApplicationStateService } from '../application-state.service';
#Component({
selector: 'app-nav',
templateUrl: './nav.component.html',
styleUrls: ['./nav.component.css']
})
export class NavComponent implements OnInit {
constructor(public state: ApplicationStateService) { }
ngOnInit() {
}
}
Check out this example on StackBlitz to see it in action
I am trying to implement a functionality which will display a list of nodes selected from the TreeNode structure, on the right.
Something similar to this plunkr I found : http://next.plnkr.co/edit/1Fr83XHkY0bWd9IzOwuT?p=preview&utm_source=legacy&utm_medium=worker&utm_campaign=next&preview
In my current method, I am getting the right-side list to be in the same hierarchy order as that on the left.
For eg : Even if I select node "B" before node "A", in the list, it still shows first A and then B (because A is above B in the displayed hierarchy).
Rightside.component.html :
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<ul class="selection-list">
<li *ngFor="let item of getSelections()">
<button class="btn" (click)="deselect(item)" *ngIf="item.selected">
<i class="fa fa-close"> {{ item.displayName }} </i>
</button>
</li>
</ul>
rightside.component.ts:
import { Component, Input, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
import { DataService } from '../../shared/service/data.service';
import { TreeNode } from '../../shared/dto/TreeNode';
import html from './rightside.component.html';
import css from './rightside.component.css';
#Component({
selector: 'rightside-component',
template: html,
providers: [DataService],
styles: [css],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RightSideComponent implements OnInit {
selections: string[];
#Input() treeNode: TreeNode<string>[];
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
}
getSelections() : TreeNode<string>[] {
if (typeof(this.treeNode) == "undefined" || (this.treeNode) === null) {
return [];
}
return this.treeNode;
}
deselect(item: TreeNode<string>): void {
if((item.children) !== null) {
item.children.forEach(element => {
this.deselect(element);
});
}
item.selected = false;
}
}
productline.component.html:
<p>Productlines</p>
<mat-input-container>
<input #searchInput matInput placeholder="Search for Product Line">
</mat-input-container>
<div class="flex-container">
<div class="PLCheck" *ngIf="isDataLoaded">
<fortune-select
(dataChanged)="onDataChange($event)"
[searchTerm]="searchInput.value"
[currentNode]="tree"
[singleSelect]="singleSelect"
[collapsed]="true"></fortune-select>
</div>
<div class="sendToRight">
<rightside-component
[treeNode]="selectedProductLine">
</rightside-component>
</div>
</div>
Productline.component.ts:
import { Component, Input, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
import * as API from '../../shared/api-routes';
import { DataService } from '../../shared/service/data.service';
import { ValidationService } from '../../shared/service/validation.service';
import { Subject } from 'rxjs/Subject';
import { BasestepComponent } from '../basestep-component/basestep.component';
import { TreeNode } from '../../shared/dto/TreeNode';
import html from './productline.component.html';
import css from './productline.component.css';
#Component({
selector: 'productline-component',
template: html,
providers: [DataService],
styles: [css],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductlineComponent extends BasestepComponent<string> implements OnInit {
selectedProductLine: TreeNode<string>[];
constructor(dataService:DataService, cd:ChangeDetectorRef) {
super(dataService, cd);
}
ngOnInit() {
this.subject.subscribe((productline) => this.productLineChange(productline));
}
public productLineChange(productLine: TreeNode<string>[]):void {
this.selectedProductLine = productLine;
}
}
Basestep.component.ts:
import { Input, ChangeDetectorRef } from '#angular/core';
import { castTree, ReportTemplate } from '../report-common';
import { DataService } from '../../shared/service/data.service';
import { Subject } from 'rxjs/Subject';
import { ValidationService } from '../../shared/service/validation.service';
import { TreeNode } from '../../shared/dto/TreeNode';
export class BasestepComponent<T> {
public tree: TreeNode<T>;
public singleSelect: boolean = false;
public isDataLoaded: boolean;
public template: ReportTemplate;
#Input() templateSubject: Subject<ReportTemplate>;
#Input() subject: Subject<TreeNode<T>[]>;
constructor(private dataService:DataService, private cd: ChangeDetectorRef) {}
public onDataChange(event:TreeNode<T>[]):void {
if (!ValidationService.isNullOrUndefined(this.subject)) {
this.subject.next(event);
}
}
public markForCheck() {
this.cd.markForCheck();
}
public reset() {
this.tree = null;
this.isDataLoaded = false;
this.markForCheck();
}
}
I want the order on the right to be in the order in which the items are selected and not based on the hierarchy. Hence, even if A is above B in the hierarchy, I would want B to be shown and then A based on the selections.
Basically, I would like the selections to be displayed dynamically in the order that they are chosen.
EDIT 1:
Adding code for fortune-select.component.ts:
import { Component, Input, Output, EventEmitter } from '#angular/core';
import { TreeNode } from './../dto/TreeNode';
import html from './fortune-select.component.html';
import css from './fortune-select.component.css';
#Component({
selector: 'fortune-select',
template: html,
styles: [css],
})
export class FortuneSelectComponent<T> {
#Input() currentNode: TreeNode<T>;
#Input() collapsed: boolean = false;
#Input() linked: boolean = true;
#Input() searchTerm: string = '';
#Input() singleSelect: boolean = false;
#Output() dataChanged = new EventEmitter<TreeNode<T>[]>();
selectedRadioValue:T;
public checkboxChangedEvent():void {
let list:TreeNode<T>[];
this.currentNode.indeterminate = true;
if (this.linked) {
list = TreeNode.getTopLevelCheckedNodes(this.currentNode);
} else {
list = TreeNode.getAllCheckedNodes(this.currentNode);
}
this.dataChanged.emit(list);
}
public radioChangedEvent(event:TreeNode<T>):void {
this.dataChanged.emit([event]);
}
}
Is there a way I could maybe store every selection in a list and then display the list? That way, it will show the selections in order. Right now, the entire structure is getting traversed, hence it displays in the tree's order.
Is there a way to do this?
I need to scroll the tag in app.module.ts from another products component
app.module.ts
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<nav-header></nav-header>
<main class="mdl-layout__content" #scrollMe>
<router-outlet></router-outlet>
<app-footer [otherType]="'siteSetting'" [type]="'getAllPages'"></app-footer>
</main>
</div>
<cart-quantity-icon></cart-quantity-icon>
main tag i.e #scrollMe needs to be scrolled using following component
products.components.ts
import { Component, OnInit,ViewChild,ElementRef,ContentChild } from '#angular/core';
import { ActivatedRoute, NavigationEnd ,Router, Event, NavigationStart, RoutesRecognized,RouteConfigLoadStart,
RouteConfigLoadEnd, NavigationCancel, NavigationError} from '#angular/router';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
scrollTopButton = false;
#ViewChild('infinite') private myScrollContainer : ElementRef;
#ContentChild('scrollMe') private myScroll : ElementRef;
scrollToTop() {
console.log(this.scrollTopButton);
this.myScrollContainer.nativeElement.scrollTo(0, 0);
this.myScroll.nativeElement.scrollTo(0, 0);
window.scrollTo(0,0);
//this.content.scrollToTop(700);
this.scrollTopButton = false;
}}
<div style=" max-height: 750px;
overflow: auto;" #infinite infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="1000"
(scrolled)="getProducts()" [scrollWindow]="false" class="mdl-grid mdl-cell mdl-cell--top mdl-cell--9-col mdl-cell--8-col-tablet mdl-cell--4-col-phone">
<!-- new card -->
<product *ngFor="let p of products" [data]="p" [type]="'normal'"></product>
<div >
</div>
<button *ngIf="scrollTopButton" (click)="scrollToTop()" id="return-to-top" mat-fab color="primary"><i class="material-icons" style="font-size: 24px;">keyboard_arrow_up</i></button>
When I click the return-to-top button then I need to scroll #infinite and # scrollMe. I have tried following .Also instead of contentChild tried viewChild
#ViewChild('infinite') private myScrollContainer : ElementRef;
#ContentChild('scrollMe') private myScroll : ElementRef; //Also ViewChild here but none works!!
scrollToTop() {
console.log(this.scrollTopButton);
this.myScrollContainer.nativeElement.scrollTo(0, 0);
this.myScroll.nativeElement.scrollTo(0, 0);
window.scrollTo(0,0);
//this.content.scrollToTop(700);
this.scrollTopButton = false;
}}
infinite works but scrollMe does not.
This does not work because ContentChild from app-products does not have access to parent elements in DOM. You must use service for communication.
ComService to create and and to provide.
import { Injectable, EventEmitter } from '#angular/core';
import { Observable, Subject } from 'rxjs';
#Injectable()
export class ComService {
messageStream = new Subject();
getMessageSubscribe() {
return Observable.from(this.messageStream);
}
sendMessage() {
this.messageStream.next(this.filteredOrderList);
}
}
In component main component subscribe messages:
#ViewChild('scrollMe') private myScroll : ElementRef;
constructor(
private comService: ComService
) {
this.subscribeMes = this.comService.getComunication().subsribe(element => {
if(element) {
this.myScroll.nativeElement.scrollTo(0, 0);
//when element is true you got info from child to scroll
}
})
ngOnDestroy() {
this.subscribeMes.unsubscribe();
}
}
And #scrollMe insert in top element in template of main component.
In app-products:
constructor(
private comService: ComService
) {}
#ViewChild('infinite') private myScrollContainer : ElementRef;
scrollToTop() {
console.log(this.scrollTopButton);
this.myScrollContainer.nativeElement.scrollTo(0, 0);
this.comService.sendMessage(true);
window.scrollTo(0,0);
//this.content.scrollToTop(700);
this.scrollTopButton = false;
}}
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.