Понимание декораторов ViewChild и ViewChildren в Angular 10
Декораторы @ViewChild
и @ViewChildren
в Angular обеспечивают доступ к дочерним элементам в представлении DOM по настройке просмотра запросов. Запрос представления - это запрошенная ссылка на дочерний элемент в представлении компонента, которое содержит метаданные элемента. Область применения этих декораторов ограничена представлением компонентов и его встроенными дочерними представлениями. Эти декораторы особенно полезны в случаях, когда возможность доступа к элементам в представлении и их изменения обычными способами невозможна.
Например, если библиотека поставляется с компонентом или директивой с общедоступным свойством без ввода или вывода, которое вы хотите изменить, эти декораторы позволят вам получить к ним доступ и изменить. Эти декораторы также полезны для предоставления доступа к поставщикам, настроенным в дочерних компонентах, для внедрения зависимостей (например, служб, значений конфигурации и т.д.), к которым основной компонент может не иметь доступа.
Просмотр запросов и перехватчик жизненного цикла AfterViewInit
AfterViewInit
вызывается, когда представление компонента и его дочерние представления полностью инициализированы. Таким образом, для немедленных модификаций или назначений лучшим местом для доступа к запросам представления будет ngAfterViewInit
, потому что запросы представления уже разрешены и установлены. Попытка получить к ним доступ до ngAfterViewInit
может привести к неопределенным значениям. Однако декоратор @ViewChild
предоставляет свойство static
, которое можно установить для разрешения запроса представления перед запуском обнаружения изменений. Мы расскажем, как использовать это свойство ниже.
Декоратор ViewChild
Этот декоратор принимает три свойства: selector
, read
и static
. Свойства read
и static
не являются обязательными. Эти свойства указаны так:
@ViewChild(selector {read: readValue, static: staticValue}) property;
Поддерживаемые селекторы ViewChild
selector
указывает, какой дочерний элемент в компоненте необходимо запросить. Согласно документации, @ViewChild поддерживается пять видов селекторов:
1) Классы с декораторами @Component
или @Directive
В этом первом примере MenuItemComponent
это запрос из представления MenuComponent
:
@Component({
selector: 'menu-item',
template: `<p>{{menuText}}</p>`
})
export class MenuItemComponent {
@Input() menuText: string;
}
@Component({
selector: 'menu',
template: `<menu-item [menuText]="'Contact Us'"></menu-item>`
})
export class MenuComponent{
@ViewChild(MenuItemComponent) menu: MenuItem;
}
Вот пример с директивой:
@Directive({
selector: '[textHighlight]'
})
export class TextHighlightDirective{}
@Component({
selector: 'profile',
template: '<p textHighlight>Some text to highlight</p>'
})
export class ProfileComponent{
@ViewChild(TextHighlightDirective) highlightedText: TextHighlightDirective;
}
2) Ссылочная переменная шаблона в виде строки. Ссылочные переменные шаблона обычно используются в шаблонах, но в данном случае они используются для настройки запроса просмотра:
@Component({
selector: 'menu-item',
template: `<p>{{menuText}}</p>`
})
export class MenuItemComponent {
@Input() menuText: string;
}
@Component({
selector: 'menu',
template: `
<menu-item #aboutUs [menuText]="'About Us'"></menu-item>
<menu-item #contactUs [menuText]="'Contact Us'"></menu-item>
`
})
export class MenuComponent{
@ViewChild('aboutUs') aboutItem: MenuItem;
@ViewChild('contactUs') contactItem: MenuItem;
}
3) Сервис, определенный в дереве дочерних компонентов текущего компонента. В этом примере указывается как маркер поставщика SampleService
для FirstChildComponentClass
. Поскольку <first-child>
это элемент в ParentComponent
мы можем получить доступ к нему, используя класс SampleService
в качестве токена:
export class SampleService {}
@Component({
selector: 'first-child',
providers: [SampleService]
})
export class FirstChildComponent{}
@Component({
selector: 'parent',
template: '<first-child></first-child>'
})
export class ParentComponent{
@ViewChild(SampleService) sampleService: SampleService;
}
4) Провайдер, определенный с помощью строкового токена. Хотя это указано в документации, получение поставщика с помощью этого метода возвращает неопределенные значения. Это регресс в Ivy, который по умолчанию включен в Angular 9. Исправление было сделано, но на момент публикации этой статьи оно не было включено ни в один выпуск. Чтобы это работало, вам нужно отключить Ivy в файле tsconfig.json
и вместо этого использовать ViewEngine
:
{
"angularCompilerOptions": {
"enableIvy": false,
}
}
Вот как вы можете использовать поставщика, определенного с помощью строкового токена, в качестве селектора:
@Component({
selector: 'first-child',
providers: [{ provide: 'TokenA', useValue: 'ValueA' }]
})
export class FirstChildComponent{}
@Component({
selector: 'parent',
template: '<first-child></first-child>'
})
export class ParentComponent{
@ViewChild('TokenA') providerA: string;
}
Однако, если вы хотите использовать этот тип селектора с Ivy, вы можете использовать свойство read
для получения запроса представления:
export class ParentComponent{
@ViewChild(FirstChildComponent, { read: 'TokenA' }) providerA: string;
}
5) TemplateRef
. Доступ к встроенным шаблонам можно получить с помощью декоратора @ViewChild
, который затем можно использовать для создания экземпляров встроенных представлений с помощью ViewContainerRef
:
@Component({
selector: `container`,
template: `<ng-template><h1>This container is empty</h1></ng-template>`
})
export class ContainerComponent{
@ViewChild(TemplateRef) contTemplate: TemplateRef;
}
Чтобы лучше понять, как использовать эти запросы представления после настройки, ознакомьтесь с этими живыми примерами:
Для каждого из этих типов селекторов. Они иллюстрируют, как можно использовать запросы представления для доступа и изменения встроенных представлений.
Использование свойства read
Свойство read
позволяет выбирать различные маркеры из элементов, которые вы запрашиваете. Эти токены могут быть токенами поставщика, используемыми для внедрения зависимостей, или, в некоторых случаях, быть типом запроса представления. Это необязательное свойство.
В приведенном ниже примере у FirstChildComponent
есть конфигурация поставщика со всеми видами токенов зависимости, такими как класс, строковые токены и токен внедрения. Эти токены в сочетании со свойством read
могут раскрывать эти зависимости родительским компонентам, которые встраивают FirstChildComponent
. Все эти зависимости были доступны в ParentComponent
с помощью декоратора ViewChild
и свойства read
, определяющего каждый из соответствующих токенов.
Также можно указать тип, которым должен быть запрос представления, используя свойство read
. В том же примере, оба свойства запроса fcElementRef
и fcComponent
из, FirstChildComponent
но имеют различные типы, основанные на том, что read
была определена как:
export class SampleService {}
export const ExampleServiceToken = new InjectionToken<string>('ExampleService');
@Component({
selector: 'first-child',
providers: [
SampleService,
{ provide: 'TokenA', useValue: 'valueA' },
{ provide: 'TokenB', useValue: 123 },
{ provide: ExampleServiceToken, useExisting: SampleService },
{ provide: 'TokenC', useValue: true }
]
})
export class FirstChildComponent{}
@Component({
selector: 'parent',
template: `<first-child></first-child>`
})
export class ParentComponent{
@ViewChild(FirstChildComponent, { read: 'TokenA' }) dependencyA: string;
@ViewChild(FirstChildComponent, { read: 'TokenB' }) dependencyB: number;
@ViewChild(FirstChildComponent, { read: 'TokenC' }) dependencyC: boolean;
@ViewChild(FirstChildComponent, { read: SampleService }) sampleService: SampleService;
@ViewChild(FirstChildComponent, { read: ElementRef }) fcElementRef: ElementRef;
@ViewChild(FirstChildComponent, { read: FirstChildComponent }) fcComponent: FirstChildComponent;
@ViewChild(FirstChildComponent, { read: ExampleServiceToken }) exampleService: SampleService;
}
Использование свойства static
Свойство static
принимает логическое значение и не является обязательным. По умолчанию это false
. Если это true
, запрос представления разрешается до полной инициализации полного компетентного представления и свойств с привязкой к данным. Если установлено значение false, запрос представления разрешается после полной инициализации представления компонента и свойств с привязкой к данным.
В этом примере элемент пункта запрашиваются с использованием true
и свойства и значения регистрируются для каждого в и обратных вызовах ngOnInit
ngAfterViewInit
:
@Component({
selector: 'display-name',
template: '<p #displayName>{{name}}</p>'
})
export class DisplayNameComponent implements OnInit, AfterViewInit{
@ViewChild('displayName', {static: true}) staticName: ElementRef;
@ViewChild('displayName', {static: false}) nonStaticName: ElementRef;
name: string = "Jane";
ngOnInit(){
logValues('OnInit');
}
ngAfterViewInit(){
logValues('AfterViewInit');
}
logValues(eventType: string){
console.log(`[${eventType}]\n staticName: ${this.staticName}, name value: "${this.staticName.nativeElement.innerHTML}"\n nonStaticName: ${this.nonStaticName}, name value: "${this.nonStaticName.nativeElement.innerHTML}"\n`);
}
}
Вот что будет в консоли:
[OnInit]
staticName: [object Object], name value: "" // static: true
nonStaticName: undefined, name value: "" // static: false
[AfterViewInit]
staticName: [object Object], name value: "Jane" // static: true
nonStaticName: [object Object], name value: "Jane" // static: false
В обратном вызове ngOnInit
, ни один из интерполированных значений не инициализировано, с { static: true }
, запрос представления уже решен, но не определен. Однако после события все запросы просмотра были разрешены.
Вы можете просмотреть эти живые примеры, которые лучше иллюстрируют , как использовать свойства read
и static
с декоратором @ViewChild
:
ViewChildren
Декоратор @ViewChildren
работает аналогично @ViewChild
, но вместо настройки одного запроса, он получает список запросов. Из представления компонентов DOM он извлекает дочерние элементы в виде QueryList
. Этот список обновляется при внесении любых изменений в дочерние элементы. Декоратор @ViewChildren
принимает два свойства, selector
и read
. Эти свойства работают так же, как и в декораторе @ViewChild
. Любые соответствующие дочерние элементы будут частью списка. Вот пример:
@Component({
selector: 'item-label',
template: `<h6>{{labelText}}</h6>`
})
export class ItemLabelComponent{
@Input() labelText: string;
}
@Component({
selector: 'item',
template: `<item-label *ngFor="let label of labels" [labelText]="label"></item-label>`
})
export class ItemComponent{
@ViewChildren(ItemLabelComponent) allLabels: ItemLabelComponent;
labels = ['recent', 'popular', 'new'];
}
Длина allLabels
будет равна трем, так как все элементы <item-label>
будут выделены. У QueryList
есть несколько методов, которые вы можете использовать для управления.
Для более подробной иллюстрации того, как использовать декоратор @ViewChildren
и указать свойство read
, посмотрите этот пример: