angular 16 动态生成组件

Fork Me On Github

angular 16 动态生成组件

需求

在页面中生成一个确认弹窗

新的实现方式

第一步,获取需要的工具

ts
          
@Injectable({
    providedIn: 'root'
})
export class DialogService {

    public containerRef: ViewContainerRef; //需要从Component中获取,然后传递进来
    constructor(
        private injector: Injector,
    ) { }
}
12345678910

ViewContainerRef 的获取方式

  1. 第一种在 Component 初始化时获取

    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. template 中通过 ng-container 获取

    ts
                        
    @Component({
     selector: 'app-dialog-container',
     template: '<ng-container #modalVC></ng-container>',
     styles: [''],
    })
    export class DialogContainerComponent implements AfterViewInit {
    
     @ViewChild('modalVC', {read: ViewContainerRef})
     private viewContainerRef: ViewContainerRef;
    
     constructor(
         private service: DialogService,
     ) {
    
     }
    
     ngAfterViewInit(): void {
         this.service.containerRef = this.viewContainerRef;
     }
    }
    1234567891011121314151617181920

主要区别:就是增加的元素节点为兄弟节点,所以,第一种添加的弹窗是在外面的,不受 app-dialog-container 中的样式影响;第二种是作为子元素添加的,受样式影响

第二步,实现动态生成组件

component 为组件的类名

ts
     
const dialogRef = this.containerRef.createComponent(component, {
    injector: this.injector
});

// dialogRef.instance 就是组件的实例 
12345

第三步,移除组件

ts
 
dialogRef.destroy();
1

如果需要在组件初始化,自动注入值或传值,则需要自定义 injector

ts
                             
import { InjectFlags, Injector, ProviderToken } from '@angular/core';

// 这个类是传值的载体
export class DialogPackage<T = any> {
    constructor(
        public data: T,
        public dialogId: any,
    ) {
    }
}

// 这是自定义的注射器
export class DialogInjector<T> implements Injector {

    constructor(
        private data: DialogPackage,
        private parentInjector: Injector
      ) {}

    get<T>(token: ProviderToken<T>, notFoundValue?: T, option?: InjectOptions): T;
    get(token: any, notFoundValue?: any);
    get(token: any, notFoundValue?: any, flags?: any): any {
        if (token === DialogPackage) {
            return this.data;
          }
          return this.parentInjector.get<T>(token, notFoundValue, flags);
    }

}
1234567891011121314151617181920212223242526272829

第二步需要修改

ts
      
const dialogInjector = new DialogInjector(new DialogPackage(option, dialogId), this.injector);
const dialogRef = this.containerRef.createComponent(component, {
    injector: dialogInjector
});

// dialogRef.instance 就是组件的实例 
123456

在组件中接收值

ts
         
export class DialogConfirmComponent {

    constructor(
        private data: DialogPackage<DialogConfirmOption>,
        private service: DialogService,
    ) {

    }
}
123456789

过时实现代码

第一步,获取需要的工具

ts
           
export class DialogService {

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


}
1234567891011

第二步,实现动态生成组件

component 为组件的类名

ts
       
const dialogFactory = this.resolver.resolveComponentFactory(component);
const dialogRef = dialogFactory.create(this.injector);
// 正式添加到程序和添加到页面上,才能显示
this.applicationRef.attachView(dialogRef.hostView);
this.document.body.appendChild(dialogRef.location.nativeElement);

// dialogRef.instance 就是组件的实例 
1234567

第三步,移除组件

ts
  
this.applicationRef.detachView(dialogRef.hostView);
this.document.body.removeChild(dialogRef.location.nativeElement);
12

如果需要在组件初始化,自动注入值或传值,则需要自定义 injector

ts
                             
import { InjectFlags, Injector, ProviderToken } from '@angular/core';

// 这个类是传值的载体
export class DialogPackage<T = any> {
    constructor(
        public data: T,
        public dialogId: any,
    ) {
    }
}

// 这是自定义的注射器
export class DialogInjector<T> implements Injector {

    constructor(
        private data: DialogPackage,
        private parentInjector: Injector
      ) {}

    get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
    get(token: any, notFoundValue?: any);
    get(token: any, notFoundValue?: any, flags?: any): any {
        if (token === DialogPackage) {
            return this.data;
          }
          return this.parentInjector.get<T>(token, notFoundValue, flags);
    }

}
1234567891011121314151617181920212223242526272829

第二步需要修改

ts
         
const dialogFactory = this.resolver.resolveComponentFactory(component);
// 这里的option 可以是任何值
const dialogInjector = new DialogInjector(new DialogPackage(option, dialogId), this.injector);
const dialogRef = dialogFactory.create(dialogInjector);
// 正式添加到程序和添加到页面上,才能显示
this.applicationRef.attachView(dialogRef.hostView);
this.document.body.appendChild(dialogRef.location.nativeElement);

// dialogRef.instance 就是组件的实例 
123456789

在组件中接收值

ts
         
export class DialogConfirmComponent {

    constructor(
        private data: DialogPackage<DialogConfirmOption>,
        private service: DialogService,
    ) {

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