DevGang
Авторизоваться

Понимание декораторов 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 и свойства и значения регистрируются для каждого в и обратных вызовах ngOnInitngAfterViewInit:

@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, посмотрите этот пример:

Источник:

#JavaScript #Angular #TypeScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

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

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить