7

I want to be able to watch and update when a variable outside of angular2 changes. So let's say I have this in an external javascript file:

var test = 1;

How can I bind this variable to a property in a component?

@Component({
   ...
})

export class MyComponent {
    watchy = window.test;
}

Apparently this should just work, according to this answer.
But it doesn't. If I change the variable in the console, the variable does not update displayed in a template. Am I missing something?

Community
  • 1
  • 1
Sebastian Olsen
  • 10,318
  • 9
  • 46
  • 91

3 Answers3

19

Angular only runs change detection when an async executed function completes. The function needs to be run inside Angulars zone for angular to recognize the async operation.

Because your variable is changed from outside Angulars zone. Angular doesn't run change detection.

You need to invoke change detection manually for Angular to recognize the changed variable. See also Triggering Angular2 change detection manually

If you for example can dispatch an event instead of just setting a variable, you can listen to the event.

window.dispatchEvent(new CustomEvent('test', {detail: 'newValue'}));
@Component({
   ...
})
export class MyComponent {
    @HostListener('window:test', ['$event'])
    testListener(event) {
      this.watchy = event.detail;
    }
}

Invoked event handlers automatically invoke Angulars change detection, therefore there is nothing more to do.

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • app/app.component.ts(96,12): error TS2339: Property 'watchy' does not exist on t ype 'AppComponent'. – Ravinder Payal May 12 '16 at 09:30
  • I guess this is a linter warning. Just declare the property on the class and you should be good. – Günter Zöchbauer May 12 '16 at 09:33
  • i am new and from last two days i am getting these errors. when i tried to deciare i got some other errors. piease what is right way to deciare – Ravinder Payal May 12 '16 at 09:36
  • adding `watchy:any;` to the class should do. If you get an error please create a new question because this is then more an IDE setup issue and not related to the question. – Günter Zöchbauer May 12 '16 at 09:38
  • After checking many times I succeeded somewhere but now got this error.. `app/app.component.ts(99,28): error TS2339: Property 'detail' does not exist on type 'Window'.` – Ravinder Payal May 12 '16 at 10:46
  • 1
    Sorry, that is a mistake in my answer. It should be `event.detail` and thanks for the hint. – Günter Zöchbauer May 12 '16 at 10:49
  • `event` doesn't have any property named `detail` i checked this by logging `event` variable in `console`! – Ravinder Payal May 12 '16 at 12:08
  • If you dispatch it as shown above, it should have one. See also https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events – Günter Zöchbauer May 12 '16 at 12:15
  • I exactly copied your code but detail object is not their.. it is `undefined` .... I am trying this from today morning and here its evening????? – Ravinder Payal May 12 '16 at 13:05
  • 1
    This code, as written, will only work for button/click/etc events. If you simply are trying to send a message (data) through an event, you now use `window.dispatchEvent(new CustomEvent('test', {detail: 'newValue'}));` Notice, it uses CustomEvent, not Event. The property `detail` is only available via CustomEvent. You can see this API and more details : https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent – Kim Gentes Oct 06 '16 at 21:02
  • Thanks a lot for the hint. Fixed. – Günter Zöchbauer Oct 07 '16 at 03:06
3

Apparently this should just work, according to this answer.

I'm not sure how you jumped to that conclusion, but no matter, there is a bigger issue with the code. This line of code

watchy = window.test;

Will create a component property that is a primitive type. When that line of code executes, watchy will be assigned the value 1. watchy, since it is a primitive, has no relationship with window.test after the assignment is made – it simply gets a copy of the window.test value for the assignment. So, if you then change the value of window.test, JavaScript won't update watchy, so Angular change detection isn't even a factor here.

If you want the component property to be linked to the global variable, you could wrap your primitive type inside an object:

var myObj = { test: 1}
export class MyComponent {
  watchy = window.myObj;
}

Now, watchy is a reference type, and it refers to the myObj object – it does not get a copy of the object, it just "points" to it. So, if you then change myObj.test, then watchy will "see" the new value, since it is still pointing to the myObj.test object. But Angular change detection won't notice if you change the value outside the Angular zone.

If you are displaying the value of test in a component template, you will need to change test inside the Angular zone in order for change detection to run and notice the change. Instead of repeating a bunch of code here, see Angular 2 How to get Angular to detect changes made outside Angular?


Günter's answer is another approach: set up an event listener inside Angular (hence inside the Angular zone), then fire that event whenever test changes.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
0

This will never work. You need to tell Angular2 to update the watchy, there are many ways to achive this, but it's a bit weird to have it in a global var outside of Angular2 app.

For example you can have the watchy in a function that will be triggered on a click event from an element within the component, something like :

@Component({
   template: '<div (click)="onClickEvent()"></div>{{watchy}}'
})

export class MyComponent {
    watchy = window.test;

   onClickEvent() {
    this.watchy = window.test;
  }
}

Then change the var, trigger the click event and it will work.

Tiberiu Popescu
  • 4,486
  • 2
  • 26
  • 38