Angular 2 How to get Angular to detect changes made outside Angular?
Asked Answered
C

1

18

I am trying to create a simple example project to test the angular 2 change detection mechanism: I create a pure javascript object in script tags on the main index page. it contains the following:

        var Tryout = {};
        Tryout.text = "Original text here";
        Tryout.printme = function(){
            console.log(Tryout.text);
        }
        Tryout.changeme = function(){
            Tryout.text = "Change was made";
        }

One function to console log it, and one to change the text property.

Now in Angular 2 the code looks like this:

import {Component} from "angular2/core"

@Component({
    selector: 'my-app',
    template: `
        <h1>{{TryoutRef.text}}</h1>
        <input type="text" [(ngModel)]="TryoutRef.text">
        <button (click)="consoleLogMe()">Console Log</button>
        <button (click)="changeMe()">Change me inside</button>
    `
})

export class MyApp{

    TryoutRef:any = Tryout;
    constructor(){
    }
    changeMe(){
        this.TryoutRef.changeme();
    }
    consoleLogMe(){
        console.log(this.TryoutRef.text);
    }

}
declare var Tryout:string;

What I am trying to do is this: When I call the function Tryout.printme() normally with onclick (Completely outside of angular) I want angular to detect the change and update the screen.

I succeeded to this point: When I call Tryout.printme() from the component (the changeme() function is calling Tryout.printme()), Angular detects the change and updates the UI which is fine. Also, when I change from outside angular and I call consoleLogMe() from Angular it is logging the changed text and updates the UI.

I guess I need to execute Tryout.changeme() in the same Zone that Angular is running in somehow. Any ideas? I have a big project which is done in pure javascript/jquery, and now I slowly need to rewrite the handlebar templates to angular2 components without touching the model (yet). For that I need to force the model to execute in the same zone as angular.

If I wanted to do something like this in Angular 1 I would just $scope.$apply it would work.

Here is a gif from the example:

enter image description here

Coprolite answered 20/12, 2015 at 13:50 Comment(1)
Why is Tryout type string in the component file?Dogmatize
I
11

You can do this by exporting NgZone inside your Angular app. Usually, you should do all things inside Angular, but if you really want to execute your logic out of Angular, you need to get the right zone, as you have said.

This trick is abusing Angular's dependency injection and hooking the injected zone on window object, as this issue shows. Declaring a dependency on NgZone, and assigning it to window.zoneImpl for exporting.

//our root app component
import {Component, NgZone} from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>

    </div>
  `,
})
export class App {
  constructor(zone: NgZone) {
    this.name = 'Angular2'
    window.app = this
    window.zoneImpl = zone
  }
}

After Angular bootstrapping, you should have a zoneImpl global variable. You can use the run method to kick Angular off.

zoneImpl.run(() => window.app.name = "new name!")

Live demo.

Incus answered 21/12, 2015 at 5:43 Comment(5)
Another fast and ugly hack is, after bootstrapping, use setTimeout to make Angular aware of your code. NgZone has patched this global function.Incus
Could you please check the plunkr (message": "Preview has expired or project does not exist.") its gone.Coprolite
A word of warning: make sure to (as demonstrated above) name your window.zoneImpl variable something other than window.zone. Apparently window.zone is used by Angular and if you write to it, it'll screw things up.Patmos
See also Günter's alternative approach: set up an event listener inside Angular (hence inside the Angular zone). Then fire that event whenever you make changes outside the Angular zone.Essene
I just inject NgZone into all my components and wrap all external callbacks into ngZone.run(() => { doSomething(); });.Diversion

© 2022 - 2024 — McMap. All rights reserved.