AngularInDepth уходит от Medium. Более свежие статьи размещаются на новой платформе inDepth.dev. Спасибо за то, что участвуете в глубоком движении!
Кент С. Доддс написал статью об использовании prop getters в React. Наряду с реквизитами рендеринга (см. TemplateRefs являются реквизитами рендеринга Angular), геттеры свойств позволяют авторам библиотек компонентов предоставлять пользователям максимально возможный контроль над рендерингом - компоненту нужно только выполнять свою работу.
Если я правильно понимаю статью Кента, есть 3 вещи, которые вы можете сделать с помощью проп-геттеров:
- Сообщите компоненту, где применять атрибуты / слушатели
- Зарегистрируйте несколько слушателей событий
- Изменить примененные атрибуты
Я собираюсь показать, как вы можете использовать директивы содержимого для выполнения тех же трех задач. Директивы содержимого - это директивы Angular, которые помещаются в содержимое (в отличие от представления / шаблона) компонента. Затем компонент может взаимодействовать с этими директивами, используя @ContentChildren
.
Для этого я буду использовать в качестве примера понижающую передачу. Вот пример кода React, который использует понижающую передачу (напрямую скопирован из связанной статьи):
import React from 'react' import {render} from 'react-dom' import Downshift from 'downshift' const items = ['apple', 'pear', 'orange', 'grape', 'banana'] render( <Downshift onChange={selection => alert(`You selected ${selection}`)}> {({ getInputProps, getLabelProps, getItemProps, isOpen, inputValue, highlightedIndex, selectedItem, }) => <div> <label {...getLabelProps()}>Enter a fruit</label> <input {...getInputProps()} /> {isOpen ? ( <div> {items .filter(i => !inputValue || i.includes(inputValue)) .map((item, index) => <div {...getItemProps({ key: item, index, item, style: { backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }, })} > {item} </div>, )} </div> ) : ( null )} </div>} </Downshift>, document.getElementById('root'), )
А вот тот же пример с использованием мифического компонента ngx-downshift
, который использует директивы содержимого в Angular:
==== html file ====
<ngx-downshift #downshift="ngx-downshift" (change)="alert('You selected ' + $event)"> <label downshiftLabel>Enter a fruit</label>
<input type="text" downshiftInput (change)="filteredItems =
items.filter(i => !$event || i.includes($event)"> <div *ngIf="downshift.isOpen"> <div *ngFor="item of filteredItems; let i = index" [style.backgroundColor]="downshift.highlightedIndex === i ? 'lightgray' : 'white'" [style.fontWeight]="downshift.selectedItem === item ? 'bold' : 'normal'" downshiftItem> {{item}} </div> </div> </ngx-downshift>
==== ts file ==== @Component({ /* something here */ }) export class SomeComponent { items = ['apple', 'pear', 'orange', 'grape', 'banana']; alert(message: any) { console.log(message); } }
Если бы мне пришлось написать этот пример с нуля, я бы поместил большую часть логики в файл ts вместо того, чтобы помещать так много в шаблон, но я написал его таким образом, чтобы попытаться отразить стиль примера с пониженной передачей.
1. Сообщите компоненту, где применять атрибуты / слушатели.
Вся обработка компонента ngx-downshift
оставлена на усмотрение пользователя компонента. Вы можете применить директивы downshiftLabel
, downshiftInput
и downshiftItem
к любым элементам, которые хотите, и ngx-downshift
затем применит все атрибуты, которые требуются к этим элементам.
2. Зарегистрируйте несколько прослушивателей событий.
На самом деле это не проблема Angular, поскольку Angular Essential автоматически выполняет логику callAll
. Слушатели событий всегда добавляются, но не заменяются. Вы можете увидеть это даже без директив:
<button (click)="alert('hi')" (click)="alert('bye')">Say hi and bye</button>
Этот код покажет два предупреждения.
То же самое и для более сложного сценария директивы:
<button clickHi (click)="alert('bye')">Say hi and bye</button> === clickHiDirective.ts === @Directive({ selector: '[clickHi]' }) export class ClickHiDirective { @HostListener('click') alertHi() { alert('hi'); } }
Этот код также покажет два предупреждения.
3. Измените применяемые атрибуты.
Один из атрибутов, для которых понижающая передача применяется к входным данным, - aria-autocomplete="list"
. Понижение передачи делает это через getInputProps
. Если бы мы хотели изменить это на aria-autocomplete="both"
, мы могли бы сделать это getInputProps({ 'aria-autocomplete': 'both' })
. ngx-downshift
обновит атрибут директивой downshiftInput
. Нравится:
@Directive({ selector: '[downshiftInput]' }) export class DownshiftInputDirective { @HostBinding('attr.aria-autocomplete') @Input('attr.aria-autocomplete') ariaAutocomplete: 'list'; }
@HostBinding
применяет атрибут к элементу хоста директивы. @Input
вступает в игру, когда вы хотите перезаписать значение, например:
<input downshiftInput attr.aria-autocomplete="both" />
Обратите внимание, что порядок директивы и переопределения не имеет значения. Если пользователь указывает значение переопределения в любом месте элемента, оно переопределит версию директивы.
Мифический компонент ngx-downshift
ngx-downshift
на самом деле не существует, но если вы хотите его создать, пожалуйста, создайте. Однако автозаполнение Angular Material использует многие те же концепции. Вот пример использования автозаполнения:
<mat-form-field>
<input type="text" matInput [formControl]="myControl" [matAutocomplete]="auto">
</mat-form-field>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of options" [value]="option">
{{ option }}
</mat-option>
</mat-autocomplete>
Как видите, matInput
- это директива содержимого для mat-form-field
. Затем он ссылается на компонент mat-autocomplete
с помощью директивы matAutocomplete
. Компонент mat-autocomplete
действительно обрабатывает часть отрисовки списка автозаполнения за вас, но большая часть отрисовки остается на усмотрение пользователя.
Джереми Элборн и Майлз Малерба выступили на ng-conf 2017 с докладом, в котором отстаивали многие из тех же идей. Перейдите примерно к 5:00 и перейдите к тому моменту, на который я ссылаюсь.
Это для меня. Спасибо за прочтение!