Как загрузить компонент динамически, используя имя компонента в angular2?
В настоящее время я загружаю угловые компоненты динамически в моем приложении, используя следующий код.
export class WizardTabContentContainer {
@ViewChild('target', { read: ViewContainerRef }) target: any;
@Input() TabContent: any | string;
cmpRef: ComponentRef<any>;
private isViewInitialized: boolean = false;
constructor(private componentFactoryResolver: ComponentFactoryResolver, private compiler: Compiler) {
}
updateComponent() {
if (!this.isViewInitialized) {
return;
}
if (this.cmpRef) {
this.cmpRef.destroy();
}
let factory = this.componentFactoryResolver.resolveComponentFactory(this.TabContent);
this.cmpRef = this.target.createComponent(factory);
}
}
Здесь функция resolComponentFactory принимает тип компонента. Мой вопрос, есть ли способ загрузить компонент, используя строку имени компонента, например, у меня есть компонент, определенный как
export class MyComponent{
}
Как я могу добавить вышеупомянутый компонент, используя строку имени компонента "MyComponent" вместо типа?
2 ответа
Возможно, это будет работать
import { Type } from '@angular/core';
@Input() comp: string;
...
const factories = Array.from(this.resolver['_factories'].keys());
const factoryClass = <Type<any>>factories.find((x: any) => x.name === this.comp);
const factory = this.resolver.resolveComponentFactory(factoryClass);
const compRef = this.vcRef.createComponent(factory);
где this.comp
это имя строки вашего компонента, как "MyComponent"
Чтобы сделать это работает с минификацией см.
Я знаю, что этот пост старый, но в Angular многое изменилось, и мне не очень понравилось ни одно из решений из-за простоты использования и безопасности. Вот мое решение, которое, надеюсь, вам понравится больше. Я не собираюсь показывать код для создания экземпляра класса, потому что эти примеры приведены выше, а исходный вопрос о переполнении стека уже показал решение и действительно спрашивал, как получить экземпляр класса из селектора.
export const ComponentLookupRegistry: Map<string, any> = new Map();
export const ComponentLookup = (key: string): any => {
return (cls) => {
ComponentLookupRegistry.set(key, cls);
};
};
Поместите указанные выше декоратор машинописного текста и карту в свой проект. И вы можете использовать это так:
import {ComponentLookup, ComponentLookupRegistry} from './myapp.decorators';
@ComponentLookup('MyCoolComponent')
@Component({
selector: 'app-my-cool',
templateUrl: './myCool.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyCoolComponent {...}
Далее, и это важно, вам нужно добавить свой компонент в entryComponents
в вашем модуле. Это позволяет вызывать декоратор Typescript во время запуска приложения.
Теперь в любом месте вашего кода, где вы хотите использовать динамические компоненты (например, несколько из приведенных выше примеров), когда у вас есть ссылка на класс, вы просто получаете ее со своей карты.
const classRef = ComponentLookupRegistry.get('MyCoolComponent');
// Returns a reference to the Class registered at "MyCoolComponent
Мне очень нравится это решение, потому что ваш КЛЮЧ, который вы регистрируете, может быть селектором компонентов или чем-то еще, что важно для вас или зарегистрировано на вашем сервере. В нашем случае нам нужен был способ, чтобы наш сервер мог сообщить нам, какой компонент (по строке) загружать в панель управления.
Я искал решение, которое удовлетворяет требованиям Angular 9 для динамически загружаемых модулей, и придумал это
import {
ComponentFactory,
Injectable,
Injector,
ɵcreateInjector as createInjector,
ComponentFactoryResolver,
Type
} from '@angular/core';
export class DynamicLoadedModule {
public exportedComponents: Type<any>[];
constructor(
private resolver: ComponentFactoryResolver
) {
}
public createComponentFactory(componentName: string): ComponentFactory<any> {
const component = (this.exportedComponents || [])
.find((componentRef) => componentRef.name === componentName);
return this.resolver.resolveComponentFactory(component);
}
}
@NgModule({
declarations: [LazyComponent],
imports: [CommonModule]
})
export class LazyModule extends DynamicLoadedModule {
constructor(
resolver: ComponentFactoryResolver
) {
super(resolver);
}
}
@Injectable({ providedIn: 'root' })
export class LazyLoadUtilsService {
constructor(
private injector: Injector
) {
}
public getComponentFactory<T>(component: string, module: any): ComponentFactory<any> {
const injector = createInjector(module, this.injector);
const sourceModule: DynamicLoadedModule = injector.get(module);
if (!sourceModule?.createComponentFactory) {
throw new Error('createFactory not defined in module');
}
return sourceModule.createComponentFactory(component);
}
}
Применение
async getComponentFactory(): Promise<ComponentFactory<any>> {
const modules = await import('./relative/path/lazy.module');
const nameOfModuleClass = 'LazyModule';
const nameOfComponentClass = 'LazyComponent';
return this.lazyLoadUtils.getComponentFactory(
nameOfComponentClass ,
modules[nameOfModuleClass]
);
}
Также возможен доступ через импорт:
import * as possibleComponents from './someComponentLocation'
...
let inputComponent = possibleComponents[componentStringName];
Затем вы можете создать экземпляр компонента, например:
if (inputComponent) {
let inputs = {model: model};
let inputProviders = Object.keys(inputs).map((inputName) => { return { provide: inputName, useValue: inputs[inputName] }; });
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
let injector: ReflectiveInjector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicInsert.parentInjector);
let factory = this.resolver.resolveComponentFactory(inputComponent as any);
let component = factory.create(injector);
this.dynamicInsert.insert(component.hostView);
}
обратите внимание, что компонент должен быть в @NgModule entryComponents
https://stackblitz.com/edit/angular-hzx94e
Вот как вы можете загружать угловые компоненты строкой. Это также будет работать для сборок Prod.
Кроме того, он позволяет вводить данные в каждый динамически загружаемый компонент.
Я пользователь, как это сделать, может быть вам полезен.
1. сначала определен класс, который использует в качестве компонента карты имен и класс RegisterNMC для карты имени модуля nmc
export class NameMapComponent {
private components = new Map<string, Component>();
constructor(components: Component[]) {
for (let i = 0; i < components.length; i++) {
const component = components[i];
this.components.set(component.name, component);
}
}
getComponent(name: string): Component | undefined {
return this.components.get(name);
}
setComponent(component: Component):void {
const name = component.name;
this.components.set(name, component);
}
getAllComponent(): { [key: string]: Component }[] {
const components: { [key: string]: Component }[] = [];
for (const [key, value] of this.components) {
components.push({[key]: value});
}
return components;
}
}
export class RegisterNMC {
private static nmc = new Map<string, NameMapComponent>();
static setNmc(name: string, value: NameMapComponent) {
this.nmc.set(name, value);
}
static getNmc(name: string): NameMapComponent | undefined {
return this.nmc.get(name);
}
}
type Component = new (...args: any[]) => any;
в файле ngMgdule вы должны поместить компоненты, которые динамически загружаются, в entryCompoent.
const registerComponents = [WillBeCreateComponent];const nmc = новый NameMapComponent(registerComponents); RegisterNMC.setNmc('компонент-демонстрация', nmc);
3. в контейнере компонент
@ViewChild('insert', {read: ViewContainerRef, static: true}) insert: ViewContainerRef;
nmc: NameMapComponent;
remoteData = [
{name: 'WillBeCreateComponent', options: '', pos: ''},
];
constructor(
private resolve: ComponentFactoryResolver,
) {
this.nmc = RegisterNMC.getNmc('component-demo');
}
ngOnInit() {
of(this.remoteData).subscribe(data => {
data.forEach(d => {
const component = this.nmc.getComponent(d.name);
const componentFactory = this.resolve.resolveComponentFactory(component);
this.insert.createComponent(componentFactory);
});
});
}
это нормально, надеюсь смогу вам помочь ^_^!