Angular — исключение при добавлении динамического компонента

У меня есть простой тестовый код для добавления динамических компонентов с помощью Angular 4.

@Component({
  selector: 'component',
  template: `

    <ul><li #item *ngFor="let number of list">{{number}}</li></ul>

    <ng-template #anchor> </ng-template>


    <ng-template #template>
      <li><input type="text" [(ngModel)]="myInput"/></li>
    </ng-template>`
})
class _Component {

  @ViewChild('template')
  template: TemplateRef<any>


  @ViewChild('anchor', { read: ViewContainerRef })
  anchor: TemplateRef<any>


  @ViewChildren('item', { read: ViewContainerRef })
  items: QueryList<ViewContainerRef>
  myInput='';
  list: number[] = [0, 1, 2, 3, 4]

  ngAfterViewInit() {
    this.anchor.createEmbeddedView(this.template)
  }

}

Все, что делает этот код, это добавляет фиктивный шаблон в конце.

введите здесь описание изображения

Но этот код выдает исключение:

ExpressionChangedAfterItHasBeenCheckedError: выражение изменилось после проверки. Предыдущее значение: «не определено». Текущая стоимость: ''. Похоже, что представление было создано после того, как его родитель и его дочерние элементы были проверены на наличие загрязнений. Был ли он создан в хуке обнаружения изменений?

Это хорошо описательное исключение. Представление было обновлено после того, как уже проверено на изменение.

Но есть несколько вещей, которые я не понимаю здесь:

Вопрос(ы):

1:

Если я уберу input из шаблона - теперь шаблон будет:

<ng-template #template>
    <li></li>
</ng-template>

— Тогда я не получаю исключения. Это почему ?

2:

Другое решение (среди многих) — заменить ngAfterViewInit на ngAfterContentInit. Я уже знаю разницу между этими двумя.

Если так - могу ли я заключить (из того, что исключение пропало), что при каждом событии Angualr - происходит обнаружение изменений? (что имеет смысл, потому что ngAfterViewInit происходит _после_ ngAfterContentInit), поэтому, возможно, Angular обнаружил предыдущее динамическое изменение в ngAfterViewInit ? Я прав?

PLNKR


person Royi Namir    schedule 29.05.2017    source источник
comment
почему вы вставляете в <ng-template #anchor? Вы намеревались использовать <ng-container?   -  person Max Koretskyi    schedule 29.05.2017
comment
@Maximus Неважно (ИМХО). Angular добавляет динамический контент КАК SIBLING. так что это действительно не имеет значения.   -  person Royi Namir    schedule 29.05.2017
comment
Я рекомендую вам прочитать все, что вам нужно знать об обнаружении изменений в Angular, а также это   -  person Max Koretskyi    schedule 29.05.2017


Ответы (1)


Если я удалю ввод из шаблона - теперь шаблон:

Вход не вызывает проблемы. ngModel вызывает проблему. Экземпляры директив и компонентов представлены в виде узлов представления внутри angular — своего рода дочерние элементы текущего компонента. Во время каждого цикла обнаружения изменений Angular обновляет входные данные для этих экземпляров компонента/директивы. Вот выдержка из Все, что вам нужно знать об обнаружении изменений в Angular, которое показывает порядок операций:

  • обновляет свойства input в экземплярах дочернего компонента/директивы (1)
  • вызывает перехватчики жизненного цикла AfterContentInit и AfterContentChecked для дочерних экземпляров компонента/директивы (5)
  • запускает обнаружение изменений для дочернего представления (повторяет шаги в этом списке) (8)
  • вызывает перехватчики жизненного цикла AfterViewInit и AfterViewChecked для экземпляров дочерних компонентов/директив (9)

Итак, предположим, что Angular выполняет обнаружение изменений для AppComponent. Он запускает обнаружение изменений для _Component (6). Директив пока нет, так что нечего проверять. Затем Angular вызывает хук afterViewInit для _Component, где вы создаете экземпляр дочерней директивы ngModel. Но обнаружение изменений для директивы ngModel никогда не срабатывает! После завершения текущего цикла обнаружения изменений Angular проверяет наличие изменений и обнаруживает, что @Input из ngModel является пустой строкой, но предыдущие значения равны undefined, так как они никогда не проверялись.

Сравните это со случаем, когда вы использовали хук AfterContentInit. Angular вызывает этот хук для _Component. Вы создаете дочернюю директиву там. Обнаружение изменений Angular Runs для файла _Component. Теперь директива уже существует, поэтому обнаружение изменений также выполняется для этой дочерней директивы как часть обнаружения изменений для файла _Component. Поскольку Angular применил начальное значение пустой строки, при следующей проверке директивы не возникнет ошибки.

person Max Koretskyi    schedule 29.05.2017
comment
Максимус - спасибо за статью. Таким образом, Angular не запускает обнаружение изменений при каждом методе события — только на этапах 3,7,8? (Другими словами, в каких угловых событиях происходит обнаружение изменений?) - person Royi Namir; 29.05.2017
comment
вам, вероятно, придется прочитать всю статью. все операции, перечисленные в статье, являются частью обнаружения изменений для конкретного представления/компонента. Вы можете иметь в виду что-то другое, когда говорите change detection, что вы имеете в виду под change detection? - person Max Koretskyi; 29.05.2017
comment
Забудь это :-). Надо будет прочитать и если будет вопрос - пингую. (Кстати, я упомянул, что знаю разницу в порядке событий - в моем вопросе. Я не знал, меняют ли триггеры angular обнаружение при каждом событии. - person Royi Namir; 29.05.2017
comment
@Royi, да, прочитайте статью и следите за новостями :). Это довольно подробно, я потратил значительное количество времени, чтобы исследовать и написать это. Кстати, заканчиваем очередную статью о динамических компонентах. Подпишитесь на меня на Medium, чтобы получать уведомления :) - person Max Koretskyi; 29.05.2017
comment
Конечно. :-). Спасибо. - person Royi Namir; 29.05.2017
comment
Что касается вашего ответа о ngModel: почему вы говорите, что он обновляет parent до пустой строки? . Вход не находится внутри подкомпонента. Считается ли вход дочерним элементом компонента? - person Royi Namir; 29.05.2017
comment
@Royi, я обновил ответ, теперь посмотрим, имеет ли смысл - person Max Koretskyi; 29.05.2017
comment
КСТАТИ - цифры в скобках не соответствуют тем, что в статье. (просто говорю - для будущих читателей). - person Royi Namir; 30.05.2017
comment
Хм, я пытался создать встроенное представление в ngAfterContentInit(), но все еще получаю исключение (см. здесь). Интересно, что в демоверсии, о которой я говорю, нет такой проблемы, как у меня. - person Stefan Falk; 27.04.2018