Using WebSpeech API through AngularJS - javascript

I have been trying to use the WebSpeech Api through angularjs. Everything seems to work but the model doesn't get updated at once.
If I start the recognition again, the model updates. Seems like some inner loop/other construct is holding angular to see the changes.
Here is the codepen that I made.
Steps to reproduce:
1. Click start, and speak
2. After recognition detects end of speech hit start once again to start another recognition.
3. As soon as the second recognition is started, the model is updated with previous transcript.
Note: If a do console.log as below then it shows correct transcript, means the recognition part is working fine.
if(event.results[i].isFinal) {
self.final = self.final.concat(event.results[i][0].transcript);
console.log(event.results[i][0].transcript);
}

Everything seems perfect, except you forgot to call $scope.$apply(); when you modified values to get it effect on view. So it should be like this,
angular.module('speech',[]);
angular.module('speech').controller('speechController', function($scope) {
this.rec = new webkitSpeechRecognition();
this.interim = [];
this.final = '';
var self = this;
this.rec.continuous = false;
this.rec.lang = 'en-US';
this.rec.interimResults = true;
this.rec.onerror = function(event) {
console.log('error!');
};
this.start = function() {
self.rec.start();
};
this.rec.onresult = function(event) {
for(var i = event.resultIndex; i < event.results.length; i++) {
if(event.results[i].isFinal) {
self.final = self.final.concat(event.results[i][0].transcript);
console.log(event.results[i][0].transcript);
$scope.$apply();
} else {
self.interim.push(event.results[i][0].transcript);
}
}
};
});
I have updated your codepen with working solution.
AngularJs creates a "watch" internally for the all data-bindings created in view and call $scope.$digest() which inturns iterate thorugh all watches and checks if any of the watched variables have changed. When you call $scope.$apply() it internally calls $scope.$digest() so data-binding gets refreshed.
Listener directives, such as ng-click, register a listener with the DOM. When the DOM listener fires, the directive executes the associated expression and updates the view using the $apply() method.
When an external event (such as a user action, timer or XHR) is received, the associated expression must be applied to the scope through the $apply() method so that all listeners are updated correctly (ref).
So in your case view gets update when you click next start button again (ng-click) and not when recording event occurs.
Also would be usefult to read this

Related

Trigger Click from AngularJS Controller

I'm using a request header with a key and a value to read from the AngularJS controller. If that header, in this case, is AppHeaders is valid and has a custom value, I need to trigger a click from that controller. The code that I have, if something like this:
$scope.$applyAsync(function() {
if (appHeaders != null && appHeaders['header-name'] != null) {
if (appHeaders['header-name'] == "custom-value") {
$('.class-name').click();
}
}
});
What's wrong? I make a deep debug into this and the conditional works fine. I guess that the problem is because the element on the DOM doesn't exist when the click is fired.
Thanks for your help guys! The final solution results on apply the $broadcast event on the functions declared and use a counter to validate the last cycle of the calls to trigger the click on the element.
// Here we declare an empty array to store each request
var requests = [];
// Our first function
$scope.firstFunction = function() {
// Execute broadcast event with the name of the function
$scope.$broadcast('requestEnded',requests.push('firstFunction'));
};
// Our last function
$scope.secondFunction = function() {
// Execute broadcast event with the name of the function
$scope.$broadcast('requestEnded', requests.push('secondFunction'));
};
// This listener is executed each time that requestEnded is fired
$scope.$on('requestEnded', function(event, countRequests) {
// Here we validate that the count of requests is the desire
if (countRequests == 2) {
// Trigger Click
$('.class-selector').click();
}
});
I write a post with the entire research explaining this:
https://jbrizio.github.io/2017/10/20/Trigger-click-when-determinate-requests-finish-using-AngularJS.html

How to suppress a watch statement or digest cycle in Angular

Currently I have a text input attached to a model with a $scope.watch statement observing the model. This is all used to achieve a type of auto complete / typeahead feature.
<!-- HTML -->
<input type="text" ng-model="search.mySearchText">
// JS
var deregister = $scope.$watch('search.mySearchText', doSearch);
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
// do something with the data
});
}
This works fine. However, occasionally in my .then function I want to make changes to search.mySearchText. Obviously this would cause the watcher to be fired again, but I don't want this.
What I'm hoping to do is find a way to suppress the $watch from firing that next time. Maybe by somehow telling Angular that that particular watched model property is no longer dirty?
I tried removing the $watch by de/re- registering the watch at appropriate times, but that didn't work either.
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
deregister(); // shut off the watch
search.mySearchText = 'some new string'; // manipulate the model property that I don't want to cause a search
deregister = $scope.$watch('search.mySearchText', doSearch);
});
}
However, this didn't prevent the event firing like I expected, which is why I'm now looking for a way to suppress the event.
You could have a variable that determines whether doSearch exits early, like so:
var searchActive = true;
function doSearch() {
if (!searchActive) return;
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
searchActive = false;
// do manipulation of search.mySearchText
searchActive = true;
});
}

Frequent updates to Angular controller not reflected in view

Working on an interesting issue with Angular and frequent updates from SignalR.
on the server, I have a singleton instance of a manager which is currently set to send updates every 500ms
public void StartTest() {
var i = 0;
while (i < 1000) {
if (_cancelRequested) {
_cancelRequested = false;
return;
}
_context.Clients.All.sent(i++);
Thread.Sleep(500);
in my view i'm using "controller as vm" syntax:
{{vm.status}}
finally in my controller (on signalR initialization):
// Using 'Controller As' syntax, so we assign this to the vm variable (for viewmodel).
var vm = this;
vm.status = '';
...
function activateSignal() {
var hub = $.connection.mailerHub;
hub.client.sent = function (counter) {
log(' singal_R ## event: sent ' + counter); // <-- this works on every update
vm.status = counter; // this works only on last update, or if stop is pressed it writes the most recent update.
};
that code does 2 things: writes to log (this sends a toastr update to the browser), and sets the status variable on the controller.
the log comments are reflected in the browser as expected, every half second i get the nice toastr notification with event number, however setting the variable doesn't have any effect, until i request stop, or it reaches 999.
when i press stop, vm.status gets written out with last sequence number, or when the entire thing stops, it writes out 999.
so it appears to be some issue with frequency of setting properties on vm? would i need something like $scope.$watch to make this work?
Well, it is not clear enough for me exactly what you are trying to achieve, but it looks like you are updating your model outside the control of angular. So, you need to notify angular of what you are doing.
Most likely, something like this should solve the problem:
hub.client.sent = function (counter) {
$scope.apply(function(){
log(' singal_R ## event: sent ' + counter);
vm.status = counter;
});
};

Event is firing multiple times (Parse.com)

I'm writing an app in Parse, using the JavaScript framework. In my view, I have a link with the class 'new-page'. In the JS code, I have:
events: {
"click .new-page" : "createPage",
}
createPage is:
createReel: function() {
var self = this;
// Get current pagelist
var pages= new PageList;
pages.query = new Parse.Query(Page);
pages.query.equalTo("owner", Parse.User.current());
pages.query.ascending("order");
pages.fetch({
success: function(pagelist) {
var newPage = new Page;
newPage .save({
success: function(newpage) {
// Redirect to page edit
new PageEditView();
}
});
}
});
}
First time round, this works fine - the new page is created, and it goes to edit mode for that page. But if I then go back to the view with the 'Add Page' button and click it again, I get 2 new pages. If I do it again, I get 4, and so on.
I assume the event is 'building up', so that the more times the button is clicked, the more times the event gets fired.
I'm not sure where to start looking.
Late answer, but I just had a similar issue.
The mistake I made was that I created the view instance again every time I needed it.
That is not how Backbone is ment to work.
Views are (usually) created once. And when their model changes, the are being rendered again.
you seem to be instantiating your view every time the user saves at this line
PageEditView();
That's when the events stack up.
Instantiate that view once when you start up the page and then render it whenever you update the model for that view.
In native bakbone, event handlers can listen to model changes. Somehow it won't work in the Parse backbone version (or I can't get it to work). So for now, I do that manually with
PageEditView.model = model
PageEditView.render();
Make sure you undelegate events before calling the new PageEditView.

AngularJS : service $broadcast and $watch not triggering on receiving controller

AngularJS noob here, on my path to the Angular Enlightenment :)
Here's the situation:
I have implemented a service 'AudioPlayer' inside my module 'app' and registered like so:
app.service('AudioPlayer', function($rootScope) {
// ...
this.next = function () {
// loads the next track in the playlist
this.loadTrack(playlist[++playIndex]);
};
this.loadTrack = function(track) {
// ... loads the track and plays it
// broadcast 'trackLoaded' event when done
$rootScope.$broadcast('trackLoaded', track);
};
}
and here's the 'receiver' controller (mostly for UI / presentation logic)
app.controller('PlayerCtrl', function PlayerCtrl($scope, AudioPlayer) {
// AudioPlayer broadcasts the event when the track is loaded
$scope.$on('trackLoaded', function(event, track) {
// assign the loaded track as the 'current'
$scope.current = track;
});
$scope.next = function() {
AudioPlayer.next();
};
}
in my views I show the current track info like so:
<div ng-controller="PlayerCtrl">
<button ng-click="next()"></button>
// ...
<p id="info">{{current.title}} by {{current.author}}</p>
</div>
the next() method is defined in the PlayerCtrl, and it simply invokes the same method on the AudioPlayer service.
The problem
This works fine when there is a manual interaction (ie when I click on the next() button) - the flow is the following:
PlayerCtrl intercepts the click and fires its own next() method
which in turn fires the AudioPlayer.next() method
which seeks the next track in the playlist and calls the loadTrack() method
loadTrack() $broadcasts the 'trackLoaded' event (sending out the track itself with it)
the PlayerCtrl listens the broadcast event and assigns the track to the current object
the view updates correctly, showing the current.title and current.author info
However, when the next() method is called from within the AudioService in the 'background' (ie, when the track is over), all the steps from 1 to 5 do happen, but the view doesn't get notified of the change in the PlayerCtrl's 'current' object.
I can see clearly the new track object being assigned in the PlayerCtrl, but it's as if the view doesn't get notified of the change. I'm a noob, and I'm not sure if this is of any help, but what I've tried is adding a $watch expression in the PlayerCtrl
$scope.$watch('current', function(newVal, oldVal) {
console.log('Current changed');
})
which gets printed out only during the 'manual' interactions...
Again, like I said, if I add a console.log(current) in the $on listener like so:
$scope.$on('trackLoaded', function(event, track) {
$scope.current = track;
console.log($scope.current);
});
this gets printed correctly at all times.
What am I doing wrong?
(ps I'm using AudioJS for the HTML5 audio player but I don't think this is the one to blame here...)
When you have a click event the $scope is updated, without the event you'll need to use $apply
$scope.$apply(function () {
$scope.current = track;
});
As it's not safe to peek into the the digest internals, the easiest way is to use $timeout:
$timeout(function () {
$scope.current = track;
}, 0);
The callback is executed always in the good environment.
EDIT: In fact, the function that should be wrapped in the apply phase is
this.loadTrack = function(track) {
// ... loads the track and plays it
// broadcast 'trackLoaded' event when done
$timeout(function() { $rootScope.$broadcast('trackLoaded', track); });
};
Otherwise the broadcast will get missed.
~~~~~~
Actually, an alternative might be better (at least from a semantic point of view) and it will work equally inside or outside a digest cycle:
$scope.$evalAsync(function (scope) {
scope.current = track;
});
Advantage with respect to $scope.$apply: you don't have to know whether you are in a digest cycle.
Advantage with respect to $timeout: you are not really wanting a timeout, and you get the simpler syntax without the extra 0 parameter.
// apply changes
$scope.current = track;
try {
if (!$scope.$$phase) {
$scope.$apply($scope.current);
}
} catch (err) {
console.log(err);
}
Tried everything, it worked for me with $rootScope.$applyAsync(function() {});

Categories

Resources