Component Communication in Angular

In Angular, components are the building blocks of an application. As the complexity of an application grows, the need for components to communicate with each other arises. In this tutorial, we will explore the different ways components can share data and interact with each other.

Parent-Child Communication

Parent-child communication is one of the most common types of component interactions. A parent component can pass data to its child component using the @Input() decorator. The child component can then receive this data and use it as needed.

Sharing Data via Input

To share data from a parent component to a child component, you can use the @Input() decorator in the child component. Here’s an example:

// parent.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'parent-component',
  template: `
    <child-component [childProperty]="parentProperty"></child-component>
  `,
})
export class ParentComponent {
  parentProperty = "I come from parent";
}

// child.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'child-component',
  template: `
    Hi {{ childProperty }}
  `,
})
export class ChildComponent {
  @Input() childProperty: string;
}

Sharing Data via ViewChild

To access a child component’s properties or methods from a parent component, you can use the @ViewChild() decorator. Here’s an example:

// parent.component.ts
import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'parent-component',
  template: `
    <child-component #child></child-component>
  `,
})
export class ParentComponent {
  @ViewChild('child') child: ChildComponent;

  ngAfterViewInit() {
    console.log(this.child.childProperty);
  }
}

// child.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'child-component',
  template: `
    Hi {{ childProperty }}
  `,
})
export class ChildComponent {
  childProperty = "I come from child";
}

Sibling Communication

Sibling components are components that share the same parent component. To communicate between sibling components, you can use a shared service or pass data through their common parent component.

Sharing Data with a Service

To share data between sibling components, you can create a shared service that holds the data and inject it into both components. Here’s an example:

// 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();

  changeMessage(message: string) {
    this.messageSource.next(message);
  }
}

// first.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'first-component',
  template: `
    {{ message }}
  `,
})
export class FirstComponent implements OnInit {
  message: string;

  constructor(private data: DataService) {}

  ngOnInit() {
    this.data.currentMessage.subscribe((message) => (this.message = message));
  }
}

// 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>
  `,
})
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');
  }
}

Unrelated Components

Unrelated components are components that do not share a common parent component. To communicate between unrelated components, you can use a shared service or pass data through route parameters.

Sharing Data with a Route

To share data between unrelated components using route parameters, you can use the Router service to navigate to a new route and pass data as query parameters. Here’s an example:

// 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 {
  constructor(private router: Router) {}

  onTap() {
    let navigationExtras: NavigationExtras = {
      queryParams: {
        firstname: 'Nic',
        lastname: 'Raboy',
      },
    };
    this.router.navigate(['page2'], navigationExtras);
  }
}

// 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;

  constructor(private route: ActivatedRoute) {
    this.route.queryParams.subscribe((params) => {
      this.firstname = params['firstname'];
      this.lastname = params['lastname'];
    });
  }
}

NgRx

NgRx is a state management library for Angular that provides a powerful way to manage global state and side effects. While not specifically designed for component communication, NgRx can be used to share data between components by storing it in the application’s global state.

To use NgRx, you need to install the @ngrx/store package and create a store module that defines the application’s state and actions. Here’s an example:

// app.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { reducers } from './reducers';

@NgModule({
  declarations: [AppComponent],
  imports: [StoreModule.forRoot(reducers)],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

// reducers.ts
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import { createAction, createReducer, on } from '@ngrx/store';

export const addMessage = createAction(
  '[Messages] Add Message',
  (message: string) => ({ message })
);

const adapter: EntityAdapter<string> = createEntityAdapter<string>();

const initialState: EntityState<string> = adapter.getInitialState();

const messagesReducer = createReducer(
  initialState,
  on(addMessage, (state, { message }) => adapter.addOne(message, state))
);

export function reducer(state = initialState, action) {
  return messagesReducer(state, action);
}

In this example, we define a store module that imports the StoreModule and defines a reducer for managing messages. We then create an action to add a new message to the store.

To use the store in a component, you can inject the Store service and dispatch actions or select data from the store. Here’s an example:

// messages.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { addMessage } from './actions';

@Component({
  selector: 'messages',
  template: `
    <ul>
      <li *ngFor="let message of messages">{{ message }}</li>
    </ul>
    <input [(ngModel)]="newMessage" />
    <button (click)="addNewMessage()">Add Message</button>
  `,
})
export class MessagesComponent {
  newMessage = '';
  messages = [];

  constructor(private store: Store<any>) {
    this.store.select('messages').subscribe((messages) => {
      this.messages = messages;
    });
  }

  addNewMessage() {
    this.store.dispatch(addMessage({ message: this.newMessage }));
    this.newMessage = '';
  }
}

In this example, we inject the Store service and select the messages from the store. We then dispatch an action to add a new message to the store when the user clicks the "Add Message" button.

Conclusion

Component communication is a crucial aspect of building complex Angular applications. By using the different methods outlined in this tutorial, you can share data between components and create a robust and maintainable application architecture. Whether you’re using parent-child communication, sibling communication, or unrelated component communication, understanding how to share data between components is essential for building scalable and efficient Angular applications.

Leave a Reply

Your email address will not be published. Required fields are marked *