Поиск…


Вступление

Разделяйте информацию между различными директивами и компонентами.

Передавать данные от родителя к дочернему с привязкой ввода

HeroChildComponent имеет два входных свойства, обычно украшенных украшениями @Input.

import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
  selector: 'hero-child',
  template: `
    <h3>{{hero.name}} says:</h3>
    <p>I, {{hero.name}}, am at your service, {{masterName}}.</p>
  `
})
export class HeroChildComponent {
  @Input() hero: Hero;
  @Input('master') masterName: string;
}

Изменяется входное свойство перехвата с помощью сеттера

Используйте средство ввода свойств входа для перехвата и действия над значением родителя.

Установитель свойства ввода имени в дочернем NameChildComponent обрезает пробел от имени и заменяет пустое значение текстом по умолчанию.

import { Component, Input } from '@angular/core';
@Component({
  selector: 'name-child',
  template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
  private _name = '';
  @Input()
  set name(name: string) {
    this._name = (name && name.trim()) || '<no name set>';
  }
  get name(): string { return this._name; }
}

Здесь NameParentComponent демонстрирует варианты имен, включая имя со всеми пробелами:

import { Component } from '@angular/core';
@Component({
  selector: 'name-parent',
  template: `
  <h2>Master controls {{names.length}} names</h2>
  <name-child *ngFor="let name of names" [name]="name"></name-child>
  `
})
export class NameParentComponent {
  // Displays 'Mr. IQ', '<no name set>', 'Bombasto'
  names = ['Mr. IQ', '   ', '  Bombasto  '];
}

Родитель слушает дочернее событие

Детский компонент предоставляет свойство EventEmitter, с помощью которого он генерирует события, когда что-то происходит. Родитель связывается с этим свойством события и реагирует на эти события.

Свойство EventEmitter для ребенка является выходным свойством, обычно украшенным украшением @Output, как показано в этом элементе VoterComponent:

import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
  selector: 'my-voter',
  template: `
    <h4>{{name}}</h4>
    <button (click)="vote(true)"  [disabled]="voted">Agree</button>
    <button (click)="vote(false)" [disabled]="voted">Disagree</button>
  `
})
export class VoterComponent {
  @Input()  name: string;
  @Output() onVoted = new EventEmitter<boolean>();
  voted = false;
  vote(agreed: boolean) {
    this.onVoted.emit(agreed);
    this.voted = true;
  }
}

Нажатие кнопки вызывает эмиссию истинного или ложного (булева полезная нагрузка).

Родительский VoteTakerComponent связывает обработчик события (onVoted), который отвечает на полезную нагрузку дочернего события ($ event) и обновляет счетчик.

import { Component }      from '@angular/core';
@Component({
  selector: 'vote-taker',
  template: `
    <h2>Should mankind colonize the Universe?</h2>
    <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
    <my-voter *ngFor="let voter of voters"
      [name]="voter"
      (onVoted)="onVoted($event)">
    </my-voter>
  `
})
export class VoteTakerComponent {
  agreed = 0;
  disagreed = 0;
  voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
  onVoted(agreed: boolean) {
    agreed ? this.agreed++ : this.disagreed++;
  }
}

Родитель взаимодействует с дочерним элементом через локальную переменную

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

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

import { Component, OnDestroy, OnInit } from '@angular/core';
@Component({
  selector: 'countdown-timer',
  template: '<p>{{message}}</p>'
})
export class CountdownTimerComponent implements OnInit, OnDestroy {
  intervalId = 0;
  message = '';
  seconds = 11;
  clearTimer() { clearInterval(this.intervalId); }
  ngOnInit()    { this.start(); }
  ngOnDestroy() { this.clearTimer(); }
  start() { this.countDown(); }
  stop()  {
    this.clearTimer();
    this.message = `Holding at T-${this.seconds} seconds`;
  }
  private countDown() {
    this.clearTimer();
    this.intervalId = window.setInterval(() => {
      this.seconds -= 1;
      if (this.seconds === 0) {
        this.message = 'Blast off!';
      } else {
        if (this.seconds < 0) { this.seconds = 10; } // reset
        this.message = `T-${this.seconds} seconds and counting`;
      }
    }, 1000);
  }
}

Давайте посмотрим на CountdownLocalVarParentComponent, на котором размещен компонент таймера.

import { Component }                from '@angular/core';
import { CountdownTimerComponent }  from './countdown-timer.component';
@Component({
  selector: 'countdown-parent-lv',
  template: `
  <h3>Countdown to Liftoff (via local variable)</h3>
  <button (click)="timer.start()">Start</button>
  <button (click)="timer.stop()">Stop</button>
  <div class="seconds">{{timer.seconds}}</div>
  <countdown-timer #timer></countdown-timer>
  `,
  styleUrls: ['demo.css']
})
export class CountdownLocalVarParentComponent { }

Родительский компонент не может привязывать данные к методам запуска и остановки дочернего элемента, а также к свойству секунд.

Мы можем поместить локальную переменную (#timer) в тег (), представляющий дочерний компонент. Это дает нам ссылку на сам дочерний компонент и возможность доступа к любым его свойствам или методам из родительского шаблона.

В этом примере мы создаем родительские кнопки для запуска и остановки ребенка и используем интерполяцию для отображения свойства секунд дочернего элемента.

Здесь мы видим, как родители и дети работают вместе.

Родитель вызывает ViewChild

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

Мы не можем использовать метод локальной переменной, если экземпляр родительского компонента должен читать или записывать значения дочерних компонентов или должен вызывать дочерние компоненты.

Когда класс родительского компонента требует такого доступа, мы добавляем дочерний компонент в родительский элемент как ViewChild.

Мы проиллюстрируем эту технику тем же самым примером таймера обратного отсчета. Мы не изменим его внешний вид или поведение. Ребенок CountdownTimerComponent тоже такой же.

Мы переходим от локальной переменной к методу ViewChild исключительно для демонстрации. Вот родительский элемент CountdownViewChildParentComponent:

import { AfterViewInit, ViewChild } from '@angular/core';
import { Component }                from '@angular/core';
import { CountdownTimerComponent }  from './countdown-timer.component';
@Component({
  selector: 'countdown-parent-vc',
  template: `
  <h3>Countdown to Liftoff (via ViewChild)</h3>
  <button (click)="start()">Start</button>
  <button (click)="stop()">Stop</button>
  <div class="seconds">{{ seconds() }}</div>
  <countdown-timer></countdown-timer>
  `,
  styleUrls: ['demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {
  @ViewChild(CountdownTimerComponent)
  private timerComponent: CountdownTimerComponent;
  seconds() { return 0; }
  ngAfterViewInit() {
    // Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
    // but wait a tick first to avoid one-time devMode
    // unidirectional-data-flow-violation error
    setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
  }
  start() { this.timerComponent.start(); }
  stop() { this.timerComponent.stop(); }
}

Требуется немного больше работы, чтобы получить представление child в классе родительских компонентов.

Мы импортируем ссылки на декоратор ViewChild и крючок жизненного цикла AfterViewInit.

Мы добавляем дочерний объект CountdownTimerComponent в свойство private timerComponent через свойство свойства @ViewChild.

Локальная переменная #timer удалена из метаданных компонента. Вместо этого мы привязываем кнопки к собственным методам запуска и остановки родительского компонента и представляем тикающие секунды в интерполяции вокруг метода секунд родительского компонента.

Эти методы напрямую обращаются к инжекционному компоненту таймера.

Ключ жизненного цикла ngAfterViewInit является важной морщиной. Компонент таймера недоступен до тех пор, пока Angular не отобразит родительский вид. Таким образом, мы отображаем сначала 0 секунд.

Затем «Угловой» вызывает крючок жизненного цикла ngAfterViewInit, и в это время слишком поздно обновлять отображение родительского представления секунд обратного отсчета. Правило однонаправленного потока данных Angular не позволяет нам обновлять родительское представление в том же цикле. Нам нужно подождать один оборот, прежде чем мы сможем отобразить секунды.

Мы используем setTimeout, чтобы подождать один тик, а затем пересмотреть метод секунд, чтобы он учитывал будущие значения из компонента таймера.

Родитель и дети общаются через службу

Родительский компонент и его дети совместно используют службу, интерфейс которой обеспечивает двунаправленную связь внутри семьи.

Объем экземпляра службы - это родительский компонент и его дочерние элементы. Компоненты вне этого поддерева компонентов не имеют доступа к службе или их связи.

Этот MissionService соединяет MissionControlComponent с несколькими детьми AstronautComponent.

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';
@Injectable()
export class MissionService {
  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();
  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();
  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }
  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}

MissionControlComponent предоставляет экземпляр службы, которую он разделяет со своими дочерними элементами (через массив метаданных поставщиков) и внедряет этот экземпляр в себя через свой конструктор:

import { Component }          from '@angular/core';
import { MissionService }     from './mission.service';
@Component({
  selector: 'mission-control',
  template: `
    <h2>Mission Control</h2>
    <button (click)="announce()">Announce mission</button>
    <my-astronaut *ngFor="let astronaut of astronauts"
      [astronaut]="astronaut">
    </my-astronaut>
    <h3>History</h3>
    <ul>
      <li *ngFor="let event of history">{{event}}</li>
    </ul>
  `,
  providers: [MissionService]
})
export class MissionControlComponent {
  astronauts = ['Lovell', 'Swigert', 'Haise'];
  history: string[] = [];
  missions = ['Fly to the moon!',
              'Fly to mars!',
              'Fly to Vegas!'];
  nextMission = 0;
  constructor(private missionService: MissionService) {
    missionService.missionConfirmed$.subscribe(
      astronaut => {
        this.history.push(`${astronaut} confirmed the mission`);
      });
  }
  announce() {
    let mission = this.missions[this.nextMission++];
    this.missionService.announceMission(mission);
    this.history.push(`Mission "${mission}" announced`);
    if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
  }
}

AstronautComponent также вводит службу в свой конструктор. Каждый AstronautComponent является дочерним элементом MissionControlComponent и поэтому получает экземпляр службы родителя:

import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';
@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;
  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }
  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }
  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

Обратите внимание, что мы фиксируем подписку и отписываем подписку при уничтожении AstronautComponent. Это шаг защиты от утечки памяти. В этом приложении нет реального риска, так как время жизни AstronautComponent совпадает с временем жизни самого приложения. Это не всегда было бы правдой в более сложном приложении.

Мы не добавляем этот сторож к MissionControlComponent, потому что, как родительский, он контролирует время жизни MissionService. Журнал истории показывает, что сообщения перемещаются в обоих направлениях между родительским MissionControlComponent и детьми AstronautComponent, чему способствует услуга:



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow