ViewChild возвращает «неопределенное» - Angular2

Я пытаюсь выполнить функцию дочернего компонента нажатием кнопки на родительском, но по какой-то причине он не определен.

Родитель:

.. com1.html
  <ng-template #content let-c="close" let-d="dismiss">
    <div class="modal-header">
      <h4 class="modal-title">Title</h4>
    </div>
    <div class="modal-body" style="display: flex; justify-content: center;">
      <app-com2 [var1]="value" #traceRef></app-com2>
    </div>
    <div class="modal-footer">
      <button type="button" class="btn btn-outline-dark" (click)="savePNG()"><i class="fa fa-download"></i>PNG</button>
      <button type="button" class="btn btn-outline-dark" (click)="c()">Close</button>
    </div>
  </ng-template>

<button class="btn btn-sm btn-secondary" (click)="open(content)">Button</button>

...com1.ts
export class com1 implements OnInit {
@ViewChild('traceRef') traceRef;
value = "something";

  constructor(private modalService: NgbModal) {}

  savePNG() {
    this.traceRef.savePNG();
  }

  open(content: any) {
    this.modalService.open(content, { windowClass: 'temp-modal' });
  }
}

Может ли кто-нибудь указать мне правильное направление, потому что другие темы не помогли :( Даже с ngAfterViewInit он все еще не определен.


person merstik    schedule 15.05.2018    source источник
comment
Будет ли это работать, если вы попытаетесь получить доступ к компоненту напрямую без templateRef? Например, @ViewChild(AppComComponent) traceRef;   -  person Franklin Pious    schedule 15.05.2018
comment
Что не определено? this.traceRef или savePNG?   -  person Powkachu    schedule 15.05.2018
comment
this.traceRef не определен   -  person merstik    schedule 15.05.2018
comment
Вы сказали даже с ngAfterInit, но его нужно использовать в ngAfterViewInit. Переменная шаблона не будет доступна для вашего компонента до тех пор, пока представление не будет инициализировано в этом хуке жизненного цикла. Документы: angular.io/api/core/ViewChild   -  person rmcsharry    schedule 15.05.2018
comment
Пожалуйста, опубликуйте весь свой шаблон и класс, чтобы ничего не пропустить :)   -  person Orodan    schedule 15.05.2018
comment
Извините, я упоминаю ngAfterViewInit   -  person merstik    schedule 15.05.2018
comment
Можем ли мы увидеть сообщения об ошибках, которые вы получаете в консоли?   -  person ConnorsFan    schedule 15.05.2018
comment
Я обновил более актуальный код @Orodan   -  person merstik    schedule 15.05.2018


Ответы (3)


Причина, по которой он не работает, как это было бы в обычном сценарии с ng-template, заключается в том, что служба NgbModal создает шаблон и не прикрепляет его к родительскому представлению, а скорее к корневому ApplicationRef

const viewRef = content.createEmbeddedView(context);
this._applicationRef.attachView(viewRef);

https://github.com/ng-bootstrap/ng-bootstrap/blob/0f8055f54dad84e235810ff7bfc846911c168b7a/src/modal/modal-stack.ts#L107-L109

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

В общих случаях (см. пример, предоставленный @ConnorsFan) мы используем vcRef.createEmbeddedView, который отвечает за проверку запросов. в родительском представлении:

vcRef.createEmbeddedView
          ||
          \/
function attachEmbeddedView(
   ...
  Services.dirtyParentQueries(view);  <========== set dirty for queries

https://github.com/angular/angular/blob/0ebdb3d12f8cab7f9a24a414885ae3c646201e90/packages/core/src/view/view_attach.ts#L12-L23

Простое решение, которое я вижу здесь, это просто передать или вызвать ссылку непосредственно в шаблоне:

(click)="traceRef.savePNG()"
person yurzui    schedule 15.05.2018
comment
Большое спасибо! Я пытался решить эту проблему весь день :D - person merstik; 21.05.2018

Я думаю, вам нужно что-то, как показано ниже. замените ChildComponent именем вашего дочернего класса

@ViewChild('traceRef') 
      traceRef:ChildComponent
person Rohit Ramname    schedule 15.05.2018
comment
Я не могу так инициализировать, мне нужно передать значение, и я не хочу его менять. Просто хочу сгенерировать какой-то триггер нажатием кнопки, чтобы активировать функцию в дочернем компоненте. - person merstik; 15.05.2018
comment
да. Вы можете игнорировать этот код инициализации. Это не требуется. - person Rohit Ramname; 15.05.2018
comment
Все еще не определено :( - person merstik; 15.05.2018
comment
Вы изменили ChildComponent в моем примере кода на свой класс? Можете ли вы опубликовать код дочернего компонента? - person Rohit Ramname; 15.05.2018

Я заметил, что ваш родительский шаблон является модальным, и вы открываете его с помощью переменной шаблона здесь: ((click)="open(content)" через ваш модальный сервис. Это означает, что он динамически добавляется в DOM.

Так что это может быть проблема синхронизации, потому что родитель загружается динамически.

Я бы попробовал это, в ngAfterViewInit CHILD, запустить событие, используя EventEmitter. Захватите событие в родительском элементе, а затем установите флаг в значение true. Затем В ТЕОРИИ вы можете сделать это в родительском:

  savePNG() {
    if (this.childLoaded) this.traceRef.savePNG();
  }

Вы точно знаете, что ребенок загружен, поэтому доступен savePNG. Однако см. мое примечание — traceRef все равно будет неопределенным.

Поэтому вам нужно передать ссылку на дочернюю функцию в EventEmitter и использовать ее вместо #traceRef.

Например - у ребенка:

export class app-com2 implements blah blah {
  @Output() childLoaded: EventEmitter<any> = new EventEmitter<any>();

  savePNG() {
    // code to save the image
  }

  ngAfterViewInit() {
    this.childLoaded.emit(this.savePNG);
  }
}

Родитель:

..com1.html

  <ng-template #content let-c="close" let-d="dismiss">
    <div class="modal-header">
      <h4 class="modal-title">Title</h4>
    </div>
    <div class="modal-body" style="display: flex; justify-content: center;">
      <app-com2 [var1]="value" (childLoaded)="handleChildLoaded($event)"></app-com2>
    </div>

..com1.ts

handleChildLoaded(save: any) {
  const result = save;
  console.log(result);
}

Документы по дочерним и родительским сообщениям.

ПРИМЕЧАНИЕ

Думая об этом больше - это проблема жизненного цикла/времени, поэтому ваш код никогда не будет работать. Может быть поэтому:

  1. Ваш родительский компонент загружает, запускает все хуки жизненного цикла (например, ngOnInit и ngAfterViewInit и т. д.). #content обрабатывается и становится доступным в ngAfterViewInit и более поздних хуках.

  2. Теперь вы нажимаете кнопку, чтобы загрузить #content (который доступен). Но у него есть компонент <app-com2>, на который ссылается переменная шаблона #traceRef. Но переменные шаблона обрабатываются только непосредственно перед срабатыванием хука ngAfterViewInit, который уже сработал для родителя, поэтому #traceRef приходит слишком поздно, чтобы Angular мог его подключить. Следовательно, он всегда будет неопределенным.

См. комментарии ниже относительно (2) - не совсем правильно, но я подозреваю, что из-за того, как вы загружаете модальное окно, #traceRef всегда будет неопределенным.

person rmcsharry    schedule 15.05.2018
comment
Ага выглядит так... Можете ли вы порекомендовать другой способ запуска функции ребенка? - person merstik; 15.05.2018
comment
@rmcsharry - Вы уверены в своем примечании 2? См. этот stackblitz. - person ConnorsFan; 15.05.2018
comment
@ConnorsFan Хорошо, кажется, ваш stackblitz опровергает мой пункт 2 - может быть, это потому, что он загружает модальное окно с помощью modalService, и это приводит к тому, что #traceRef всегда не определено (поэтому не совсем то же самое, что ваш ngIf, который задерживает добавление дочернего элемента в ДОМ). - person rmcsharry; 15.05.2018
comment
@merstik Я добавил полный код идеи EventEmitter - передать функцию напрямую как часть даже полезной нагрузки. Это должно работать, но вам нужно будет найти лучший способ вызвать фактическую функцию в методе дескриптора (конечно, вместо console.log). - person rmcsharry; 15.05.2018