First, what you need to understand the relationship between the components. Then you can choose the right way to communicate. I will try to explain all the methods that I know and use in my practice for the interaction between components.
What can be the relationship between the components?
1. Parent> Child

Input data exchange
This is probably the most common way to exchange data. It works with the @Input() decorator, which allows you to transfer data through a template.
parent.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ' <child-component [childProperty]="parentProperty"></child-component> ', styleUrls: ['./parent.component.css'] }) export class ParentComponent{ parentProperty = "I come from parent" constructor() { } }
child.component.ts
import { Component, Input } from '@angular/core'; @Component({ selector: 'child-component', template: ' Hi {{ childProperty }} ', styleUrls: ['./child.component.css'] }) export class ChildComponent { @Input() childProperty: string; constructor() { } }
This is a very simple method. It is easy to use. We can also track data changes in a child component using ngOnChanges .
But do not forget that if we use an object as data and change the parameters of this object, the link to it will not change. Therefore, if we want to get a modified object in a child component, it must be immutable.
2. Child> Parent

Data exchange through ViewChild
ViewChild allows one component to be embedded in another, providing parent access with its attributes and functions. However, one caveat is that child will not be available until the view is initialized. This means that we need to implement the AfterViewInit lifecycle hook to receive data from the child.
parent.component.ts
import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { ChildComponent } from "../child/child.component"; @Component({ selector: 'parent-component', template: ' Message: {{ message }} <child-compnent></child-compnent> ', styleUrls: ['./parent.component.css'] }) export class ParentComponent implements AfterViewInit { @ViewChild(ChildComponent) child; constructor() { } message:string; ngAfterViewInit() { this.message = this.child.message } }
child.component.ts
import { Component} from '@angular/core'; @Component({ selector: 'child-component', template: ' ', styleUrls: ['./child.component.css'] }) export class ChildComponent { message = 'Hello!'; constructor() { } }
Communication via Output () and EventEmitter
Another way to exchange data is to send data from a child, which can be listed by the parent. This approach is ideal when you want to share data changes that happen in things like button clicks, form entries, and other user events.
parent.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ' Message: {{message}} <child-component (messageEvent)="receiveMessage($event)"></child-component> ', 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: 'child-component', template: ' <button (click)="sendMessage()">Send Message</button> ', styleUrls: ['./child.component.css'] }) export class ChildComponent { message: string = "Hello!" @Output() messageEvent = new EventEmitter<string>(); constructor() { } sendMessage() { this.messageEvent.emit(this.message) } }
3. Siblings

Child> Parent> Child
I am trying to explain other ways of communicating between brothers and sisters below. But you could already understand one way to understand the above methods.
parent.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ' Message: {{message}} <child-one-component (messageEvent)="receiveMessage($event)"></child1-component> <child-two-component [childMessage]="message"></child2-component> ', styleUrls: ['./parent.component.css'] }) export class ParentComponent { constructor() { } message: string; receiveMessage($event) { this.message = $event } }
baby one.component.ts
import { Component, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'child-one-component', template: ' <button (click)="sendMessage()">Send Message</button> ', styleUrls: ['./child-one.component.css'] }) export class ChildOneComponent { message: string = "Hello!" @Output() messageEvent = new EventEmitter<string>(); constructor() { } sendMessage() { this.messageEvent.emit(this.message) } }
baby two.component.ts
import { Component, Input } from '@angular/core'; @Component({ selector: 'child-two-component', template: ' {{ message }} ', styleUrls: ['./child-two.component.css'] }) export class ChildTwoComponent { @Input() childMessage: string; constructor() { } }
4. Unbound components

All the methods that I described below can be used for all the above options for communication between components. But each has its own advantages and disadvantages.
Data exchange with the service
When transferring data between components that do not have a direct connection, such as siblings, grandchildren, etc., you must use a common service. When you have data that should always be synchronized, I find that RxJS BehaviorSubject is very useful in this situation.
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) } }
first.component.ts
import { Component, OnInit } from '@angular/core'; import { DataService } from "../data.service"; @Component({ selector: 'first-componennt', template: ' {{message}} ', styleUrls: ['./first.component.css'] }) export class FirstComponent implements OnInit { message:string; constructor(private data: DataService) {
second.component.ts
import { Component, OnInit } from '@angular/core'; import { DataService } from "../data.service"; @Component({ selector: 'second-component', template: ' {{message}} <button (click)="newMessage()">New Message</button> ', styleUrls: ['./second.component.css'] }) export class SecondComponent implements OnInit { message:string; constructor(private data: DataService) { } ngOnInit() { this.data.currentMessage.subscribe(message => this.message = message) } newMessage() { this.data.changeMessage("Hello from Second Component") } }
Exchange data with a route
Sometimes you need to not only transfer simple data between components, but also save some page state. For example, we want to save the filter in the online market, and then copy this link and send it to a friend. And we expect him to open the page in the same state as us. The first and perhaps the fastest way to do this is to use query parameters .
The request parameters are more like the lines /people?id= , where id can be anything, and you can have as many parameters as you want. Request parameters will be separated by an ampersand.
When working with query parameters, you do not need to define them in the routes file, and they can be called parameters. For example, take the following code:
page1.component.ts
import {Component} from "@angular/core"; import {Router, NavigationExtras} from "@angular/router"; @Component({ selector: "page1", template: ' <button (click)="onTap()">Navigate to page2</button> ', }) export class Page1Component { public constructor(private router: Router) { } public onTap() { let navigationExtras: NavigationExtras = { queryParams: { "firstname": "Nic", "lastname": "Raboy" } }; this.router.navigate(["page2"], navigationExtras); } }
On the receive page, you will receive the following request parameters:
page2.component.ts
import {Component} from "@angular/core"; import {ActivatedRoute} from "@angular/router"; @Component({ selector: "page2", template: ' <span>{{firstname}}</span> <span>{{lastname}}</span> ', }) export class Page2Component { firstname: string; lastname: string; public constructor(private route: ActivatedRoute) { this.route.queryParams.subscribe(params => { this.firstname = params["firstname"]; this.lastname = params["lastname"]; }); } }
Ngrx
The latter method, which is more complex but more powerful, is to use NgRx . This library is not for data exchange; It is a powerful state management library. I can’t explain in a short example how to use it, but you can go to the official website and read the documentation about it.
For me, the NgRx Store solves a few problems. For example, when you have to deal with observables and when the responsibility for some observable data is distributed between different components, the actions of the repository and reducer ensure that data changes will always be performed in the “right way”.
It also provides a reliable solution for caching HTTP requests. You will be able to store requests and their answers to make sure that your answer has not yet been saved.
You can read about NgRx and see if you need it in your application or not:
Finally, I want to say that before choosing some data exchange methods you need to understand how this data will be used in the future. I mean, maybe now you can only use the @Input decorator to share your username and last name. Then you add a new component or a new module (for example, the admin panel), which needs additional information about the user. This means that this may be the best way to use the service for user data or some other way of exchanging data. You should think about this more before you start exchanging data.