Search code examples
javascriptangulartypescriptobservableangular2-changedetection

Angular2 Calling exposed method from outside app and loses change binding


I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.

app.component

constructor(private _public: PublicService,) {
        window.angular = {methods: this._public};
    }

PublicService

export class PublicService {

    constructor(
        private  _viewManager: ViewManagerComponent,
    ) {}

    CallMe(){
        this._viewManager.renderView('page1')
    }
}

LayoutManagerComponent

@Component({
    selector: 'view-manager',
    template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
    //This is the variable being watched
    view = "page";

    renderView = function(type){
        console.log(type)
        this.view = type;
        console.log(this.view)
    };
}

So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.

----Update - Still not working -------

export class ViewManagerComponent {
    constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {

    }
    view = "page";

     @Output() renderView(type){
        // type is 'page'
        console.log(this.view)
        this.zone.run(() => {
            // type is 'page'
            console.log(this.view)
            this.view = type;
            // type is 'page1'
            console.log(this.view)
        });
        // type is 'page1'
        console.log(this.view)
        //cdRef errors: 
        //view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
        this.cdRef.detectChanges();
    };

}

Solution

  • In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.

    Run change detection explicitely

    contructor(private cdRef:ChangeDetectorRef) {}
    
    someMethodCalledFromOutside() {
      // code that changes properties in this component 
      this.cdRef.detectChanges();
    }
    

    Run the code that modifies the components properties inside Angulars zone explicitely

    contructor(private zone:NgZone) {}
    
    someMethodCalledFromOutside() {
      this.zone.run(() => {
      // code that changes properties in this component 
      });
    }
    

    The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.

    If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.

    See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries

    update

    export class ViewManagerComponent {
        constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
          self = this;
        }
        view = "page";
    
         @Output() renderView(type){
            // type is 'page'
            console.log(self.view)
            self.zone.run(() => {
                // type is 'page'
                console.log(self.view)
                self.view = type;
                // type is 'page1'
                console.log(self.view)
            });
            // type is 'page1'
            console.log(self.view)
            self.cdRef.detectChanges();
        };
    
    }