SherlockHolmesKePapa
SherlockHolmesKePapa

Reputation: 643

How to change data in other components based on click event in one component in Angular?

I'm a beginner in Angular and I developed an Angular 4/5 application consisting of 5 components: A, B, C, D, and E. All these components are displayed on the single (same) page. The component A consist of a dropdown list in the navigation bar. Now, what I've to do is that after selecting any particular option in the dropdown list of the component A, I've to change the data of other components B, C, D, and E simultaneously.

I'm extremely confused as how to achieve that kind of data binding. Can anyone help on how to achieve this?

Since posting the whole code will be an extremely difficult task here, since I have about 2 dozen files so, I 've posted the code for component A, component B, and the root component.

component A code, where component A is navigation bar

navigation-bar.component.html:

<div class="container model-family-navigation-bar">
  <nav class="navbar navbar-expand-sm navbar-dark model-family-navigation-bar">
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
      aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav">
        <!-- <div class="col-sm-auto"> -->
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle navigation-bar-content text-white" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
            aria-haspopup="true" aria-expanded="false">
            <span id="selected">Model Family</span>
          </a>
          <div class="dropdown-menu bg-success table-striped" aria-labelledby="navbarDropdown">
            <h1 class="dropdown-header text-white">Select Model Family</h1>
            <div *ngFor="let item of dropdownElements">
              <div class="dropdown-divider"></div>
              <a class="dropdown-item" (click)="showList()">{{item.model_family}}</a>
            </div>
          </div>
        </li>
        <!-- </div> -->
        <!-- <div class="col-sm-auto"> -->
        <li class="nav-item">
          <span class="navigation-bar-content navbar-text text-white ml-4">Proportional Directional Valve</span>
        </li>
        <!-- </div> -->
      </ul>

      <ul class="navbar-nav ml-auto">
        <li class="nav-item navbar-right">
          <a class="nav-link text-white" href="https://www.google.com/">Data Sheet</a>
        </li>
      </ul>

    </div>
  </nav>
</div>

navigation-bar.component.ts:

import { Component, OnInit } from '@angular/core';
import {NavigationDropdownElements} from './navigation-bar-dropdownElements'

@Component({
  selector: 'navigation-bar',
  templateUrl: './navigation-bar.component.html',
  styleUrls: ['./navigation-bar.component.css']
})
export class NavigationBarComponent implements OnInit {
  dropdownElements = NavigationDropdownElements;

  ngOnInit() {}

}

navigation-bar-dropdownElements.ts

class NavigationDropdown{
    model_family: string;
}
export const NavigationDropdownElements: NavigationDropdown[] = [
    { model_family: 'CP210-1'},
    { model_family: 'CP211-2'},
    { model_family: 'CP212-3'}
];

component B code, where component B is model-family-description

model-family-description.component.ts:

import { Component, OnInit } from '@angular/core';
import { descriptionElements1, descriptionElements2 } from './model-family-description-elements';
@Component({
  selector: 'model-family-description',
  templateUrl: './model-family-description.component.html',
  styleUrls: ['./model-family-description.component.css']
})
export class ModelFamilyDescriptionComponent implements OnInit {
  desc1 = descriptionElements1;
  desc2 = descriptionElements2;
  constructor() { }

  ngOnInit() {
  }

}

model-family-description.component.html

<div class="container border-section">
  <div class="row">
    <p class="ml-2">{{desc2.description}}</p>
  </div>
</div>

model-family-description-elements.ts

class ModelFamilyDescription {
    description: string;
}

export const descriptionElements1: ModelFamilyDescription =
    { description: 'This is a proportional, 3 position 4 way, directional control valve.' };

export const descriptionElements2: ModelFamilyDescription =
    { description: 'This is a proportional, 4 position 5 way, non-directional control valve.' };

root component:

app.component.html:

<navigation-bar></navigation-bar>
<model-family-description></model-family-description>

app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

}

Please help.

Upvotes: 1

Views: 5581

Answers (4)

Ashraful Islam
Ashraful Islam

Reputation: 1263

You need to emit drop-down option changes from component A

Then receive the changes from component B and C with the setter method. Setter method always get called during value update.

Check Here: https://stackblitz.com/edit/angular-dshm6g

You can also use observable pattern from rxJs to solve this kind of problem.

Upvotes: 1

HDJEMAI
HDJEMAI

Reputation: 9800

First create a service:

For example:

message.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class MessageService {
    private subject = new Subject<any>();

    sendMessage(message: string) {
        this.subject.next({ text: message });
    }

    clearMessage() {
        this.subject.next();
    }

    getMessage(): Observable<any> {
        return this.subject.asObservable();
    }
}

Then add that service to app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

import { MessageService } from './message.service';

@NgModule({
  imports:      [
    BrowserModule,
    FormsModule,
    ModalModule.forRoot()
  ],
  declarations: [
    AppComponent 
  ],
  providers:    [
    MessageService
  ],
  bootstrap:    [ 
    AppComponent 
  ]
})
export class AppModule { }

Now you need to import the service in the component where you want to send the data from (component A in your case)

something like:

import { Component, OnInit } from '@angular/core';
import {NavigationDropdownElements} from './navigation-bar-dropdownElements'

import { MessageService } from './message.service';

@Component({
  selector: 'navigation-bar',
  templateUrl: './navigation-bar.component.html',
  styleUrls: ['./navigation-bar.component.css']
})
export class NavigationBarComponent implements OnInit {
  dropdownElements = NavigationDropdownElements;

  constructor(private messageService: MessageService) {}

  sendMessage(data:string): void {
    // send message to subscribers via observable subject
    this.messageService.sendMessage(data);
  }

  showList() {
    //on a click, you send the message here
    let message:string="data example";
    this.sendMessage(message);
  }

  clearMessage(): void {
    // clear message
    this.messageService.clearMessage();
  }

  ngOnInit() {}

}

And finaly you have to import the service in the component where you want to read the message or data to receive from the first component (component B for example)

something like:

import { Component, OnInit,  OnDestroy} from '@angular/core';
import { descriptionElements1, descriptionElements2 } from './model-family-description-elements';
import { Subscription } from 'rxjs/Subscription';

import { MessageService } from './message.service';
@Component({
  selector: 'model-family-description',
  templateUrl: './model-family-description.component.html',
  styleUrls: ['./model-family-description.component.css']
})
export class ModelFamilyDescriptionComponent implements OnInit {
  //you receive the data in this variable --> message
  message: any;
  subscription: Subscription;

  desc1 = descriptionElements1;
  desc2 = descriptionElements2;

  constructor(private messageService: MessageService) {

  }

  ngOnDestroy() {
    // unsubscribe to ensure no memory leaks
    this.subscription.unsubscribe();
  }

  ngOnInit() {
    // subscribe to component A messages
    this.subscription = this.messageService.getMessage().subscribe(message => { this.message = message; });
  }

}

Upvotes: 2

Khaled Ahmed
Khaled Ahmed

Reputation: 1134

there are two ways to do this using EventEmitter or using Subject I prefer Subject. you can read more about Subject from the documentation of rxjs lib http://reactivex.io/rxjs/file/es6/Subject.js.html#lineNumber19 it's not an angular implemented function unlike EventEmitter if you want to read more about EventEmitter https://angular.io/api/core/EventEmitter

Upvotes: 0

szmitas
szmitas

Reputation: 331

You should create a common @Injectable with Subject

Example:

@Injectable()
export class CommonService {

    private dropdownValue: Subject<string> = new Subject();

    public getDropdownValue() : Observable {
        return this.dropdownValue.asObservable();
    }

    public setDropdownValue(value: string) : void {
        this.dropdownValue.next(value);
    }

Then in component A:

constructor(
    private commonService: CommonService
) {
   ...
}

protected onDropdownChange(value: string) : void {
    this.commonService.setDropdownValue(value);
}        

And in other components:

constructor(
    private commonService: CommonService
) {
    this.commonService.getDropdownValue().subscribe((newValue) => {
        // action on data change
    });
}

Upvotes: 2

Related Questions