Обмен данными между компонентами Angular

от автора

Проблема

В результате работы с фреймворком Angular, мы декомпозируем наше web-приложение. И по этому у нас возникает ситуация, когда нам нужно передавать данные между компонентами.

@Input()

Что бы передать данные в дочерний компонент, мы можем использовать декоратор @Input(). Он позволит нам передать данные из родительского компонента в дочерний. Рассмотрим простой пример:

import { Input, Component} from '@angular/core';        @Component({     selector: 'app-child',     template: `<h1>Title: {{ title }}</h1>` }) export class ChildComponent {      @Input() title: string; }

В дочернем компоненте мы мы «задекорировали» нужное нам свойство title. Не забываем импортировать декоратор:

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

Осталось только передать параметр title в дочерний компонент из родительского:

import { Component } from '@angular/core';        @Component({     selector: 'app-component',     template: `<app-child [title]="title" [userAge]="age"></app-child>` }) export class AppComponent {      public title = 'Hello world!'; }

Параметры из класса мы передаем с помощью квадратных скобок [title]="title", простую строку мы можем передать и без использования квадратных скобок title="Hello world". Мы научились передавать параметры из родительского в дочерний, но что если нам надо сделать все наоборот?

@Output()

Благодаря директиве @Output() мы можем привязаться к событиям дочернего компонента. На первый взгляд не очень понятно, так что давайте рассмотрим пример:

import { Component } from '@angular/core';         @Component({     selector: 'app-counter',     template: `<h1>Count: {{ count }}</h1>               <app-add (buttonClick)="onAdd()"></app-add>` }) export class AppCounter {      public count = 0;  public onAdd(): void {     this.count++; }  }
import { Component, EventEmitter, Output } from '@angular/core';  @Component({     selector: 'app-add',     template: `<button (click)="add()"></button>`; }) export class AppAdd {      @Output() buttonClick = new EventEmitter();  		public add(): void {     	this.buttonClick.emit(); 		} }

Думаю данный код требует некоторых объяснений. При клике на кнопку в компоненте AppAdd срабатывает событие click, которое вызывает функцию add(). Код this.buttonClick.emit() вызовет событие buttonClick в компоненте AppCounter. Очень важно правильно импортировать EventEmitter:

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

Но есть одно «но», мы не передали никакую информацию в родительский компонент. Рассмотрим уже другой вариант в котором мы будем передавать информацию в родительский компонент:

import { Component } from '@angular/core';  @Component({     selector: 'app-better-counter',     template: `<h1>Count: {{ count }}</h1> 							<app-buttons (buttonClick)="onChange($event)"></app-buttons>` }) export class BetterCounterComponent {      public count = 0;  		public onChange(isAdd: boolean): void {       if (isAdd) {         this.count++;       } else {           this.count--;       } 	 } }
import { Component, EventEmitter, Output } from '@angular/core';  @Component({     selector: 'app-buttons',     template: `<button (click)="change(true)"></button>                 							 <button (click)="change(false)"></button>` }) export class ButtonsComponent {      @Output() buttonClick = new EventEmitter<boolean>();  		public change(change: boolean): void {     	this.buttonClick.emit(change); 		} }

Давайте рассмотрим список внесенных изменений:

  • Добавили тип передаваемых данных new EventEmitter<boolean>()

  • В метод emit передали нужную информацию this.buttonClick.emit(change)

  • Принимаем данные как $event в родительском компоненте (buttonClick)="onChange($event)"

@Input() и @Output() достаточно удобно, но не в ситуации, когда на надо передать данные в дочерний компонент, дочернего компонента и т.д., или же компоненты находятся в разных частях приложения.

Сервисы и RxJs

Одними из лучших вариантов обмена данных остаются сервисы. Создадим простой сервис который бы мог оповещать компоненты про изменение данных, а так же передавать значения:

import { Injectable } from '@angular/core'; import { Subject } from 'rxjs';  @Injectable({   providedIn: 'root', }) export class SimpleService {     public count$ = new Subject<number>();  		public changeCount(count: number) {    		this.count$.next(count);    	} }

Наш сервис готов. В нём мы создадим переменную count$. Знак доллара — это договорённость между программистами в обозначениях потоков. Теперь простыми словами про Subject. Subject — это труба, по которой мы можем передавать данные. Данные получают компоненты, которые оформили подписку на Subject. Давайте посмотрим, как изменять count из компонента:

 import { SimpleService } from './services/simple.service.ts';  @Component({     selector: 'app-any',     template: `` }) export class AnyComponentComponent {      constructor(           private readonly simpleService: SimpleService     ) {}      	public setAnyCount(): void {       this.simpleService.changeCount(Math.random()); 		} }

Мы передали результат Math.random() и пустили его по всем подписчикам. Теперь посмотрим как следить за этими изменениями:

import { Component, OnInit } from '@angular/core'; import { SimpleService } from './services/simple.service.ts';  @Component({     selector: 'app-other',     template: `` }) export class OtherComponentComponent implements OnInit {      constructor(        private readonly simpleService: SimpleService     ) {}        ngOnInit(): void {       this.simpleService.count$.subscribe((count) => this.log(count));     }      private log(data: number): void {   		console.log(data);     }  }

На инициализации мы подписываемся на изменения count, и при каждом вызове count$.next(...) где-либо сработает функция которую мы передали в subscribe. Единственная проблема которая осталась в коде — утечка памяти. При переходе между страницами нашего приложения, компонент будет дестроится, а когда он нам снова понадобится произойдёт повторная инициализация. Старая подписка не пропала, а новые с каждым разом будут только добавляться. Функция log() будет запускаться столько раз, сколько у нас есть подписок. Если бы мы имели там какой-нибудь сложный функционал, то пользовать приложения заметил бы снижение производительности. Этого можно избежать, отписавшись от count$ на OnDestroy. Для этого вынесем подписку в переменную и вызовем у неё метод unsubscribe():

import { Component, OnInit, OnDestroy } from '@angular/core'; import { SimpleService } from './services/simple.service.ts'; import { Subsription } from 'rxjs';  @Component({     selector: 'app-other',     template: `` }) export class OtherComponentComponent implements OnInit, OnDestroy {    	private subs: Subsription;      constructor(       private readonly simpleService: SimpleService     ) {}      ngOnInit(): void {       this.subs = this.simpleService.count$.subscribe((count) => this.log(count));     }      ngOnDestroy(): void {   		this.subs.unsubscribe(); 		}      private log(data: number): void {   		console.log(data);     } }

Мы можем подписаться на множество Subject из компонента, подписаться на один и тот же Subject из разных компонентов.

Итог

Мы можем обмениваться данными между компонентов с помощью @Input(), @Output(), а также RxJs. В данной статье я опустил store, так как статья рассчитана на новичков. Советую попрактиковаться в данной теме, что бы улучшить свои навыки.

ссылка на оригинал статьи https://habr.com/ru/post/554124/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *