Оригинальная ссылка:Exploring Angular DOM manipulation techniques using ViewContainerRef
Если вы хотите узнать больше о том, как Angular использует технологии Renderer и View Containers для управления DOM, вы можете посмотреть это видео на YouTube.my talk at NgVikings.
Каждый раз, когда я читаю статьи о том, как Angular манипулирует DOM, я всегда нахожу в этих статьях упоминаниеElementRef
,TemplateRef
,ViewContainerRef
и другие классы. Хотя эти классы описаны в официальной документации Angular или связанных статьях, общая идея редко описывается, и есть несколько соответствующих примеров того, как эти классы работают вместе, и эта статья в основном описывает их.
Если вы пришли из мира angular.js, легко понять, как манипулировать DOM с помощью angular.js. angular.js будет вlink
Внедрить DOM в функциюelement
, вы можете запрашивать любой узел в шаблоне компонента, добавлять или удалять узлы, изменять стили и т. д. Однако у этого подхода есть существенный недостаток:Тесно связан с браузерной платформой.
Новая версия Angular должна работать на разных платформах, таких как платформа браузера, мобильная платформа или платформа веб-воркеров, поэтому требуется уровень абстракции между API-интерфейсом для конкретной платформы и интерфейсом фреймворка. Этот уровень абстракции в Angular состоит из следующих ссылочных типов:ElementRef
,TemplateRef
,ViewRef
,ComponentRef
а такжеViewContainerRef
. В этой статье подробно объясняется каждый ссылочный тип и то, как этот ссылочный тип манипулирует DOM.
@ViewChild
Прежде чем изучать абстрактные классы DOM, давайте узнаем, как получить их в компонентах/директивах. Angular предоставляет технологию под названием DOM Query, в основном производную от@ViewChild
а также@ViewChildren
Декораторы. Основные функции у них одинаковые, разница только в@ViewChild
возвращает одну ссылку,@ViewChildren
вернулсяQueryListОбъект оборачивает несколько ссылок. В этом примере основнойViewChild
пример и исключен из описания@
.
Обычно эти два декоратора связаны со ссылочными переменными шаблона (template reference variable) вместе ссылочная переменная шаблона представляет собой просто именованную ссылку на элемент DOM в шаблоне, подобноhtml
элементальid
Атрибуты. Вы можете пометить элемент DOM ссылкой на шаблон и использовать его в классе компонента/директивы.ViewChild
Декоратор запрашивает его, например:
@Component({
selector: 'sample',
template: `
<span #tref>I am span</span>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tref", {read: ElementRef}) tref: ElementRef;
ngAfterViewInit(): void {
// outputs `I am span`
console.log(this.tref.nativeElement.textContent);
}
}
ViewChild
Основной синтаксис декоратора:
@ViewChild([reference from template], {read: [reference type]});
Как вы можете видеть в приведенном выше примере, я поставилtref
в качестве ссылочного имени шаблона и поместитеElementRef
связанные с этим элементом. второй параметрread
является необязательным, потому что Angular выводит ссылочный тип на основе типа элемента DOM. Например, если он (#tref) монтирует что-то вродеspan
Простой HTML-элемент, который возвращает Angular.ElementRef
; если он монтируетсяtemplate
элемент, возвращаемый AngularTemplateRef
. Некоторые ссылочные типы, такие какViewContainerRef
не может быть выведен Angular, поэтому он должен быть вread
Явно объявлено в параметре. другие, такие какViewRef
Его нельзя смонтировать в DOM-элементе, поэтому его нужно кодировать вручную и создавать в конструкторе.
Теперь давайте рассмотрим, как получить эти цитаты.
ElementRef
Это самый простой абстрактный класс, если вы посмотрите на его структуру класса, он содержит только объект смонтированного элемента, который полезен для доступа к собственным элементам DOM, таким как:
// outputs `I am span`
console.log(this.tref.nativeElement.textContent);
Однако,Команда Angular не одобряет эту нотацию, не только потому, что этот подход подвергает риску безопасность, но и тесно связывает вашу программу со слоями рендеринга, что затрудняет запуск вашей программы на нескольких платформах. думаю проблема не в использованииnativeElement
Вместо этого это вызвано использованием определенного API DOM, такого какtextContent
. Но, как вы увидите позже, Angular реализует общую идею управления DOM, так что низкоуровневые API, такие какtextContent
.
использоватьViewChild
Украшенный элемент DOM вернетсяElementRef
, но поскольку все компоненты монтируются в пользовательские элементы DOM, а все инструкции действуют на элементы DOM, и компоненты, и инструкции могут получать информацию об элементе хоста через DI (внедрение зависимостей).ElementRef
объект. Например:
@Component({
selector: 'sample',
...
export class SampleComponent{
constructor(private hostElement: ElementRef) {
//outputs <sample>...</sample>
console.log(this.hostElement.nativeElement.outerHTML);
}
...
Таким образом, компонент может получить доступ к своему хост-элементу через DI (внедрение зависимостей), ноViewChild
Декораторы часто используются для получения элементов DOM в представлениях шаблонов. Однако с инструкцией все обстоит наоборот: поскольку у инструкции нет шаблона представления, она в основном используется для получения смонтированного инструкцией хост-элемента.
TemplateRef
Концепция шаблона знакома большинству разработчиков и представляет собой набор элементов DOM в представлении для нескольких приложений. Представлено в HTML5templateвкладку, браузер передаетscript
Настройки во вкладкеtype
атрибуты для импорта шаблонов, такие как:
<script id="tpl" type="text/template">
<span>I am span in template</span>
</script>
Этот подход не только семантически ошибочен, но и требует ручного создания модели DOM.template
теги, которые браузеры могут анализироватьhtml
и создатьDOM
дерево, но не будет отображать его, доступ к дереву DOM можно получить черезcontent
Доступ к атрибутам, например:
<script>
let tpl = document.querySelector('#tpl');
let container = document.querySelector('.insert-after-me');
insertAfter(container, tpl.content);
</script>
<div class="insert-after-me"></div>
<ng-template id="tpl">
<span>I am span in template</span>
</ng-template>
Угловой принимаетtemplate
Таким образом, этикетка реализуетTemplateRef
абстрактные классы приходят иtemplate
пометьте вместе, чтобы увидеть, как он используется:
@Component({
selector: 'sample',
template: `
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tpl") tpl: TemplateRef<any>;
ngAfterViewInit() {
let elementRef = this.tpl.elementRef;
// outputs `template bindings={}`
console.log(elementRef.nativeElement.textContent);
}
}
Фреймворк Angular удален из DOMtemplate
элемент и вставляем на его место комментарий, вот как это выглядит после рендеринга:
<sample>
<!--template bindings={}-->
</sample>
TemplateRef
это абстрактный класс с простой структурой, егоelementRef
Атрибут — это ссылка на его основной элемент, аcreateEmbeddedView
метод. ОднакоcreateEmbeddedView
метод полезен, потому что он создает представление и возвращает ссылку на это представлениеViewRef
.
ViewRef
Эта абстракция представляет собой представление Angular (View), в мире Angular представление (View) представляет собой комбинацию кучи элементов, созданных и уничтоженных вместе, является краеугольным камнем построения пользовательского интерфейса программы. Angular побуждает разработчиков думать о пользовательском интерфейсе как о наборе представлений, а не просто о дереве html-тегов.
Angular поддерживает два типа представлений:
- Встроенный вид, автор
Template
поставка - Просмотр хоста, по
Component
поставка
Создание встроенного представления
Шаблоны — это просто чертежи для представлений, доступ к которым можно получить через ранее упомянутыйcreateEmbeddedView
метод для создания представления, например:
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
}
Создать представление хоста
Представление хоста создается при динамическом создании экземпляра компонента, динамический компонент может бытьComponentFactoryResolver
Создайте:
constructor(private injector: Injector,
private r: ComponentFactoryResolver) {
let factory = this.r.resolveComponentFactory(ColorComponent);
let componentRef = factory.create(injector);
let view = componentRef.hostView;
}
В Angular каждый компонент привязан к экземпляру инжектора, поэтому создайтеColorComponent
Инжектор текущего компонента (т.е. SampleComponent) передается, когда компонент. Кроме того, не забывайте, что при динамическом создании компонентов вам необходимо создать компонент в модуле (модуле) или хост-компонент.EntryComponentsсвойство для добавления к созданному компоненту.
Теперь, когда мы увидели, как создаются встроенное представление и основное представление, после создания представления его можно использовать.ViewContainer
Вставьте в дерево DOM. Эта функция в основном исследуется ниже.
ViewContainerRef
Контейнер представления — это контейнер, который монтирует одно или несколько представлений.
Первое, что нужно сказать, это то, что любой элемент DOM может быть контейнером представления, как бы интересно это ни было, для привязкиViewContainer
DOM-элемент, Angular не вставляет представление внутрь элемента, а добавляет представление после элемента, что похоже наrouter-outlet
Как вставлять компоненты.
Обычно лучше поставитьViewContainer
связаны вng-container
элемент, потому чтоng-container
Элементы отображаются как комментарии, поэтому в DOM не добавляются дополнительные элементы HTML. В следующем примере показано, как создать в шаблоне компонентаViewContainer
:
@Component({
selector: 'sample',
template: `
<span>I am first span</span>
<ng-container #vc></ng-container>
<span>I am last span</span>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;
ngAfterViewInit(): void {
// outputs `template bindings={}`
console.log(this.vc.element.nativeElement.textContent);
}
}
Как и другие абстрактные классы,ViewContainer
пройти черезelement
Атрибуты привязаны к элементам DOM. Например, в приведенном выше примере привязка будет отображаться как комментарий.ng-container
элементы, поэтому вывод также будетtemplate bindings={}
.
Просмотр действий
ViewContainer
Предоставляются некоторые API представления действий:
class ViewContainerRef {
...
clear() : void
insert(viewRef: ViewRef, index?: number) : ViewRef
get(index: number) : ViewRef
indexOf(viewRef: ViewRef) : number
detach(index?: number) : ViewRef
move(viewRef: ViewRef, currentIndex: number) : ViewRef
}
Выше мы видели, как создавать два типа представлений с помощью шаблонов и компонентов, а именно встроенные представления и представления компонентов. Как только у вас есть представление, вы можете пройтиinsert
метод вставляется в DOM. В следующем примере показано, как создать встроенное представление из шаблона иng-container
Вставьте вид в отмеченное местоng-container
позже, а не вставлять внутрь этого элемента DOM).
@Component({
selector: 'sample',
template: `
<span>I am first span</span>
<ng-container #vc></ng-container>
<span>I am last span</span>
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;
@ViewChild("tpl") tpl: TemplateRef<any>;
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
this.vc.insert(view);
}
}
С приведенной выше реализацией окончательныйhtml
Это выглядит как:
<sample>
<span>I am first span</span>
<!--template bindings={}-->
<span>I am span in template</span>
<span>I am last span</span>
<!--template bindings={}-->
</sample>
в состоянии пройтиdetach
Метод удаляет DOM из представления, и другие методы могут узнать его значение по имени метода, например, получить объект ссылки на представление по индексу, переместить позицию представления или удалить все представления из контейнера представления.
Создать представление
ViewContainer
Также предоставляется API для ручного создания представления:
class ViewContainerRef {
element: ElementRef
length: number
createComponent(componentFactory...): ComponentRef<C>
createEmbeddedView(templateRef...): EmbeddedViewRef<C>
...
}
Вышеупомянутые два метода являются хорошей инкапсуляцией, вы можете передать объект ссылки на шаблон или объект фабрики компонентов, чтобы создать представление и вставить представление в определенную позицию в контейнере представления.
нгтемплатаутлет и нгкомпонентаутлет
Хотя хорошо знать внутренности манипуляций Angular с DOM, было бы неплохо иметь какой-то ярлык. Да, Angular предоставляет два ярлыка:ngTemplateOutlet
а такжеngComponentOutlet
. Обе директивы являются экспериментальными на момент написания статьи.ngComponentOutlet
Также будет доступна в версии 4. Если вы читали вышеизложенное, легко увидеть, что делают эти две инструкции.
ngTemplateOutlet
Эта директива пометит элемент DOM какViewContainer
и вставляет встроенное представление, созданное шаблоном, устраняя необходимость явного создания встроенного представления в классе компонента. Таким образом, в приведенном выше примере для создания встроенного представления и вставки#vc
Затем код для элемента DOM можно переписать:
@Component({
selector: 'sample',
template: `
<span>I am first span</span>
<ng-container [ngTemplateOutlet]="tpl"></ng-container>
<span>I am last span</span>
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
export class SampleComponent {}
Из приведенного выше примера видно, что нам не нужно писать какой-либо код для создания экземпляра представления в классе компонента. Очень удобно, правда?
ngComponentOutlet
Эта команда связана сngTemplateOutlet
очень похоже, разница естьngComponentOutlet
Создается основное представление, созданное путем создания экземпляра компонента, а не встроенное представление. Вы можете использовать его следующим образом:
<ng-container *ngComponentOutlet="ColorComponent"></ng-container>
Суммировать
Кажется, что нужно переварить много новых знаний, но на самом деле модель мышления Angular по манипулированию DOM через представления очень четкая и последовательная. вы можете использоватьViewChild
Запросите ссылочную переменную шаблона, чтобы получить абстрактный класс Angular DOM. Простейшая оболочка для элемента DOM:ElementRef
; в то время как для шаблонов вы можете использоватьTemplateRef
для создания встроенных представлений; для компонентов вы можете использоватьComponentRef
для создания основного представления, и в то же время вы можете использоватьComponentFactoryResolver
СоздайтеComponentRef
. Два созданных представления (то есть встроенное представление и основное представление) будутViewContainerRef
управлять. Наконец, Angular предоставляет еще два ярлыка для автоматизации этого процесса:ngTemplateOutlet
Директивы используют шаблоны для создания встроенных представлений;ngComponentOutlet
Используйте динамические компоненты для создания основных представлений.