Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2: Change detection inside observable

Just I tried my first app with Angular2 RC3 (Using Angular-CLI) and I'm lost with this...

I have a problem with "Change detection" of variable word. I update the word variable inside the subscribe method of Observable, but no changes detected.

app.component.ts

import { Component, Inject, OnInit } from '@angular/core';
import { VoiceRecognitionService } from './voice-recognition.service';

@Component({
  moduleId: module.id,
  selector: 'app-root',
  template: `<h1>{{word}}</h1>`, // => No prints any update
  providers: [VoiceRecognitionService],
  styleUrls: ['app.component.css']
})
export class AppComponent implements OnInit {

  private voice: VoiceRecognitionService;
  public word: string = '';

  constructor( @Inject(VoiceRecognitionService) voiceRecognition: VoiceRecognitionService) {
    this.voice = voiceRecognition;
  }

  ngOnInit() {
    this.voice.record('ca')
      .subscribe(word => {
        console.log(word); // => Is printing well the new word
        this.word = `${word}...`; // => No changes detected
      });
  }
}

I remember in Angular 1 the use of $scope.$apply for similar cases and I search the same for Angular2 and I found NgZone.run, I tried to execute inside NgZone.run, but nothing.

What I'm doing wrong?

Thank you so much.


Extra:

I share my service with the Observable:

voice-recognition.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';

export interface IWindow extends Window {
  webkitSpeechRecognition: any;
}

@Injectable()
export class VoiceRecognitionService {

  constructor() {
    /* void */
  }

  /**
   * Record
   * @param {string} language - Language of the voice recognition
   * @returns {Observable<string>} - Observable of voice converted to string 
   */
  record(language: string): Observable<string> {
    return Observable.create((observer) => {
      const { webkitSpeechRecognition }: IWindow = <IWindow>window;
      const recognition = new webkitSpeechRecognition();

      recognition.onresult = (e) => observer.next(e.results.item(e.results.length - 1).item(0).transcript);
      recognition.onerror = observer.error;
      recognition.onend = observer.completed;

      recognition.continuous = true;
      recognition.interimResults = true;
      recognition.lang = language;
      recognition.start();
    });
  }
}
like image 851
Aral Roca Avatar asked Dec 24 '22 04:12

Aral Roca


1 Answers

I assume the webkitSpeechRecognition API is not patched by Angulars zone.

To work around use zone.run(...) to force the execution back into Angulars zone explicitely:

@Injectable()
export class VoiceRecognitionService {

  constructor(private zone:NgZone) {
    /* void */
  }

  /**
   * Record
   * @param {string} language - Language of the voice recognition
   * @returns {Observable<string>} - Observable of voice converted to string 
   */
  record(language: string): Observable<string> {
    return Observable.create((observer) => {
      const { webkitSpeechRecognition }: IWindow = <IWindow>window;
      const recognition = new webkitSpeechRecognition();

      recognition.onresult = (e) => this.zone.run(() => observer.next(e.results.item(e.results.length - 1).item(0).transcript));
      recognition.onerror = (e) => this.zone.run(() => observer.error(e));
      recognition.onend = (e) => this.zone.run(() => observer.completed(e));

      recognition.continuous = true;
      recognition.interimResults = true;
      recognition.lang = language;
      recognition.start();
    });
  }
}

If the callbacks need a different number of parameters ((e)) please adjust the code accordingly. I just assumed one parameter for each.

The advantage of this approach is that the users of this service don't have to take additional measures.

like image 85
Günter Zöchbauer Avatar answered Dec 28 '22 10:12

Günter Zöchbauer