Angular Components - Communication Between Components

Jun 30 2022

5 min read

#Passing Data Between Components.

There are different ways to pass data in and out of components. It can be a simple property or method being passed from parent to children. Or, it could be any complex object from one parent component to a deeply nested child component. They approach differs on the complexity of the application and the relationship between the components.


#Passing Data from Parent to Child

#Using @Input decorator

When the data is passed from a parent to child component, we use @Input() decorator. In the parent component, we specify the variable name which holds the data that is being passed and bind it to the child components view inside [] brackets.

In the child component, we declare the same variable name by decorating it with @Input() so that Angular will know the value will be sent from a parent component.

parent.component.ts
child.component.ts
Copy

@Component({
selector: 'app-parent',
# mark
template: `<app-child [msgFromParent]='messageToChild'></app-child>`,
styleUrls: ['./parent.component.scss']
})
export class ParentComponent {
messageToChild = "Stop playing video games."
constructor() {}
}

This is a one-way relationship: parent to child. Only parent can send the data to child.


#Passing Data from Child to Parent

There are different ways that a child component can pass data to the parent. The two most commonly used patterns are:

  • @ViewChild() decorator in parent component
  • @Output() decorator and EventEmitter class in child component

#Using @ViewChild Decorator

This decorator is used in parent component to create a reference to it's child component. By doing this, we will get access to all the public properties of the child component.

parent.component.ts
child.component.ts
child.component.html
Copy

export class ParentComponent implements AfterViewInit {
@ViewChild(ChildComponent) childComponent;
message: string;
constructor() {}
ngAfterViewInit() {
this.message = this.childComponent.message;
}
}

Once the child component properties are set, we can use ngAfterViewInit lifecycle hook in parent to read the child components properties and update in the template.

#Using @Output Decorator and EventEmitter Class

This decorator is used in child component alongside with EventEmitter class. This approach involves defining events in child components and the parent component can listen to these events.

parent.component.ts
child.component.ts
Copy

@Component({
selector: 'app-parent',
template: `
<p>Message: {{ message }}</p>
<app-child
(messageEvent)='receiveMessageFromChild($event)'
></app-child>
`,
styleUrls: ['./parent.component.scss']
})
export class ParentComponent {
message: string;
receiveMessageFromChild($event: string) {
this.message = $event
}
}

This approach is ideal when child component needs to let the parent component know of events happening inside it like button clicks etc.

In the first approach, we can clearly see there is a tight cohesion between the parent and child component because of the reference it holds. Meanwhile, in the second approach, there is no such cohesion as the communication is happening through events. Any parent component can register itself to listen to these events. It's up to them. This leads to better separation of concerns.


#Passing Data Between Unrelated Components

In a complex application with many number of components, there will be a scenario where you set some data in one component and read that same data in another component. These components need not to have a parent/child relationship. They could be unrelated components with no common parent.

#Using Shared Service

Passing data between unrelated components can be done using a shared service. They contain all the data objects which are read and updated by any component. We will define getters and setters to read and update the shared data values.

The below service is a typescript class which has a single private property which will be shared across all the components. The important part is @Injectable decorator. This tells Angular that this class can be injected into other components.

data.service.ts
component-one.component.ts
Copy

@Injectable()
export class DataService {
private message: string;
constructor() {}
setMessage(message: string) {
this.message = message;
}
getMessage() {
return this.message
}
}

To use the service, we inject them into the required component's constructor. This is commonly known as Dependency Injection. It's a pattern where the objects are injected into the classes. Once the objects are injected, we can use ngOnInit() lifecycle hook to read the service class data objects and set it to the components variables.

Angular will be using the Singleton design pattern for all the injectable services to make sure that only one instance of the class is available, and the data is reflected in all the components if it is changed in any one of the component.


@Component({
selector: 'app-component-two',
template: `
{{ message }}
<button (click)='updateMessage()'>update</button>
`,
styleUrls: ['./component-one.scss']
})
export class ComponentTwo implements OnInit() {
message: string;
constructor(private dataService: DataService) {}
ngOnInit() {
this.message = this.dataService.getMessage()
}
updateMessage() {
this.dataService.setMessage("shared data message is updated")
}
}

In this component we are injecting the data service and updating the message on a button click. The data will be updated in the service class and also in all the other components where this data is being read.