0

I create a lazyLoad Directive to handle lazy load component.

@Directive({
    selector: '[lazyComponent]',
})
export class LazyCompDirective<I, T extends LazyLoadedComponent<I>> implements OnInit, OnDestroy {
    private _inputs: I|null = null;
    private compRef: ComponentRef<T>|null = null;
    private readonly subscription = new Subscription();
    private type?: Type<T>;

    @Input('lazyComponent') public set component(type: Type<T>) {
        if (type) {
            this.compRef = this.viewContainerRef.createComponent(type);
            this.refreshInputs(this._inputs);
            this.type = type;
        }
    }

    @Input() public set inputs(data: I) {
        if (this.compRef) {
            this.refreshInputs(data);
            (this.compRef).hostView.detectChanges();
        } else {
            this._inputs = data;
        }
    }

    public constructor(private readonly viewContainerRef: ViewContainerRef) {}

    ngOnInit(): void {
        if (this.type) {
            this.compRef = this.viewContainerRef.createComponent(this.type);
            this.refreshInputs(this._inputs);
        }
    }

    public ngOnDestroy() {
        this.compRef = null;
        this.subscription.unsubscribe();
    }

    private refreshInputs(inputs: I | null) {
        if (inputs) {
            Object.keys((inputs)).forEach(inputName => {
                // @ts-expect-error: skip error
                (this.compRef as ComponentRef<unknown>).instance[inputName] = inputs[inputName];
            });
        }
    }

    static ngTemplateContextGuard<T extends LazyLoadedComponent<I>, I>(_dir: LazyCompDirective<I, T>, _context: unknown): _context is LazyLoadedComponent<I> {
        return true
    }

    static ngTemplateGuard_inputs<T extends LazyLoadedComponent<I>, I>(_dir: LazyCompDirective<I, T>, _state: I): _state is I {
        return true;
    }
}

so we can use this property as

<ng-template [lazyComponent]="component" [inputs]="componentInput"></ng-template>

We define this interface so we can always have a strong type with the directive

export interface LazyLoadedComponent<I> {
    inputs?: I;
}

to load the component we need to implement this interface

@Component({
    selector: 'lazy-component',
    template: '<div>I'm Lazy</div>',
})
export class LazyComponent implements LazyLoadedComponent<LazyComponentInput>, OnInit {
    public inputs?: LazyComponentInput;

    public constructor(
        
    ) {}

    public ngOnInit(): void {}
}

LazyComponentInput

export interface LazyComponentInput{
a: string,
b: number,
}

I found that the [inputs] don't really check whether the type is valid or not.

I feel like my type check guard is wrong but I couldn't figure out how should I check the type. Does anyone know how should I fix the type-checking?

moussesj94
  • 485
  • 1
  • 8
  • 27
  • You don't have any constraints on the `I` type, so it could be anything. If anything, the type `T` is constrained what you send as `[inputs]`, so it should affect the type of `[frLazyComponent]`, but you don't seem to assign to that. Shouldn't you just name it `'lazyComponent'`? – ShamPooSham Apr 13 '23 at 07:41
  • @ShamPooSham yes that is a typo when I copy the code to here – moussesj94 Apr 14 '23 at 04:18

0 Answers0