angular 14 替换 ComponentFactoryResolver 实现动态创建组件

Fork Me On Github

angular 14 替换 ComponentFactoryResolver 实现动态创建组件

需求

移植弹出框到 angular 项目中。

方案

直接使用其他项目依赖

比较常用的有以下两个

  1. ngx-toastr

  2. ng-bootstrap

但是通过查看源码,发现都使用使用 ComponentFactoryResolver 来实现组件的新建,然后通过document.body.appendChild 添加到页面上的,

但是,angular 文档中提示,ComponentFactoryResolver 不推荐使用。

  
Deprecated: Angular no longer requires Component factories. Please use other APIs where Component class can be used directly.
Note: since v13, dynamic component creation via ViewContainerRef.createComponent does not require resolving component factory: component class can be used directly.
12

自己实现

没有了 ComponentFactoryResolver, 那么就使用 ViewContainerRef.createComponent

主要思路:

  1. 新建一个容器组件,获取到 ViewContainerRef

    ts
                  
    @Component({
     selector: 'app-dialog-container',
     template: '',
     styles: [''],
    })
    export class DialogContainerComponent {
    
     constructor(
         private service: DialogService,
         private viewContainerRef: ViewContainerRef,
     ) {
         this.service.containerRef = this.viewContainerRef;
     }
    }
    1234567891011121314
  2. 新建一个全局的服务提供者,

ts
                                                                
interface IDialogRef {
    id: any;
    element: ComponentRef<any>;
}

@Injectable({
    providedIn: 'root'
})
export class DialogService {
    private dialogItems: IDialogRef[] = [];
    public containerRef: ViewContainerRef;

    constructor(
        private injector: Injector,
    ) {

    }

    /**
     * 加载loading
     * @param option 
     * @returns loading 的id, 使用 remove(id: any) 进行关闭
     */
    public loading(option?: DialogLoadingOption): any {
        option = Object.assign({}, option, {
            time: 2000,
            closeable: true,
        });
        return this.createDailog(DialogLoadingComponent, option);
    }

    /**
     * 创建组件
     * @param component 
     * @param option 
     * @returns 
     */
    private createDailog<T>(component: Type<T>, option: any): any {
        const dialogId = ++ DialogService.guid;
        if (!this.containerRef) {
            return;
        }
        const dialogInjector = new DialogInjector(new DialogPackage(option, dialogId), this.injector);
        const dialogRef = this.containerRef.createComponent(component, {
            injector: dialogInjector
        });
        this.dialogItems.push({
            id: dialogId,
            element: dialogRef
        });
        return dialogId;
    }

    /**
     * 删除组件
     * @param i 
     */
    private removeAt(i: number) {
        const item = this.dialogItems[i];
        this.dialogItems.splice(i, 1);
        const dialogRef = item.element;
        dialogRef.destroy();
    }
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

作为对比,ViewContainerRef.createComponentComponentFactoryResolver 使用更简单,而且更符合 angular 特色

ts
                   
constructor(
        private resolver: ComponentFactoryResolver,
        private applicationRef: ApplicationRef,
        private injector: Injector, 
        @Inject(DOCUMENT) private document: Document,
    ) { }


// 添加组件
const dialogFactory = this.resolver.resolveComponentFactory(component);
const dialogRef = dialogFactory.create(dialogInjector);
this.applicationRef.attachView(dialogRef.hostView);
this.document.body.appendChild(dialogRef.location.nativeElement);


// 删除组件
this.applicationRef.detachView(dialogRef.hostView);
this.document.body.removeChild(dialogRef.location.nativeElement);
this.dialogItems.splice(i);
12345678910111213141516171819
  1. 在组件内部实现删除功能
ts
             
export class DialogMessageComponent implements OnDestroy {
    constructor(
        private data: DialogPackage<DialogMessageOption>,
        private service: DialogService,
    ) {

    }

    @HostListener('click')
    public close() {
        this.service.remove(this.data.dialogId);
    }
}
12345678910111213
  1. 以模块的方式提供
ts
                     
@NgModule({
    imports: [
        CommonModule,
    ],
    declarations: [
        ...COMPONENTS
    ],
    exports: [
        ...COMPONENTS
    ],
})
export class DialogModule {
    static forRoot(): ModuleWithProviders<DialogModule> {
        return {
            ngModule: DialogModule,
            providers: [
                DialogService
            ]
        };
    }
}
123456789101112131415161718192021
  1. 使用

第一步,在 app.module.ts 中导入

ts
 
DialogModule.forRoot(),
1

第二步,在 app.component.ts 页面中添加容器

html
 
<app-dialog-container></app-dialog-container>
1

第三步,使用

ts
    
constructor(
    private toastrService: DialogService) {
    this.toastrService.loading();
}
1234

总结

ViewContainerRef.createComponentComponentFactoryResolver 使用更简单,而且更符合 angular 特色,始终保持所有创建的内容在框架内。

完整代码请查看 Angular-ZoDream

点击查看全文
标签: angular
0 1221 1
在 angular 项目中实现对页面的访问控制
按下回车键,焦点移动到下一个表单或提交表单
使用ng-template 显示tree结构数据
angular 12使用 KaTex 显示 AsciiMath 格式的公式
ng-deep 使用