神刀安全网

Angular 2 NgZone Intro – The new “scope.apply”

Adding youtube player and google sign-in features to the echoes player version that I started developed with angular 2 was almost a no brainer. Since I took few steps ahead in preparing the angular 1 version code to angular 2 , all left to do is copy and paste. However, issues started to rise once I started to check its functionality. Then I discovered NgZone.

Limitations Outside Angular 2 World

In angular 2, the change detection mechanism has been redesigned well enough to boost optimization and performance for several cases. This is all true when the app is running inside angular 2 world.

In Echoes Player , I had to use 2 features that required interacting with external services. By external I mean that, the interactions goes outside of the angular 2 scope:

  1. Youtube Player sends events from youtube domain to echoes player domain.
  2. Google Sign-in sends events from google auth domain to echoes player domain.

Upon each event that is received asynchronously , in Echoes player scope, a chain of reactions happen and updates internal state which is managed currently with ngrx/store.

However, since these events originates in an external source – another domain – angular 2 change detection doesn’t recognize that the data has changed. So, with angular 2, we’re still left with connecting these to angular’s change detection.

The Problems That Have Been Resolved By NgZone

I quickly noticed the problems with integrating the above external services to Echoes.

Whenever a change has been made following an external event, it wasn’t rendered unless I did a simple behavior (click) which invoked the change detection mechanism. Lets go through each problem and its solution.

The Youtube Player Problem: No UI Update

Echoes is integrated with youtube player iframe api . In order to use it, a player has to be created with the api:

// youtube-player.service.ts createPlayer (callback) {     const store = this.store;     const service = this;     const defaultSizes = this.defaultSizes;     return new window.YT.Player('player', {         height: defaultSizes.height,         width: defaultSizes.width,         videoId: '',         events: {             onReady: () => {},             onStateChange: onPlayerStateChange         }     }); } 

In this process, the api creates a player object which interacts with youtube’s domain. The actual video playing is played on youtube’s domain, so, the actual player (in youtube’s domain) notifies a change thru the “ onPlayerStateChange ” callback that is passed in the constructor of this player.

Echoes uses this events in order to update the app’s player state (using ngrx/store ) of the current state of the player, so it can, i.e, show/hide the pause/play buttons. This is the point where i started to see the problem. The buttons wouldn’t reflect the actual state of the player.

The Youtube Player Solution: NgZone

In order to notify angular change detection that there are changes which originated outside of the app, I has to use NgZone .

NgZoneis a service that can be used in order to execute work (functions/code) outside or inside the angular world. Thus, in the end of these operations, Angular’s change detection is triggered, and updates whatever is necessary. I see it very similar to angular 1 “ scope.apply “, however, more mature, optimized and performant.

In order to re-enter angular’s world, NgZone’s “ run ” function should take a function as the operation that is needed to be run, and then, the relevant changes will be rendered.

First I imported NgZone in “ youtube-player.service.ts “:

import { Injectable, NgZone } from '@angular/core'; 

Then, I injected it to its constructor:

constructor (public store: Store<any>, private zone: NgZone){   // now zone is available via:   // this.zone } 

Then, I updated the “ createPlayer ” function and wrapped the “ onPlayerStateChange ” callback with NgZone’s run function. Notice that I also used the es6/es2015 fat arrow in order to keep the “this” context so I can access the service’s zone:

createPlayer (callback) {     const store = this.store;     const service = this;     const defaultSizes = this.defaultSizes;     return new window.YT.Player('player', {         height: defaultSizes.height,         width: defaultSizes.width,         videoId: '',         events: {             onReady: () => {},             onStateChange: (ev) => this.zone.run(() => onPlayerStateChange(ev))         }     }); } 

The Google Sign-in Problem: No User’s Playlists

With this version of Echoes which is implemented with angular 2, I chose to experiment with google’s web sign-in strategy. I chose assigning a handler to a button with google’s api “gapi.auth2”.

The expected use case is:

  1. the user navigates to the “my playlists” screen
  2. then clicks the google sign-in button
  3. a pop up window in google’s domain opens with details to sign in
  4. the user authorizes sign-in to echoes player
  5. the pop-up is closed and the user is back to echoes player page
  6. echoes player gets access the user’s playlists
  7. the playlists should be rendered and the sign-in button should be hidden

The code which is responsible for steps 2-5 starts with assigning a click handler and listeners the the sign-in button:

// user-manager.service.ts attachSignIn() {   if (this.auth2 && !this.isSignedIn && !this.isAuthInitiated) {     this.isAuthInitiated = true;     // Attach the click handler to the sign-in button     this.auth2.attachClickHandler('signin-button', {}, this.onLoginSuccess.bind(this), this.onLoginFailed.bind(this));   } } 

Notice that although the “ success ” and “ fail ” functions are bind with “ this ” (service) context, still, these will be invoked asynchronously outside of angular’s world – so even, the “ bind ” function isn’t the answer to this one.

After the “ success ” callback is invoked, steps 6-7 should be invoked – however – with this implementation it won’t.

The Google Sign-in Solution: NgZone

Similar to the youtube player problem, the solution here is using NgZone’s “ run ” function. It is setup and injected in a similar manner to the “ user-manager.service.ts ” and defined as a private member. In this case, I created an expression using es6/es2015 fat arrow to reuse and simplify wrapping the relevant callbacks:

attachSignIn() {   // an experssion for reuse with "this.zone"   const run = (fn) => (r) => this.zone.run(() => fn(r));   if (this.auth2 && !this.isSignedIn && !this.isAuthInitiated) {     this.isAuthInitiated = true;     // Attach the click handler to the sign-in button with "run"     this.auth2.attachClickHandler('signin-button', {}, run(this.onLoginSuccess.bind(this)), run(this.onLoginFailed.bind(this)));   } } 

That’s it – problem is solved for this scenario.

What’s More With NgZone

NgZone has a lot more to offer. If there is a code that should be run, however shouldn’t affect the change detection, then the “ runOutsideAngular ” method should be used.

Moreover, NgZone emits some useful events like: onUnstable, onError and more.

Feel free to explore the code of Echoes Player with angular 2

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Angular 2 NgZone Intro – The new “scope.apply”

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址