简体   繁体   中英

Angular2 dependency injection: creating a reference to a property that is initially undefined

I'm trying to use Angular2 dependency injection to get a reference to a "Webgl2RenderingContext", which is undefined until I call the createContext function. Is there way to inject the reference into a several different service classes and then set the value after the HTMLCanvasElement is available?

@Injectable()
export class RenderContext {   
    get context() { return this.render_context; };

    constructor() { };

    createContext(canvas: HTMLCanvasElement) {
        this.render_context = canvas.getContext("webgl2");
    };

    private render_context: WebGL2RenderingContext;
}

let get_context = () => {
    return (render_context: RenderContext) => {
        return render_context.context;
    }
}

export const webgl2 = new OpaqueToken("webgl2");

export const webgl2_providers = [
    RenderContext,
    {
        provide: webgl2,
        useFactory: get_context(),
        deps: [RenderContext]
    }
];

I think this is a good case for Observables, and probably in your case a Subject from RxJS, since it allows sending to multiple observers through one emit call. In the below code I use a ReplaySubject because it lets you subscribe to the subject after the context has been sent and still get the most resent value (ie you don't have to make sure all your subscriptions are in place before creating the context, and you don't have to change code on if context has already been created or not).

@Injectable()
export class RenderContext {  
    //import from 'rxjs/ReplaySubject'  Provide Observable functionality to
    //multiple observers, and will replay last-set value so those that subscribe
    //after context is set still get the context.
    private __contextSubject : ReplaySubject = new ReplaySubject(1);

    private render_context: WebGL2RenderingContext;

    constructor() { };

    //not using property getter now since returning Observable, not actual value
    getContext() : Observable {
         return this.__contextSubject;
    };

    //send value to subscribers
    private emitContext() {
        this.__contextSubject.next(this.render_context);
    }

    //after storing context, also emit it to subscribers
    createContext(canvas: HTMLCanvasElement) {
        this.render_context = canvas.getContext("webgl2");
        this.emitContext();
    };

}

Then you would get the context by injecting the RenderContext directly (instead of going through a factory):

class OtherComponent {
    constructor(renderContext: RenderContext) {
        renderContext.getContext().subscribe(
            (context: WebGL2RenderingContext) => {
                //do what you need to do with the context...
            }
        )
    }
}

One solution I found was creating a ReflectiveInjector as a property of the MainCanvas component, adding a single provider for the initialized value of "WebGL2RenderingContext" after it is created, then creating a child injector with all the other classes that depend on the context:

export class MainCanvas {
    @ViewChild("canvas") canvas_ref: ElementRef;

    private gl: WebGL2RenderingContext;
    private context_injector: ReflectiveInjector;
    private scene_renderer: SceneRenderer;

    constructor(private render_context: RenderContext) {};

    ngAfterViewInit() {
        this.render_context.createContext((<HTMLCanvasElement>this.canvas_ref.nativeElement));
        this.gl = this.render_context.context;

        if (this.gl) {

            let gl_provider = [{ provide: webgl2, useValue: this.gl }];

            this.context_injector = ReflectiveInjector.resolveAndCreate(gl_provider)
                .resolveAndCreateChild([SceneRenderer, cube_providers, shader_providers]);

            this.scene_renderer = this.context_injector.get(SceneRenderer);
        }
    }
};

Edit: It also works without making a child injector:

this.context_injector = ReflectiveInjector.resolveAndCreate([gl_provider, SceneRenderer, cube_provider, shader_providers]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM