Component Interaction

Pass data between HTML and TS

Π”ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ Π΄ΠΈΠ½Π°ΠΌΠΈΠΊΠΈ

Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π΅ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ title

import {Component} from '@angular/core'

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: [
    './card.component.scss'
  ]
})
export class CardComponent {

  title = 'My Card Title'

}

Π§Π΅Ρ€Π΅Π· ΠΈΠ½Ρ‚Π΅Ρ€ΠΏΠΎΠ»ΡΡ†ΠΈΡŽ добавляСм эту ΠΏΠ΅Ρ€Π΅Π½Π½ΡƒΡŽ Π² наш шаблон:

<div class="card">
  <h2>{{ title }}</h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci, ratione.</p>
</div>

Π˜Π½Ρ‚Π΅Ρ€ΠΏΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΌΠΎΠΆΠ½ΠΎ всС, Ρ‡Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ привСсти ΠΊ строкС β€” Π»ΡŽΠ±Ρ‹Π΅ простыС Ρ‚ΠΈΠΏΡ‹. Если ΠΌΡ‹ ΠΏΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΠΈΠ½Ρ‚Π΅Ρ€ΠΏΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚, Ρ‚ΠΎ ΠΌΡ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠΌ [object Object]. Π§Ρ‚ΠΎΠ±Ρ‹ всС Ρ‚Π°ΠΊΠΈ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ содСрТимоС ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚Π°ΠΊΡƒΡŽ ΡˆΡ‚ΡƒΠΊΡƒ ΠΊΠ°ΠΊ ΠΏΠ°ΠΉΠΏΡ‹. НапримСр, ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π½Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Π² json:

<div class="card">
    <h2>{{ someObject | json }}</h2>
</div>

Π§Π΅Ρ€Π΅Π· ΠΈΠ½Ρ‚Π΅Ρ€ΠΏΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π²Ρ‹Π·Ρ‹Π²Π°Ρ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ (ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹Π΅ Π² классС ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°)

Π’ angular ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ символы для интСрполяции:

import {Component} from '@angular/core'

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: [
    './card.component.scss'
  ],
  interpolation: ['{{', '}}']
})
export class CardComponent {}

Bindings

Π­Ρ‚ΠΎ ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ связки ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π° ΠΈ шаблона, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ присутствуСт Π² Angular. ΠŸΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ‚ Π² одностороннСм порядкС ΡΠ²ΡΠ·Ρ‹Π²Π°Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ (измСнилось Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π΅ β€” измСнилось Π²ΠΎ view).

Допустим, ΠΌΡ‹ Ρ…ΠΎΡ‚ΠΈΠΌ ΠΌΠ΅Π½ΡΡ‚ΡŒ динамичСски ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ элСмСнт шаблона (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΡƒ).

import {Component, OnInit} from '@angular/core'

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: [
    './card.component.scss'
  ],
  interpolation: ['{{', '}}']
})
export class CardComponent implements OnInit {
  imgUrl: string = 'https://some.img/img.jpg'
  
  ngOnInit() {
    setTimeout(() => {
      this.imgUrl = 'https://some.img/img2.jpg'
    }, 3000)
  }
  
}

Π’ html:

<div>
    <img src="{{ imgUrl }}">
</div>

Π­Ρ‚ΠΎ сработаСт, Π½ΠΎ Ρ‚Π°ΠΊΠΎΠΉ синтаксис Π½Π΅ совсСм ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π΅Π½. Для этого Π΅ΡΡ‚ΡŒ ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ binding.

ДСлаСтся это Ρ‡Π΅Ρ€Π΅Π· ΠΎΠ±ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° Π² ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚Π½Ρ‹Π΅ скобки:

<div>
    <img [src]="imgUrl">
</div>

Π‘ΠΎΠΎΡ‚Π², Angualr ΠΏΠΎΠ½ΠΈΠΌΠ°Π΅Ρ‚, Ρ‡Ρ‚ΠΎ Π² Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΈ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° Π±ΡƒΠ΄Π΅Ρ‚ ΠΊΠΎΠ΄ TS (ΠΈΠ»ΠΈ JS).

Event binding (Two Way Binding)

ДСлаСтся это Ρ‡Π΅Ρ€Π΅Π· оборачивания события Π² ΠΊΡ€ΡƒΠ³Π»Ρ‹Π΅ скобки ΠΈ прописывания ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°:


<div class="card">
  <h2>{{ title }}</h2>
  <p>{{ testText }}</p>

  <div>
    <button (click)="changeTitle()">Change Title</button>
    <button (click)="title = 'Inline title'">Change Title Inline</button>
  </div>
  
  <div>
    <!-- $event β€” это Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ ΠΈΠ²Π΅Π½Ρ‚, Π½ΡƒΠΆΠ΅Π½ для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ° -->
    <input type="text" (input)="inputHandler($event)" [value]="title">  
    
    <!-- Π”Ρ€ΡƒΠ³ΠΎΠΉ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ (Π±ΠΎΠ»Π΅Π΅ простой) β€” Ρ‡Π΅Ρ€Π΅Π· Π»ΠΎΠΊΠ°Π»ΡŒΠ½ΡƒΡŽ ссылку Π½Π° элСмСнт -->
    <input type="text" #myInput (input)="inputHandler2(myInput.value)" [value]="title">
  </div>
</div>

Π’ angular Π΅ΡΡ‚ΡŒ встроСнный ΠΌΠΎΠ΄ΡƒΠ»ΡŒ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт это Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΅Ρ‰Π΅ ΠΏΡ€ΠΎΡ‰Π΅. Для этого Π² нашСм ΠΌΠΎΠ΄ΡƒΠ»Π΅, Π² ΠΏΠΎΠ»Π΅ imports ΠΌΡ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ Π΅Ρ‰Π΅ ΠΎΠ΄ΠΈΠ½ Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΌΠΎΠ΄ΡƒΠ»ΡŒ β€” FormsModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { CardComponent } from './card/card.component';


@NgModule({
  declarations: [
    AppComponent,
    CardComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

ΠœΠΎΠ΄ΡƒΠ»ΡŒ FormsModule содСрТит Π² сСбС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ, которая позволяСт Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с Ρ„ΠΎΡ€ΠΌΠ°ΠΌΠΈ, ΠΈ содСрТит Π² сСбС ngModel, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ Two Way Binding Π³ΠΎΡ€Π°Π·Π΄ΠΎ ΠΏΡ€ΠΎΡ‰Π΅ (ΠΌΡ‹ просто ΠΎΠ±ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Π΅ΠΌ ΠΈ ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚Π½Ρ‹Π΅ ΠΈ Π² ΠΊΡ€ΡƒΠ³Π»Ρ‹Π΅ скобки ngModel ΠΈ Π² Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎΠΌΠ΅Ρ‰Π°Π΅ΠΌ Ρ‚ΠΎ ΠΏΠΎΠ»Π΅, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Ρ…ΠΎΡ‚ΠΈΠΌ ΡΠ²ΡΠ·Π°Ρ‚ΡŒ Π² двустороннСм порядкС):

<div class="card">
  <h2>{{ title }}</h2>
  <p>{{ testText }}</p>
  
  <div>
    <!-- 2way binding Ρ‡Π΅Ρ€Π΅Π· FormsModule -->
    <input type="text" [(ngModel)]="title">
    <!-- Π’Π°ΠΊ ΠΆΠ΅, Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Π΅ΡΡ‚ΡŒ событиС для подписи Π½Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ поля -->
    <input type="text" [(ngModel)]="title" (ngModelChange)="changeHandler()">
  </div>
</div>

Pass data between parent and child components

Parent -> Child ( @Input() )

ДСлаСтся это ΠΏΡƒΡ‚Π΅ΠΌ объявлСния поля Ρ‡Π΅Ρ€Π΅Π· Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ @Input().

import { Component, OnInit } from '@angular/core';
import { Input } from '@angular/core'

import { Product } from '../products';

export class ChildComponent implements OnInit {

    @Input() product!: Product;
    @Input('master') masterName = ''; // tslint:disable-line: no-input-rename
    
    // Intercept input property changes with a setter
    @Input()
    get name(): string { return this._name; }
    set name(name: string) {
        this._name = (name && name.trim()) || '<no name set>';
    } 
    private _name = '';
    
    constructor() { }
    
    ngOnInit() {
    }

}

Π“Π΄Π΅-Ρ‚ΠΎ Π² объявлСнии ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°:

<app-product-alerts
  [product]="some_product"
  [master]="some_master"
  [name]="some_name">
</app-product-alerts>

Child -> Parent ( @Output() )

ДСлаСтся это ΠΏΡƒΡ‚Π΅ΠΌ объявлСния поля Ρ‡Π΅Ρ€Π΅Π· Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ @Output().

import { Component } from '@angular/core';
import { Input } from '@angular/core';
import { Output, EventEmitter } from '@angular/core';
import { Product } from '../products';


export class ProductAlertsComponent {
  @Input() product: Product | undefined;
  @Output() notify = new EventEmitter<boolean>();
  
  notify_f(value: boolean) {
    this.notify.emit(value);
  }
}

Π“Π΄Π΅-Ρ‚ΠΎ Π² Π΄Ρ€ΡƒΠ³ΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π΅

<button (click)="share()">
  Share
</button>

<app-product-alerts
  [product]="product" 
  (notify)="onNotify($event)">
</app-product-alerts>
export class SomeParentComponent {
    onNotify(value: boolean) {
        // some actions
    }
}

Calls functions to other components

Calls child's functions from the parent component

ДСлаСтся Ρ‡Π΅Ρ€Π΅Π· ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π° β€” #childComponent. ΠŸΡ€ΠΈΠΌΠ΅Ρ€:

<div class="card">
  <h2>{{ title }}</h2>
  <p>{{ testText }}</p>

  <div>
    <button (click)="changeTitle(childComponent.value)">Change Title</button>
  </div>
  
  <div>
    <!-- Π”Ρ€ΡƒΠ³ΠΎΠΉ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ (Π±ΠΎΠ»Π΅Π΅ простой) β€” Ρ‡Π΅Ρ€Π΅Π· Π»ΠΎΠΊΠ°Π»ΡŒΠ½ΡƒΡŽ ссылку Π½Π° элСмСнт -->
    <input type="text" #childComponent (input)="inputHandler2(childComponent.value)" [value]="title">
  </div>
</div>

Π”Ρ€ΡƒΠ³ΠΎΠΉ способ (Π±ΠΎΠ»Π΅Π΅ Π³ΠΈΠ±ΠΊΠΈΠΉ) β€” ΠΈΠ½ΠΆΠ΅ΠΊΡ‚ΠΈΡ‚ΡŒ Π΄ΠΎΡ‡Π΅Ρ€Π½ΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ Π²Π½ΡƒΡ‚Ρ€ΡŒ Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΎΠ³ΠΎ Ρ‡Π΅Ρ€Π΅Π· @ViewChild. ΠŸΡ€ΠΈΠΌΠ΅Ρ€:

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

@Component({
    ...,
    tempate: `
        <div class="seconds">{{ seconds() }}</div>
        <app-child></app-child>
    `,
    ...
})
export class ParentComponent implements AfterViewInit {
    
    @ViewChild(ChildComponent)
    private childComponent!: ChildComponent;
    
    seconds() {return 0;}
    
    ngAfterViewInit() {
        // Redefine `seconds()` to get from the `ChildComponent.seconds` ...
        // but wait a tick first to avoid one-time devMode
        // unidirectional-data-flow-violation error
        // ΠšΡ€Π°Ρ‚ΠΊΠΎ: это Ρ…Π°ΠΊ, Ρ‚ΠΊ Π΄ΠΎΡ‡Π΅Ρ€Π½ΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ Π½Π΅ сущСствуСт, ΠΏΠΎΠΊΠ° Π½Π΅ отрисуСтся Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ,
        // Π° ΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ, Π²Ρ‹Π·ΠΎΠ² Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ΅Π½ ( child.seconds() ).
        // Π­Ρ‚ΠΎΡ‚ ΠΊΠΎΠ΄ доТидаСтся 1 Ρ‚ΠΈΠΊ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ, ΠΏΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ
        setTimeout(() => this.seconds = () => this.childComponent.seconds, 0);
    }
    
    start() { this.childComponent.start(); }
}

Using a service

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ сСрвиса:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable()
export class SomeService {

  // Observable string sources
  private someStringSource = new Subject<string>();
  private someStringSource2 = new Subject<string>();

  // Observable string streams
  someStringStreams$ = this.someStringSource.asObservable()
  someStringStreams2$ = this.someStringSource2.asObservable()

  // Service message commands
  someSend(value: string) {
    this.someStringSource.next(value);
  }

  someRecv(value: string) {
    this.someStringStreams2.next(value);
  }
}

Π ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚

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

import { SomeService } from './some.service';


@Component({
    ...,
    template: `
        <button (click)="someFunc()">Test</button>
        <app-child
          *ngFor="let value of values"
          [someInput]="value">
        </app-child>
    `,
    providers: [SomeService],
    ...
})
export class ParentComponent {
    
    history: string[] = [];    
        
    constructor(private someService: SomeService) {
        someService.someStringStreams$.subscribe(
            someValue => {
                this.history.push(`${someValue}`);
            };
        )
    }
    
    someFunc() {
        this.someService.someSend('some data');
        this.history.push('send some data');
        
    }
}

Π”ΠΎΡ‡Π΅Ρ€Π½ΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚

import { Component, Input, OnDestroy } from '@angular/core';

import { SomeService } from './some.service';
import { Subscription } from 'rxjs';


@Component({
    ...,
    template: `
        <button 
            (click)="confirm()">
            Test
        </button>
    `,
    ...
})
export class ChildComponent implements OnDestroy {
    @Input() someInput = '';
    somedata = '<no data>';
    subscription: Subscription;
    
    constructor(private someService: SomeService) {
        this.subscription = someService.someStringStreams2$.subscribe(
            somedata => {
                this.somedata = somedata;
            }
        )
    }
    
    confirm() {
        this.someService.someRecv(this.someInput);
    }
    
    ngOnDestroy() {
        // prevent memory leak when component destroyed
        this.subscription.unsubscribe();
    }
}

ΠžΡ‚ΠΏΠΈΡΠΊΠ° ΠΎΡ‚ событий Π² ngOnDestroy() ΠΊΡ€Π°ΠΉΠ½Π΅ Π²Π°ΠΆΠ½Π° для Π΄ΠΎΡ‡Π΅Ρ€Π½ΠΈΡ… элСмСнтов, Ρ‚ΠΊ это Π·Π°Ρ‰ΠΈΡ‰Π°Π΅Ρ‚ ΠΎΡ‚ ΡƒΡ‚Π΅Ρ‡Π΅ΠΊ памяти (для Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΈΡ… ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² этого Π΄Π΅Π»Π°Ρ‚ΡŒ Π½Π΅ Π½Π°Π΄ΠΎ, Ρ‚ΠΊ сСрвис SomeService закрываСтся вмСстС с Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΈΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠΌ).

Last updated