0

I have a segment of code where I am getting some weird output. The parameter being used in the function is changing when I would not think it would.

entry point to the code.

handleAction(action : HAction){
    this.openForm("marksForm","Form");
  }

method to open the form.

public openForm(name : string, type : string){
    console.log("Name",name)
    let cb = this.createComponentInitCallback(this.compService.getComponentType(type),
                                                name);
    let itemconfig ={
        type: 'row',
        content: [{
          type: 'component',
          title: 'Form Test',
          componentName: 'h-form',
          componentState: {}
        }]
      }
      let tryRegister = false;
    try{
      this.goldenLayout.getComponent(name);
    }catch(e){console.log("registering component",name); tryRegister=true;}
    if(tryRegister)
      this.goldenLayout.registerComponent(name,cb);
    if(this.goldenLayout.root.contentItems[0])
      this.goldenLayout.root.contentItems[ 0 ].addChild(itemconfig);
    else
      this.goldenLayout.root.addChild(itemconfig);
  }

This method creates the defined callback function.

public createComponentInitCallback(componentType: Type<any>, name : string ): ComponentInitCallback {
    console.log("1Name",name);
    let f = (container: GoldenLayout.Container, componentState: any) => {
      console.log("2Name",name);
      this.ngZone.run(() => {
        console.log("3Name",name);
        // Create an instance of the angular component.
        const factory = this.componentFactoryResolver.resolveComponentFactory(componentType);
        const injector = this._createComponentInjector(container, componentState);
        const componentRef = this.viewContainer.createComponent(factory, undefined, injector);
        console.log("4Name",name)
        componentRef.instance.name=name;
        // Bind the new component to container's client DOM element.
        container.getElement().append($(componentRef.location.nativeElement));


        this._bindEventHooks(container, componentRef.instance);

        // Store a ref to the compoenentRef in the container to support destruction later on.
        (container as any)[COMPONENT_REF_KEY] = componentRef;
      });
    };

    return f;
  }

You will see my log statements. This callback gets executed inside the GoldenLayout library. However, I was pretty sure this should work.

Below are the outputs:

Name marksForm

1Name marksForm

2Name h-form

3Name h-form

4Name h-form

The first console output is logging what is passed into this method. You can see that it is obviously changing on me so I have to be doing something wrong. Oddly enough, the componentType parameter is working perfectly fine.

What am I doing wrong here?

Mark
  • 911
  • 13
  • 30

2 Answers2

2

A function that relies on lexical this and is supposed to be passed as callback should always be bound to the context.

createComponentInitCallback method can be bound to the context, either with bind or an arrow function (see this explanation on bound prototype methods vs arrow instance methods):

constructor() {
   this.createComponentInitCallback = this.createComponentInitCallback.bind(this);
}

Or resulting callback can be bound to the context:

let cb = this.createComponentInitCallback(this.compService.getComponentType(type),
                                            name).bind(this);

Considering there are no scenarios where this should differ from current class instance, the first option is preferable.

As for function scope, it cannot be lost under no circumstances. If name was passed as an argument in parent function, it will remain unchanged in nested function.

Community
  • 1
  • 1
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • sorry, maybe I didn't describe it well. `this` binding is not the problem. I changed it to use what @Mehdi said and used the suggestion by you. The problem is the `name` argument to the `createComponentInitCallback` method is change. See my `console.log()` satements and the output below it. All the code segments are in the same typescript class. – Mark Dec 16 '17 at 01:01
  • I see. `self = this` that was recommended in another answer is absolutely redundant with arrows. It's either one or another. It's unclear how the thing is working in your case. The code that causes the problem wasn't posted. Consider providing http://stackoverflow.com/help/mcve . `name` won't change magically. If it behaves like that you can be sure that there is `openForm("h-form", ...)` or `createComponentInitCallback(..., "h-form")` call somewhere. – Estus Flask Dec 16 '17 at 01:12
  • agreed on the magically. I just don't understand how another call like you state could possible change the `name` parameter. Maybe if I understand how 1 can impact the other I can do more debugging. Since I am using several libraries creating a plunkr will be tough. If you know how this can happen that will help. Otherwise, I will get started on trying to create a plunkr. – Mark Dec 16 '17 at 01:17
  • Since a scope (I assume that you meant lexical scope with `name` variable) cannot be physically lost in JS (unless the callback is stringified and `eval`ed, which is highly unlikely), this narrows down the possible options. Try to set up breakpoints in places where openForm and createComponentInitCallback are called, for starters. – Estus Flask Dec 16 '17 at 01:21
  • thanks for the pointers. Tracing with the debugger step by step into the libraries, show that the input on the `ItemConfig` was being used as a key in a map where the value was the callback. so `h-form` is another component but since I had that hardcoded ( accident...never updated it ) it was referencing that callback instead of the one I was expecting. I knew it was something stupid. – Mark Dec 16 '17 at 02:15
0

Unless you are using Angular HttpModule to make calls, any async call made with an external library will result in running your call back out of the original scope. To mitigate this you need to assign this to a local variable the callback can use.

public createComponentInitCallback(componentType: Type<any>, name : string ): ComponentInitCallback {
    console.log("1Name",name);
    let self = this;
    let f = (container: GoldenLayout.Container, componentState: any) => {
      console.log("2Name",name);
      this.ngZone.run(() => {
        console.log("3Name",name);
        // Create an instance of the angular component.
        const factory = self.componentFactoryResolver.resolveComponentFactory(componentType);
        const injector = self._createComponentInjector(container, componentState);
        const componentRef = self.viewContainer.createComponent(factory, undefined, injector);
        console.log("4Name",name)
        componentRef.instance.name=name;
        // Bind the new component to container's client DOM element.
        container.getElement().append($(componentRef.location.nativeElement));


        self._bindEventHooks(container, componentRef.instance);

        // Store a ref to the compoenentRef in the container to support destruction later on.
        (container as any)[COMPONENT_REF_KEY] = componentRef;
      });
    };

    return f;
  }
Mehdi
  • 2,263
  • 1
  • 18
  • 26
  • `this` works fine, its the name argument of the method that is not working. It is changing. Any way to get that to stick in the callback? I can't change the function signature because that's what is required for the library. Just in case I did change it and I get the same output. – Mark Dec 16 '17 at 00:41