Avatar ·

Ошибка Angular @ViewChild: Expected 2 arguments, but got 1 — как исправить?

Привет! Перехожу с Angular 7 на Angular 9 и столкнулся с непонятной ошибкой. Раньше я использовал `@ViewChild()` так: ```typescript @ViewChild('myDiv') myDiv: ElementRef; ``` Но теперь компилятор TypeScript выдаёт ошибку: ``` Expected 2 arguments, but got 1. ``` Я читал, что в Angular 8+ изменили сигнатуру `@ViewChild`. Я попробовал добавить `{ static: false }` — ошибка исчезла. Но я не совсем понимаю, что это значит. Когда нужно использовать `static: true`, а когда `static: false`? Например, у меня есть шаблон с элементом `
`, и я хочу получить к нему доступ в `ngAfterViewInit()`. Также есть директива ``, к которой я обращаюсь в `ngOnInit()`. Подскажите, пожалуйста, как правильно использовать `@ViewChild` в новых версиях Angular. Почему нужно указывать второй аргумент? И что будет, если я поставлю `static: true` везде? Спасибо! <<>>

Причина ошибки и правильное использование @ViewChild в Angular 9+

Ошибка Expected 2 arguments, but got 1 возникает из-за того, что начиная с Angular 8 декоратор @ViewChild (и @ContentChild) требует обязательный второй аргумент — объект с опцией static. Это изменение было введено для повышения производительности и предсказуемости работы с шаблонами.

Что такое static: true и static: false?

  • static: false (рекомендуется по умолчанию) — означает, что элемент или компонент будет доступен только после того, как Angular завершит рендеринг представления, то есть в хуках ngAfterViewInit() и позже. Это стандартный вариант для динамических элементов, условных конструкций (*ngIf) и компонентов, которые могут появляться не сразу.
  • static: true — делает элемент доступным уже на этапе ngOnInit(). Это подходит только для статических элементов, которые всегда присутствуют в шаблоне и не зависят от условий (например, #myStaticDiv без *ngIf).

Правильные примеры

1. Доступ к элементу в ngAfterViewInit (static: false)

// Шаблон: <div #myDiv>Привет</div>
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-example',
  template: '<div #myDiv>Привет</div>'
})
export class ExampleComponent implements AfterViewInit {
  @ViewChild('myDiv', { static: false }) myDiv!: ElementRef;

  ngAfterViewInit() {
    console.log(this.myDiv.nativeElement.textContent); // "Привет"
  }
}

2. Доступ к компоненту в ngOnInit (static: true)

// Шаблон: <app-child #childComp></app-child>
import { Component, ViewChild, OnInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-parent',
  template: '<app-child #childComp></app-child>'
})
export class ParentComponent implements OnInit {
  @ViewChild('childComp', { static: true }) childComp!: ChildComponent;

  ngOnInit() {
    console.log(this.childComp); // Доступно сразу
  }
}

Когда использовать static: true?

  • Элемент или компонент не обёрнут в *ngIf, *ngFor или [hidden].
  • Вам нужно получить доступ к нему в ngOnInit() (редкий случай, обычно лучше использовать ngAfterViewInit).
  • Пример: статический заголовок страницы <h1 #title>Заголовок</h1>.

Когда использовать static: false?

  • Элемент может быть динамическим (условный рендеринг, циклы).
  • Вы обращаетесь к нему в ngAfterViewInit() или позже.
  • Это безопасный вариант по умолчанию — используйте его, если не уверены.

Что будет, если поставить static: true везде?

В некоторых случаях это приведёт к ошибкам. Например, если элемент скрыт через *ngIf, то в ngOnInit() он ещё не существует, и вы получите undefined. Angular также выдаст предупреждение в консоли. Поэтому лучше явно указывать правильное значение.

Итог

Ошибка решается простым добавлением второго параметра. Для большинства случаев используйте { static: false }. Только если вы уверены, что элемент статичен и нужен в ngOnInit, ставьте { static: true }.

<<>>

Альтернативный подход: использование @ViewChild без static с Angular 14+

В Angular 14 и новее появилась возможность не указывать второй аргумент, если вы используете строгий режим и сигналы. Однако это работает только при определённых условиях:

// Angular 14+ с сигналами
import { Component, viewChild } from '@angular/core';

@Component({
  template: '<div #myDiv>Привет</div>'
})
export class ExampleComponent {
  myDiv = viewChild<ElementRef>('myDiv'); // Без второго аргумента
}

Но если вы используете классический декоратор @ViewChild, то второй аргумент всё ещё обязателен. В Angular 15+ команда Angular рекомендует переходить на новый функциональный синтаксис viewChild(), который не требует static и автоматически определяет, когда элемент доступен.

Миграция с @ViewChild на viewChild

  • Замените @ViewChild('ref', { static: false }) на viewChild('ref').
  • Для статических элементов используйте viewChild.required(), если элемент гарантированно существует.
  • Результат — сигнал, который можно читать через .value или использовать в шаблоне.

Этот подход упрощает код и избавляет от необходимости думать о static. Рекомендую изучить сигналы, если вы работаете с современными версиями Angular.