Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

View is not updated on change in Angular2

Tags:

angular

I've started exploring Angular2 (I'm coming with Angular1 and a bit of React background) and I got stuck with a problem.

I want to bind certain keystrokes to actions in my component, so I've decided to use Angular2 lifecycle to bind/unbind actions.

However, if I do something from within a Mousetrap callback, it works, but it's not rendered and a change is not visible until a digest cycle is run.

Do I need to run something explicitly to update a view

Could somebody help me to figure out what is going on? Any help would be very appreciated.


import {Component} from 'angular2/core';
const Mousetrap = require('mousetrap');

@Component({
  template: `<div>
    Video template: Mode {{ mode }}
    <input type="number" [(ngModel)]="mode"/>
  </div>`
})
export class Video {

  public mode: number;

  constructor() {
    this.mode = 0;
  }

  ngOnInit() {

    console.log('hello Video component');
    Mousetrap.bind('d', () => console.log('this.mode=', this.mode));
    Mousetrap.bind('i', () => this.incrementMode()); // doesn't work

    this.incrementMode(); // works
    this.incrementMode(); // works
    setTimeout(() => this.incrementMode(), 4000); // works

  }

  incrementMode() {
    console.log('incMode', this.mode++);
  };

  ngOnDestroy() {
    console.log('bye bye Video component');
    Mousetrap.unbind(['d', 'i']);
  }

}
like image 616
Jan Vorcak Avatar asked Jan 04 '16 14:01

Jan Vorcak


2 Answers

Although @Günter's answer is absolutely correct I want to propose a different solution.

The problem with Mousetrap library is that it creates its instance outside of the angular zone (see here). But to fire change detection after each async event the instance should be instantiated inside the angular zone. You have two options to achieve this:

  1. Use dependency injection:
bootstrap(App, [provide(Mousetrap, { useFactory: () => new Mousetrap() }) ]);

// ...

@Component({
  selector: 'my-app', 
  // ...
})
export class App {
  constructor(@Inject(Mousetrap) mousetrap) {
    this.mousetrap = mousetrap;
    // ...
  }
  //...
}
  1. Just create instance of Mousetrap inside of the constructor:
@Component({
  selector: 'my-app', 
  // ...
})
export class App {
  constructor() {
    this.mousetrap = new Mousetrap();
    // ...
  }
  //...
}

In both cases you will have the ability to use the mousetrap instance like this:

ngOnInit() {
  this.mousetrap.bind('i', () => this.incrementMode()); // It works now!!!
  // ...
}

Now you don't need to use ngZone.run() in every bind call. In case of dependency injection you also can use this mousetrap instance in any component/service of your application (not only in App component).

See this plunk. I use dependency injection there.

like image 85
alexpods Avatar answered Oct 11 '22 14:10

alexpods


If MouseTrap is something outside Angular you might need to inject NgZone and run your code like

  Mousetrap.bind('i', () => this.ngZone.run(() => this.incrementMode()));
like image 20
Günter Zöchbauer Avatar answered Oct 11 '22 15:10

Günter Zöchbauer