AngularJS with WebRTC and Socket.IO scope issues - angularjs

I'm currently working on a video-conference call application with AngularJS, WebRTC and Socket.IO. I've stumbled upon an issue and am not sure how to fix it.
Basically I have a list of rooms which is looped through by an ngRepeat and then a directive is included. The html looks as following:
My rooms:
<div class="room" ng-repeat="room in userRooms">
<div class="title">{{room.roomId}}</div>
<div class="content">
<conference-call></conference-call>
</div>
</div>
The conference-call directive includes a template with the following html:
<!-- local stream -->
<div class="clients-wrapper" ng-repeat="client in room.clients | filter: getMyId()">
<div class="client-wrapper">
<video ng-src="{{localVideoURL}}" muted autoplay></video>
<div class="display-name">socket-id: {{client}} (local), stream-id: {{localStream.id}}</div>
</div>
<div style="clear:both;"></div>
</div>
<!-- remote stream -->
<div class="clients-wrapper" ng-repeat="client in room.clients | filter: '!'+getMyId()">
<div class="client-wrapper">
<video ng-src="{{peerVideoURL}}" autoplay></video>
<div class="display-name">{{client}} (remote)</div>
</div>
<div style="clear:both;"></div>
</div>
Populating the local video URL goes as follows:
function handleUserMedia(stream) {
scope.localStream = stream;
scope.localVideoURL = $sce.trustAsResourceUrl(URL.createObjectURL(stream));
scope.$apply();
};
and for the remote video URL the following code is executed:
function handleRemoteStreamAdded(event) {
scope.remoteStream = event.stream;
scope.peerVideoURL = $sce.trustAsResourceUrl(URL.createObjectURL(event.stream));
scope.$apply();
};
In a later stage this will be replaced by something like peerVideos.append(stream). But that's not what this is about. Because here's the problem:
pc.onaddstream (the native WebRTC-function which is called when a
client joins) where pc = new RTCPeerConnection(pc_config,
pc_constraints); is never called.
I think this is because of the following:
Whenever someone joins the room the server emits a call which on the client side results in a $scope.$apply() call (the AngularJS way to update and refresh the scope and re-render the page). This makes sure the client sees the remote client in the room he's in. However, because of this $scope.$apply() call the whole conference-room directive is refreshed and re-initialized. This causes the problem where pc.onaddstream is never called because no-one's ever added to the peer connection, because it is re-initialized, or at least I think this is what happens.
Has anyone of you had a similar problem? And how should I approach this issue?

Related

Page render speed in AngularJS using ng-include

I've got an issue where I populate a page in pieces. There are an arbitrary number of categories with an arbitrary number of items. The code is generally something like the below (warning, transposed).
$scope.getItems = function(key) {
$http.get('get-items?key=' + key)
.then(function(res) {
for (let item of res.data) {
$scope.categories[item.category].items.push(item);
}
});
}
let populateCategories = function() {
for (let key in $scope.categories) {
$scope.getItems(key);
}
}
$scope.getCategories = function(next) {
$http.get('get-categories')
.then(function(res) {
$scope.categories = res.data;
next();
});
$scope.getCategories(populateCategories);
}
The idea is to first get what categories will be on the page, and render them, empty (but w/ a busy icon). After that, hit and endpoint one time per category and populate w/ the results. The busy icon is shown via ng-show & a boolean pointing to the size of the items. 1 or more items = no busy icon, an the items should show.
The loading of the categories more or less works. Populating them though, is not so free flowing. Watching the console output, it takes ages for the browser to render. The busy icon goes away somewhat quickly, but I don't see the items until a bunch of them are ready.
Worth noting, (I think) I saw this problem appear when I moved the html that displays each item from a single file, an template and used ng-include, as I'm using it on two different places. Surely that would not be a cause would it?
EDIT: Adding the html - simplified
item-template.html
<div class="row">
<div class="col-xs-2 col">
<img src="{{item.img}}">
</div>
<div class="col-xs-10 col">
<div>{{item.details}}</div>
</div>
</div>
list.html
<body>
<div class ="container-fluid">
<div class="row">
<div ng-repeat="(key, value) in categories">
<div>{{key}}</div>
<div ng-show="value.busy"">
<img ng-src="{{busy_image}}">
</div>
<div ng-repeat="item in value.items track by $index">
<!-- This in fact seems to be the culprit -->
<div ng-include="item-template.html">
</div>
</div>
</div>
</div>
</body>
So, playing around, if I simply paste the contents of template.html into list.html, the response is much, much better. Looking at this issue, the solution seems to be to use a cache service. I'm happy to use something like that but I'm still curious as to why. The template I'm using isn't small (166 lines) but I can't imagine it being that heavy either on a modern computer.
Several things from the top of head:
amount of items to be shown in the HTML. Large lists with x properties = alot of Angular watchers.
if there are alot of items, maybe check for an alternative to ng-repeat
instead of ng-include item-template.html, create a Component
ng-repeat with track by
use bind once
in this case you can replace ng-show with ng-if

AngularJS Scope not updating in view after async call

I am having trouble updating my scope on the front-end while making a request to an API. On the backend I can see that the value of my $scope variable is changing but this is not being reflected in the views.
Here is my controller.
Controllers.controller('searchCtrl',
function($scope, $http, $timeout) {
$scope.$watch('search', function() {
fetch();
});
$scope.search = "Sherlock Holmes";
function fetch(){
var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
$timeout(function(){
$http.get(query)
.then(function(response){
$scope.beers = response.data;
console.log($scope.beers);
});
});
}
});
Here is a snippet of my html
<div ng-if="!beers">
Loading results...
</div>
<p>Beers: {{beers}}</p>
<div ng-if="beers.status==='success'">
<div class='row'>
<div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
<h2>{{beer.name}}</h2>
<p>{{beer.style.description}}</p>
<hr>
</div>
</div>
</div>
<div ng-if="beers.status==='failure'">
<p>No results found.</p>
</div>
I've tried several solutions including using $scope.$apply(); but this just creates the common error
Error: $digest already in progress
The following post suggested to use $timeout or $asyncDefault
AngularJS : Prevent error $digest already in progress when calling $scope.$apply()
The code I have above uses $timeout and I have no errors but still the view is not updating.
Help appreciated
I you are using AngularJS 1.3+, you can try $scope.$applyAsync() right after $scope.beers = response.data; statement.
This is what Angular documentation says about $applyAsync()
Schedule the invocation of $apply to occur at a later time. The actual time difference varies across browsers, but is typically around ~10 milliseconds. Source
Update
As others have pointed out, you should not (usually) need to trigger the digest cycle manually. Most of the times it just points to a bad design (or at least not an AngularJS-friendly design) of your application.
Currently in the OP the fetch method is triggered on $watch. If instead that method was to be triggered by ngChange, the digest cycle should be triggered automatically.
Here is an example what such a code might look like:
HTML
// please note the "controller as" syntax would be preferred, but that is out of the scope of this question/answer
<input ng-model="search" ng-change="fetchBeers()">
JavaScript
function SearchController($scope, $http) {
$scope.search = "Sherlock Holmes";
$scope.fetchBeers = function () {
const query = `http://api.com/v2/search?q=${$scope.search}&key=[API KEY]&format=json`;
$http.get(query).then(response => $scope.beers = response.data);
};
}
As the comments suggest, you shouldn't need to use $timeout to trigger a digest cycle. As long as the UX that elicits the change is within the confines of an angular construct (e.g. controller function, service, etc.) then it should manifest within the digest cycle.
Based on what I can infer from your post, you are probably using a search input to hit an API with results. I'd recommend changing the logic up such that you are triggering your search on an explicit event rather than the $watcher.
<input ng-model="search" ng-change="fetch()">
Remove the $watch logic and the $timeout wrapper.
function fetch(){
var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
$http.get(query)
.then(function(response){
$scope.beers = response.data;
console.log($scope.beers);
//it's a good habit to return your data in the promise APIs
return $scope.beers;
});
}
The reasons I make this recommendation is:
You have finer control of how the ng-change callback is triggered using ng-model-options. This means you can put a delay on it, you can trigger for various UX events, etc.
You've maintained a clearer sequence of how fetch is called.
You have possibly avoided performance and $digest issues.
Hey guys I solved the issue but I'm not sure exactly why this changed anything. Rearranging my code on JS Fiddle I just put all my partials into the index.html file like so and the requests and scope variables updated smoothly. Is was there perhaps a controller conflict with my html above?
<body ng-app="beerify" ng-controller='searchCtrl'>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container"><!-- nav bar code -->
</div>
</nav>
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<div class="container">
<h1>Title</h1>
<form ng-submit="fetch()">
<div class="input-group">
<input type="text" ng-model="search"
class="form-control" placeholder="Search the name of a beer" name="srch-term" id="srch-term">
<div class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
</div>
</div>
</form>
</div>
</div>
<div class="container">
<div ng-if="!beers">
Loading results...
</div>
<div ng-if="beers.status==='success'">
<div class='row'>
<div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
<!-- ng-if will make sure there is some information being displayed
for each beer -->
<h2>{{beer.name}}</h2>
<h3>{{beer.style.name}}</h3>
<p>AbvMin: {{beer.abv}}</p>
<p>AbvMax: {{beer.ibu}}</p>
<p>{{beer.style.description}}</p>
<hr>
</div>
</div>
</div>
<div ng-if="beers.status==='failure'">
<p>No results found.</p>
</div>
</body>

angularJS shows empty slots in array but it's not

It's weird and I don't have any idea of how to solve this issue...
I have my controller with an ajax call using a service with promise, which works great.
horariosOcupadosService.getHorariosOcupados($scope.formData.cmbUnidade, $scope.formData.cmbDiaSemana).then(function(response) {
//when I set my variable with the result, everything is fine
//and I can iterate with ng-repeat with no problem
$scope.horariosOcupados = response;
$scope.formData.qtdeHorarios = $scope.horariosOcupados.length;
}), function(error) {
console.log(response);
};
But in my HTML, I have a problem... after my ng-repeat, which works fine, if I try to print $scope.horariosOcupados ({{ horariosOcupados | json }}), it prints:
[{},{},{}]
It only shows something if I change the value of the ng-model field.
<div id="horarios">
<div ng-repeat="horarioOcupado in horariosOcupados track by $index" id="horario{{$index}}" class="form-group" style="margin: 15px; 0px;">
<div class="col-md-2 text-center" id="remove"><a ng-href="#" ng-click="removeHorario($index)"><i class="fa fa-trash-o fa-2x"></i></a></div>
<div class="col-md-3">
<div class="input-group">
<span class="input-group-addon">Horário inicial</span><input name="hrEntra{{$index}}" type="text" ng-model="horariosOcupados[$index].hrInicial">
</div>
</div>
</div>
</div>
<!-- GRADE DE HORÁRIOS - FIM -->
<pre>{{ horariosOcupados | json }}</pre>
Could anyone help me to figure it out?
Thanks!
You are probably doing something outside of the angular-world.
How do you fetch your data asynchronously ?
Do you maybe use a ajax call besides the angular built-in $http service ? or maybe with the "normal" setTimeout function instead of using the angular $timeout version ?
My guess is that you are getting it somehow asynchronously without staying inside the angular world. Therefore angular does not know anything about these changes and will not update the view accordingly in time.
What you can do for the moment is try
$scope.horariosOcupados = response;
$scope.formData.qtdeHorarios = $scope.horariosOcupados.length;
$scope.$apply();
to "tell" angular that something has changed hence angular will re-render the changes immediately.
Idealy though it's most of the times not necessary to do so if you stay inside the angular-world and use the angular built-in services
I figured it out. the problem was the maxlength of my INPUT field. The length of "1900-01-01 08:00" > 5, so angularJS set it to undefined.
Thank you all for helping me!

Can't get finite flow of tweets from ntwitter using socket.io

I have my below code that uses socket.io to get tweets from my back-end. I'm using ntwitter and am opening up a stream. I'm trying to get the tweets into a queue so that I can have them float across the screen at different time intervals, but the socket never stops getting tweets so my queue isn't accessible form my front-end in angular. I do successfully pull the tweets, because I console.log them, I just can't have them show up on the front-end.
socket.on('tweet', function(data){
$scope.tweetObject = {
"user": data.user,
"text": data.text
}
$scope.queue.enqueue(tweetObject);
$scope.tweet = $scope.queue.dequeue();
console.log($scope.tweet);
});
my html front-end
<div ng-controller="mainSpaceController">
<div class="tweet" ng-repeat="x in tweetObject">
<p> in the field</p>
{{x.user}}
{{x.text}}
</div>
</div>
Possibly you need to add response.end() in the end of the response handler on your nodejs backend.
And don't forget to invoke $scope.$apply() after updating your scope with new data to force a digest cycle. Your iteration will be l
Update: In your example tweetObject is not an array it's just a plain object. So you're iterating over its properties, see more here.
The updated code:
<div ng-controller="mainSpaceController">
<div class="tweet" ng-repeat="(user, text) in tweetObject">
<p> in the field</p>
{{user}}
{{text}}
</div>
</div>

Controlling contents of a <div> in angular js

I have a single page with the following html:
<div ng-controller="TraceController">
<div ng-repeat="trace in traces">
<div>{{ trace }}</div>
</div>
</div>
<div ng-controller="DetailedTraceController">
// I want this content to be defined by the clicking of the above.
</div>
I have already populated traces with an array of data. What I would like to do now is to make another $http call based on the clicking of the individual trace elements.
The question is which is the most correct element to use for my trace element? <a> or just a <div>?
Also, what would be the most angular way to then trigger a call to the DetailedTraceController to then call another $http method with the trace details from above?
The way I ended up solving this was with the following:
<div ng-controller="TraceController">
<div ng-repeat="trace in traces">
<div ng-click="clickTrace(trace)">{{ trace }}</div>
</div>
</div>
Which then set up a method call to clickTrace with the trace in question.

Resources