В мире JavaScript есть мощный инструмент, который расширяет возможности разработки, улучшает выполнение кода и повышает производительность: объекты. В JavaScript все является объектом, и были предприняты значительные усилия, чтобы обеспечить эффективные способы работы с ними. Среди ключевых особенностей объектов JavaScript — динамический доступ к свойствам, который позволяет нам получать доступ к свойствам объектов динамически, не зная заранее их точных имен. Эта возможность открывает мир возможностей для бесшовной интеграции и установления связей между различными модулями кода. Благодаря динамическому доступу к свойствам мы можем подходить к нашим проектам с полностью динамической точки зрения, избавляя от необходимости записывать свойства статически. Кроме того, эти свойства могут выходить за рамки простых фрагментов данных; они могут включать функции для вызова, ссылки на другие объекты или даже компоненты в области одностраничные приложения (SPA). Этот уровень динамизма раскрывает истинный потенциал нашего кода JavaScript.

В этой статье мы воспользуемся возможностями динамического доступа к свойствам для решения одного из наиболее распространенных сценариев в любом SPA. В частности, мы рассмотрим, как мы можем применить эту функцию для улучшения процесса разработки в Angular.

Распространенный случай, который я хочу рассмотреть в этой статье, — это реализация функционала вкладок. Во многих приложениях часто возникает необходимость предоставить несколько вкладок на одной странице. Проблема заключается в динамическом отображении соответствующего компонента на основе активной вкладки.

Традиционно общий подход к созданию этой функциональности в Angular заключается в использовании директивы `ngSwitch`. В файл шаблона можно добавить все компоненты вкладок, а директива `ngSwitchCase` используется для условного отображения нужного компонента на основе свойства, переданного в `ngSwitch`директива. Кроме того, на уровне компонента в директиву `ngSwitchCase` можно передать условие, позволяющее отобразить нужный компонент.

Давайте рассмотрим сценарий, в котором нам нужно отобразить три вкладки для трех разных компонентов: Пользователи, Продукты и Категории. Мы хотим отображать только один компонент каждый раз при нажатии вкладки.

Используя традиционный подход, эта задача относительно проста. Мы можем определить свойство, которое указывает номер активной вкладки, и визуализировать компоненты на основе этого значения.

Однако по мере того, как количество вкладок и компонентов увеличивается, ранее упомянутый подход становится все более громоздким и сложным для поддерживать. Управление несколькими компонентами в файле шаблона приводит к загроможденной и менее читаемой структуре кода. Кроме того, этому подходу не хватает желаемого уровня расширяемости при работе с значительным числом вкладок. Файл шаблона загроможден условно отображаемыми компонентами, которые излишне занимают несколько строк, что затрудняет понимание и изменение.

Кроме того, добавление нового компонента к существующей установке оказывается сложной задачей. Каждый новый компонент требует добавления соответствующего условия, что еще больше усложняет кодовую базу и затрудняет ее расширяемость.

Для достижения более расширяемого и универсального подхода мы можем использовать метод Динамический дочерний загрузчик. При таком подходе мы можем пересмотреть условный рендеринг компонентов с другой точки зрения. В отличие от предыдущего подхода, который следует последовательной логике с использованием `ngSwitch`, который действует как последовательность операторов if-else, наш подход поощряет точку зрения, основанную на ссылках.

Мы создаем карту, содержащую компоненты, и она отвечает за отрисовку правильного компонента на основе ссылки, которую мы передаем ей.

В нашем случае эта карта может быть просто объектом, содержащим классы нужных компонентов. Каждому классу назначается ключ, и этот ключ может быть выбран на основе реализуемой нами логики.

Чтобы увидеть это в действии, давайте применим то, что мы обсуждали, к предыдущему случаю. Предположим, в структуре нашего проекта есть три компонента: Пользователи, Продукты и Категории. У нас также есть компонент tabs на нашей странице, который одновременно отображает один из этих трех компонентов. Наша цель — изменить отображаемый компонент при нажатии на вкладку.

Чтобы реализовать этот подход, нам нужно создать карту, которая содержит эти три компонента. Мы назначаем ключ каждому компоненту, который представляет собой номер активной вкладки. Как показано ниже:

 componentsMap = {
   1: UsersComponent,
   2: ProductsComponent,
   3: CategoriesComponent,
 };

Теперь, если мы скажем `componentsMap[1]`, мы знаем, что это будет ссылка на UsersComponent, так что теперь нет необходимости добавлять условие или переключатель регистра, чтобы сказать `active === 1` или `if(active === 1)`, теперь мы можем просто передать актив объекту и получить к нему динамический доступ, как показано ниже:

export class AppComponent {
 componentsMap = {
   1: UsersComponent,
   2: ProductsComponent,
   3: CategoriesComponent,
 };


 activeTab: '1' | '2' | '3' = '1';


 constructor() {}


 createComponent() {
   console.log(this.componentsMap[this.activeTab]) // UsersComponent class


 }
}

Теперь, когда у нас есть доступ к классу компонента, рендеринг компонента в Angular не так прост, как в React. В Angular мы не можем просто создать экземпляр класса, как в React. Вместо этого нам нужен класс, который имеет доступ к контейнеру, в котором должны отображаться компоненты. Например, если мы хотим отображать эти компоненты в определенном ‹div›, нам нужен доступ к этому ‹div›. > в качестве контейнера и внедрить в него класс с помощью механизмов Angular.

Для этого мы можем создать директиву в Angular. Эта директива может принимать нужный класс компонента в качестве входных данных и встраивать его в любой контейнер, где директива добавляется, как показано ниже:

@Directive({
 selector: '[appDynamicChildLoader]',
})
export class DynamicChildLoaderDirective implements OnInit {


 @Input('dynamicComponent') component: any;




 constructor(private viewContainerRef : ViewContainerRef) { }


 embedComponent() {
   this.viewContainerRef.clear();
   this.viewContainerRef.createComponent(this.component);
 }


 ngOnInit(): void {
   this.embedComponent();
 }
}

Как видите, мы создали директиву с именем `dynamicChildLoader`. Используя декоратор `@Input`, мы можем указать входной параметр для этой директивы. Этот параметр представляет класс, который мы хотим отправить. Обратите внимание, что в настоящее время класс имеет тип any, но мы можем улучшить его, назначив интерфейс, чтобы все встроенные компоненты имели одинаковую структуру. Тем не менее, он по-прежнему работает без него.

Кроме того, в конструкторе директивы мы внедрили службу ViewContainerRef. Это магия Angular, о которой я упоминал ранее. Этот сервис позволяет нам создать экземпляр экземпляра класса и встроить его в текущий контейнер, используя `createComponent`. Мы также внедрили метод `embedComponent`, который сначала очищает контейнер, чтобы гарантировать, что мы не добавляем дополнительные компоненты, поскольку мы продолжаем. Затем он вызывает метод `createComponent` и передает полученный класс в качестве аргумента, эффективно создавая и внедряя нужный компонент.

Теперь, когда у нас есть эта директива, мы можем легко включить ее в любой желаемый контейнер, выполнив следующие действия:

Добавьте директиву в элемент контейнера:

<div class="tabs-container" 
appDynamicChildLoader 
[dynamicComponent]="componentsMap[activeTab]" >
</div>

Как показано выше, в ‹div›, куда мы собираемся встроить наши динамические компоненты, мы просто включаем `appDynamicChildLoader` и передайте нужный класс компонента в качестве параметра.

Таким образом, директива позаботится о динамическом отображении указанного компонента в указанном контейнере.

Чтобы отобразить другие компоненты, мы можем просто обновить значение свойства activeTab. Как упоминалось ранее, для этого свойства можно задать значение 1, 2 или 3. для правильной ссылки на соответствующие компоненты.

Например, если мы хотим изменить его на 2, контейнер сначала очистит свое содержимое, а затем встроит. новый компонент в него. Это позволяет нам динамически переключаться между различными компонентами путем обновления свойства activeTab.

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

И снова мы можем использовать возможности Angular для решения этой проблемы. Как было показано ранее, когда мы используем метод `createComponent ` для встраивания нужного компонента в наш контейнер, он фактически возвращает экземпляр компонента, который мы только что создали. Это означает, что теперь у нас есть прямой доступ к экземпляру из файла TypeScript, а не из шаблона HTML.

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

Для этого нам нужно внести несколько изменений в структуру наших компонентов. Начнем с нашего AppComponent, где мы определили `componentsMap`. В этом компоненте нам нужно изменить `componentsMap`, чтобы он не только содержал класс компонента, но и включал данные, которые мы хотим связать с ним. Как показано ниже:

this.componentsMap = {
     1: {
       data: {
         pageTitle: 'Users',
         onClick: this.onClick,
       },
       component: UsersComponent,
     },
     2: {
       data: {
         pageTitle: 'Products',
         onClick: this.onClick,
       },
       component: ProductsComponent,
     },
     3: {
       data: {
         pageTitle: 'Categories',
         onClick: this.onClick,
       },
       component: CategoriesComponent,
     },
   };

Как видите, вместо того, чтобы назначать свойству только класс компонента, мы изменили его, назначив объект-контейнер. Этот объект-контейнер имеет свойство компонента, которое содержит класс компонента, и свойство данных, которое содержит данные для передачи компоненту.

Теперь, когда у нас есть объект и его данные, хранящиеся в одном месте, поскольку мы передаем этот объект в нашу директиву, нам также необходимо внести дополнительные изменения в нашу директиву, как показано ниже:

import { ComponentRef, Directive, Input, OnInit, ViewContainerRef } from '@angular/core';


export interface EmbeddedComponent {
 data : any;
}


export interface DynamicComponent {
 data: any;
 component : any & EmbeddedComponent;
}


@Directive({
 selector: '[appDynamicChildLoader]',
})
export class DynamicChildLoaderDirective implements OnInit {


 @Input('dynamicComponent') dynamicComponentContainer: DynamicComponent = {} as DynamicComponent;




 constructor(private viewContainerRef : ViewContainerRef) { }


 embedComponent() {
   this.viewContainerRef.clear();
   const createdComponent : ComponentRef<EmbeddedComponent> =  this.viewContainerRef.createComponent(this.dynamicComponentContainer.component);
   const componentInstance: EmbeddedComponent = createdComponent.instance;
   componentInstance.data = this.dynamicComponentContainer.data;
 }


 ngOnInit(): void {
   this.embedComponent();
 }
}

Как вы можете видеть выше, мы внесли некоторые изменения в нашу директиву, чтобы принимать как компонент, так и свойства данных внутри полученного объекта. Прежде всего, мы добавили два новых интерфейса для представления наших ожиданий и взаимодействий. Первый интерфейс представляет встроенный компонент, который является фактическим классом компонента, который мы будем визуализировать. Теперь мы гарантируем, что все отображаемые компоненты имеют свойство данных. Другой интерфейс представляет полученный объект, переданный из нашего компонента приложения. Как видите, оно включает свойство data с типом «любой» или «неизвестный», так как мы не знаем структуру данных, которые хотим отправить на данный момент. Однако вы можете улучшить это, взяв за основу структуры данных вашего компонента.

Кроме того, мы изменили имя свойства параметра, чтобы сделать его более понятным. Теперь он отражает, что представляет как компонент, так и его данные, поэтому мы переименовали его в «dynamicComponentContainer».

В методе embedComponent мы извлекли экземпляр компонента из метода createComponent, как упоминалось ранее. . Мы также передали наши интерфейсы, чтобы предотвратить ошибки TypeScript. Наконец, мы передали свойство данных экземпляру, гарантируя, что у него есть свойство данных, поскольку ранее мы передали интерфейс EmbededComponent.

Теперь, как часть нового изменения, мы добавили несколько дополнительных функций в наши встроенные компоненты, чтобы обеспечить правильную передачу данных. Как показано ниже:

export class ProductsComponent implements OnInit, EmbeddedComponent {
 data: any = {};
 pageTitle = '';
 onClick = () => {};


 ngOnInit(): void {
   this.pageTitle = this.data['pageTitle'];
   this.onClick = this.data['onClick'];
 }
}

Мы видим, что наш компонент теперь расширяет тот же интерфейс, который используется в нашей директиве. Кроме того, мы добавили новые свойства для представления полученных данных. Чтобы обеспечить ясность, мы также присвоили эти свойства соответствующим значениям из полученного объекта данных, которые использует компонент.

Теперь мы можем с уверенностью сказать, что наши компоненты отображаются динамически, даже с привязкой данных. Мы можем ясно видеть разницу между нашим подходом и обычным подходом ngSwitch. Просто добавив новый компонент к объекту, мы можем включить его без каких-либо трудностей. Это устраняет необходимость в дополнительных условиях или расширении строк нашего HTML-файла. Кроме того, стоит отметить, что HTML-файл нашего компонента состоит всего из одной строки, что значительно повышает удобство чтения и обслуживания.

Однако важно учитывать, что использование этого подхода иногда может сделать наш код более сложным, а не упростить. Поэтому нам нужно тщательно оценить, где его использовать, особенно для более сложных примеров, так как это может вызвать определенные трудности:

  • Уменьшенная видимость шаблона: поскольку рендеринг компонентов происходит динамически в директиве, в шаблоне может быть менее очевидно, какие компоненты рендерятся и их конкретные конфигурации. Это может затруднить понимание структуры компонентов с первого взгляда.
  • Потенциальное влияние на производительность. Динамическое создание и удаление компонентов во время выполнения может привести к снижению производительности, особенно при частых изменениях отображаемых компонентов. Важно оптимизировать производительность в сценариях с большим количеством динамически визуализируемых компонентов.
  • Ограниченное взаимодействие на основе шаблонов: поскольку компоненты визуализируются динамически, может быть сложнее установить взаимодействие между компонентами на основе шаблонов, например, привязки событий или ссылки на шаблоны. Возможно, вам придется больше полагаться на программные взаимодействия или общение через сервисы для достижения желаемой интерактивности.
  • Сложность отладки. Отладка динамически визуализируемых компонентов может быть более сложной, поскольку структура компонента не так явно определена в основном шаблоне. Могут потребоваться дополнительные усилия для отслеживания проблем и определения конкретных задействованных компонентов.
  • Эти соображения подчеркивают компромиссы и потенциальные проблемы, связанные с подходом динамического загрузчика дочерних элементов. Крайне важно тщательно оценить сложность вашего приложения и взвесить преимущества и недостатки, прежде чем внедрять этот подход.

Несмотря на потенциальные проблемы и недостатки, упомянутые ранее, важно отметить, что подход Динамический дочерний загрузчик по-прежнему может быть полезным инструментом в >множество сценариев. Хотя очень важно знать о возможных сложностях и компромиссах, не менее важно учитывать преимущества, которые это предлагает:

  • Гибкость и адаптируемость. Подход Dynamic Child Loader обеспечивает гибкость динамического рендеринга компонентов на основе меняющихся требований или взаимодействия с пользователем. Это позволяет вам обрабатывать динамические сценарии и соответствующим образом адаптировать пользовательский интерфейс вашего приложения.
  • Повторное использование кода и ремонтопригодность. Используя единую директиву или механизм для динамического рендеринга компонентов, вы можете добиться повторного использования и ремонтопригодности кода. Это может уменьшить дублирование кода и упростить обслуживание и обновление вашего приложения в долгосрочной перспективе.
  • Разделение задач: Dynamic Child Loader помогает отделить логику рендеринга компонентов от основного шаблона, способствуя более чистой структуре кода и лучшему разделению задач. Это может улучшить читабельность и ремонтопригодность вашей кодовой базы.
  • Улучшенный пользовательский интерфейс. Благодаря динамическому рендерингу компонентов вы можете обеспечить более интерактивный и персонализированный пользовательский интерфейс. Пользователи могут видеть компоненты, характерные для их контекста, или взаимодействовать с динамически визуализируемыми элементами в зависимости от их действий.
  • Настраиваемость и расширяемость. Подход Dynamic Child Loader обеспечивает настройку и расширяемость, позволяя легко добавлять новые компоненты или модифицировать существующие в зависимости от меняющихся требований. Он предлагает гибкость для адаптации и развития вашего приложения с течением времени.

В заключение, подход Динамический дочерний загрузчик может быть мощным методом для динамического рендеринга компонентов в приложениях Angular. Хотя он предлагает преимущества, такие как гибкость, возможность повторного использования кода и улучшенный пользовательский интерфейс, он также сопряжен с некоторыми проблемами, которые необходимо учитывать.

Сниженная видимость шаблона, потенциальное влияние на производительность, ограниченное взаимодействие на основе шаблона и сложность отладки — вот некоторые из проблем, связанных с этим подходом. Однако эти проблемы можно решить с помощью тщательного планирования, методов оптимизации и полного понимания требований приложения.

Крайне важно оценить сложность и конкретные потребности вашего приложения, прежде чем принимать решение об использовании подхода Динамический дочерний загрузчик. . Рассмотрите компромиссы, оцените преимущества по сравнению с проблемами и убедитесь, что подход соответствует общей архитектуре и целям вашего проекта.

Взвесив за и против и приняв обоснованные решения, вы сможете использовать Динамический дочерний загрузчик эффективный подход, обеспечивающий более динамичный и интерактивный пользовательский интерфейс, сохраняя при этом удобство сопровождения кода и гибкость вашего приложения Angular.

Социальное:

Линкедин

"Веб-сайт"