В Angular компоненты являются основными строительными блоками приложения. Каждый компонент управляет частью пользовательского интерфейса и содержит логику для обработки взаимодействий пользователя. Однако в реальных приложениях часто возникает необходимость организовать взаимодействие между несколькими компонентами. Это взаимодействие можно осуществлять различными способами, в зависимости от структуры приложения и нужд бизнес-логики.
Angular предоставляет механизм для обмена данными между компонентами с помощью input и output свойств.
@Input() и @Output() — это декораторы, которые позволяют компонентам передавать данные друг другу.
Рассмотрим пример, где родительский компонент передает данные в дочерний компонент и получает ответ.
// Родительский компонент
@Component({
selector: 'app-parent',
template: `
<app-child [data]="parentData" (notify)="onNotify($event)"></app-child>
`
})
export class ParentComponent {
parentData = 'Hello from Parent!';
onNotify(message: string) {
console.log(message);
}
}
// Дочерний компонент
@Component({
selector: 'app-child',
template: `
<div>{{ data }}</div>
<button (click)="sendNotification()">Notify Parent</button>
`
})
export class ChildComponent {
@Input() data: string;
@Output() notify = new EventEmitter<string>();
sendNotification() {
this.notify.emit('Hello from Child!');
}
}
В этом примере родитель передает строку в дочерний компонент через свойство data, а дочерний компонент, в свою очередь, отправляет сообщение обратно через событие notify.
Иногда взаимодействие между компонентами не ограничивается простым обменом значениями через input и output. В более сложных приложениях может потребоваться общая логика или данные, которые должны быть доступны нескольким компонентам. Для этих целей в Angular используются сервисы.
Сервис в Angular представляет собой класс, который инкапсулирует логику, не привязанную к UI. С помощью сервисов можно обмениваться данными между любыми компонентами, независимо от их иерархического положения.
Предположим, что два компонента, которые не имеют прямой связи, должны обмениваться данными. Для этого создается сервис, который будет хранить и передавать эти данные.
// Сервис
@Injectable({
providedIn: 'root',
})
export class DataService {
private dataSource = new BehaviorSubject<string>('Initial Data');
currentData = this.dataSource.asObservable();
changeData(data: string) {
this.dataSource.next(data);
}
}
// Родительский компонент
@Component({
selector: 'app-parent',
template: `
<div>{{ data }}</div>
<button (click)="changeData()">Change Data</button>
`
})
export class ParentComponent {
data: string;
constructor(private dataService: DataService) {
this.dataService.currentData.subscribe(data => this.data = data);
}
changeData() {
this.dataService.changeData('Data updated from Parent');
}
}
// Дочерний компонент
@Component({
selector: 'app-child',
template: `
<div>{{ data }}</div>
<button (click)="changeData()">Change Data</button>
`
})
export class ChildComponent {
data: string;
constructor(private dataService: DataService) {
this.dataService.currentData.subscribe(data => this.data = data);
}
changeData() {
this.dataService.changeData('Data updated from Child');
}
}
В этом примере сервис DataService управляет состоянием данных, которые могут изменяться как в родительском, так и в дочернем компонентах. Каждый компонент подписывается на обновления данных, а изменения происходят через методы сервиса.
Для более сложных приложений, где необходимо управлять состоянием приложения на глобальном уровне, используется NgRx — библиотека для управления состоянием, основанная на Redux. NgRx предоставляет единую точку для хранения состояния, что облегчает обмен данными между компонентами и управление состоянием приложения.
NgRx использует концепции действий (actions), редукторов (reducers) и эффектов (effects) для организации потока данных и взаимодействия компонентов.
export const loadData = createAction('[Data] Load Data');
export const loadDataSuccess = createAction(
'[Data] Load Data Success',
props<{ data: string }>()
);
export interface DataState {
data: string;
}
export const initialState: DataState = {
data: 'Initial data'
};
export const dataReducer = createReducer(
initialState,
on(loadDataSuccess, (state, { data }) => ({ ...state, data }))
);
@Component({
selector: 'app-data',
template: `
<div>{{ data }}</div>
<button (click)="loadData()">Load Data</button>
`
})
export class DataComponent {
data: string;
constructor(private store: Store<{ data: DataState }>) {
this.store.select('data').subscribe(data => {
this.data = data.data;
});
}
loadData() {
this.store.dispatch(loadDataSuccess({ data: 'New Data from Store' }));
}
}
В этом примере состояние данных управляется через NgRx, что позволяет централизованно управлять состоянием и взаимодействовать между компонентами без необходимости использования сервисов или передачи данных через input/output.
Angular предоставляет возможность управлять данными на уровне шаблонов с использованием локальных переменных. Это упрощает работу с динамическими данными внутри одного компонента, не требуя дополнительных классов и сервисов.
<input #inputRef type="text" (input)="onInputChange(inputRef.value)">
<p>{{ inputRef.value }}</p>
В данном примере локальная переменная inputRef хранит ссылку на DOM-элемент, что позволяет обращаться к значению напрямую в шаблоне. Это особенно полезно, если нужно получить доступ к элементу в рамках текущего компонента.
Взаимодействие между компонентами часто происходит в контексте асинхронных данных, таких как данные из API или веб-сокетов. В Angular для работы с асинхронными операциями используется сервис HttpClient и механизмы Observables.
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) {}
fetchData() {
return this.http.get<string>('https://api.example.com/data');
}
}
@Component({
selector: 'app-data',
template: `
<div *ngIf="data">{{ data }}</div>
<button (click)="loadData()">Load Data</button>
`
})
export class DataComponent {
data: string;
constructor(private dataService: DataService) {}
loadData() {
this.dataService.fetchData().subscribe(data => {
this.data = data;
});
}
}
В этом примере компонент получает данные через HttpClient и отображает их при успешном ответе от сервера. Использование Observables позволяет эффективно управлять асинхронными операциями и взаимодействием с компонентами.
Взаимодействие между компонентами — это важный аспект разработки на Angular. Использование input/output, сервисов, NgRx и других механизмов позволяет гибко и эффективно организовывать обмен данными между компонентами, создавая масштабируемые и поддерживаемые приложения.