I am trying to get a small angularJS app running. The app should receive asynchronous messages via STOMP / Websockets. If a message via the web socket arrives, the angular app is supposed to display a changed value in the UI. In general angular's data binding works as expected, but if the scope is updated within the callback function on_message() nothing happens in the UI. I have read various post on similar topics and tried the suggested solutions like using $apply within the callback function, but without success.
In the debugger I can see that the $scope.SoC gets assigned the correct value, but the UI remains unchanged.
If the function on_message(m) is called directly - just for testing - and not from socket-client, the UI gets updated correctly.
This is the abbreviated structure of the controller code
App.controller('showCaseDataCtrl', function($scope){
var mySoC = 0.7;
$scope.status = {statusMessage: "No Message", SoC: 0.77, power: 5.4, numDevices: 89};
function on_message(m) {
mySoC ++;
$scope.$apply(function() {$scope.status.SoC = mySoC;});
console.log(mySoC);
}
});
This is the HTML
<html ng-app="App">
...
<div class="col-md-4" >
<h1> <span ng-bind="status.SoC" /> SoC</h1>
<p>Charge Status</p>
</div>
...
</html>
Any suggestions what else to try are appreciated.
Update:
The problem has to to with mg-route and a separate view in the main HTML
in the main HTML I am using a statement like
<!-- views selected by the route will be injected here -->
<div ng-view ="" </div>
and the route provider looks like
App.config(function($routeProvider){
$routeProvider
.when('/',
{
controller: 'showCaseDataCtrl',
templateUrl: 'partials/Overview.html'
})
.when('/test',
{
controller: 'showCaseDataCtrl',
templateUrl: 'partials/Overview.html'
})
.otherwise({redirectTo: '/'});
});
Removing the route provider and putting all HTML directly in the main HTML with
<div ng-controller="showCaseDataCtrl" class="col-md-4" >
<h1> <span ng-bind="status.SoC" /> SoC</h1>
<p>Charge Status</p>
</div>
solved the problem.
--- But I have no clue why ---
Any explanation appreciated
The reason the $scope is not updating is because the callback is happening outside of Angular's $digest() cycle. Try wrapping it in $scope.$apply.
function on_message(m) {
// console.log('Received:' + m);
// var MyJSON = $.parseJSON(m.body);
mySoC ++;
$scope.$apply(function() {
$scope.SoC = mySoC;
});
console.log(mySoC);
}
});
Seems like you had it commented out, but that should work.
Related
I understand how two-way data binding works with AngularJS but I'm struggling to make it work in a situation described below. I've a index.template.html, home.template.html, search.template.html, app.js, search.controller.js and search.service.js files. I've a input form on a home page at '/' url.
<li ng-controller="searchCtrl"> //searchCtrl() in search.controller.js file
<form role="search">
<input type="text" ng-model="searchstring"
placeholder="Enter string here"> <br />
<button type="submit"
ng-click="searchString(searchstring)">Search</button>
</form>
</li>
And I've search.template.html to display the results as follows,
<html>
<body>
<h1> SearchedText</h1>
<h2> {{ searchedtext }} </h2>
</body>
</html>
search.controller.js is as follows,
(function () {
angular
.module('twowaybindapp')
.controller('searchCtrl', searchCtrl);
searchCtrl.$inject = ['$scope', 'SearchService'];
function searchCtrl($scope, SearchService) {
$scope.searchString = function(searchtext) {
SearchService
.getSearchResults(searchtext) //getSearchResults in search.service.js
.success(function(data) {
console.log('Service call is a success with result:');
console.log(data);
$scope.searchedtext = data;
}).error(function(e) {
console.log(e);
});
};
}
})();
My app.js file is as follows,
var myApp = angular.module('twowaybindapp', [ 'ngRoute' ]);
myApp.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider) {
$routeProvider.
when('/', {
templateUrl: 'home.view.html'
}).
when('/search', {
templateUrl: 'search.view.html',
controller: 'searchCtrl'
}).
otherwise({ redirectTo: '/' });
}]);
With this background, When I've both form and results html text (content of search.template.html) in the same home.template.html, things work fine and I see all the results but if I put them in separate files as above, either I don't know how to open the results in new view after the call or don't know if data-binding is not working. I do see results in console.log() in both cases that mean service call is working okay as expected. This might be silly question but I really need help with this. Would it work if the search form is a part of navigation directive at the top of every page and results needs to be shown below in a main ng-view? Any help is appreciated. Thanks.
-- Atarangp
When you write {{smth}} in html it parsed to $scope.smth, each controller has its own scope object, by default scopes are nested:
<div ng-controller="c1" ng-init="smth = 1">
<div ng-controller="c2">
{{smth}} << 1
</div>
</div>
<div ng-controller="c3">
{{smth}} << undefined
</div>
When you defined several views - they will have different scopes, so you can not see one scope from another.
Fastest way to make it work is change to $rootScope.searchedtext = data; cause all your scopes nests from root scope.
Other way is to make common parent state and access and set your property there.
Yet another ways are: use your own service to store data, pass data using events, pass data using properties in state obj...
I have the following URL:
http://myUrl.com/#/chooseStyle?imgUpload=6_1405794123.jpg
I want to read the imgUpload value in the query string - I'm trying:
alert($location.search().imgUpload);
But nothing alerts, not even a blank alert - but console reads:
$location is not defined
I need this value to add into a controller to pull back data, and also to carry into the view itself as part of a ng-src
Is there anything I'm doing wrong? this is my app config:
capApp.config(function($locationProvider, $routeProvider) {
$locationProvider.html5Mode(false);
$routeProvider
// route for the home page
.when('/', {
templateUrl : '/views/home.html',
controller : 'mainController'
})
// route for the caption it page
.when('/capIt', {
templateUrl : '/views/capIt.html',
controller : 'mainController'
});
}):
This is the view:
<div class="container text-center">
<h1 class="whiteTextShadow text-center top70">Choose your photo</h1>
</div>
<script>
alert($location.search().imgUpload);
</script>
Main controller:
capApp.controller('mainController', function($scope) {
$scope.message = 'Whoop it works!';
});
My end goal is that I can find a solution to capturing and re-using data from the query string.
I will also mention, this is only my first week in Angular, loving it so far! A lot to learn...
<script>
alert($location.search().imgUpload);
</script>
You're making two mistakes here:
executing code while the page is loading, and the angular application is thus not started yet
assuming $location is a global variable. It's not. It's an angular service that must be injected into your controller (or any other angular component). This should cause an exception to be thrown and displayed in your console. Leave your console open always, and don't ignore exception being thrown.
You should not do this
<script>
alert($location.search().imgUpload);
</script>
// you need to inject the module $location
//(either in service, or controller or wherever you want to use it)
// if you want to use their APIs
capApp.controller('mainController', function($scope, $location) {
$scope.message = 'Whoop it works!';
//use API of $location
alert($location.search().imgUpload);
});
During working on an angularJS app using Ui-Router I noticed that the the $scope method in the controller are called multiple times when a view is loaded. After some investigation it came down to the Ui-Router itself and it seemed like for every state the template is injected in ui-view multiple times.
To confirm that I created the simplest ui-router based navigation app with 2 templates and one controller and was able to reproduce the same problem.
I created a Plunk based on this test that you can see and try it here
Here is the source of index.html
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
</head>
<body ng-app="app">
<div><a ui-sref="page1">Page 1</a> <a ui-sref="page2">Page 2</a></div>
<div ui-view></div>
<script src="angular-ui-router.min.js"></script>
<script src="app.js"></script>
</body>
</html>
app.js
(function () {
'use strict';
angular.module('app', [
'ui.router'
])
.config(function ($stateProvider) {
$stateProvider
.state('page1', {
url: '/1',
templateUrl: 'page1.html',
controller: 'mainCtrl'
})
.state('page2', {
url: '/2',
templateUrl: 'page2.html',
controller: 'mainCtrl'
})
}
)
.controller('mainCtrl', function ($scope) {
$scope.log = log;
function log(msg) {
console.log(msg);
return true;
}
});
})();
page1.html
<h1>Page 1</h1>
<div ng-if="log('page 1')"></div>
page2.html
<h1>Page 2</h1>
<div ng-if="log('page 2')"></div>
When running this sample open your browser's console and then click on Page1 and Page 2. You will see each state change logs a message 2 or 3 times.
I've searched on the web but seems like no one else has reported this issue so it might be some wrong doing on my end. I would appreciate any help on this.
The issue you experience is not related to the UI-Router. Let me use few cites from this post (and you can find many more others about $apply() and $digest()):
Understanding Angular’s $apply() and $digest() by Sandeep Panda
$apply and $digest Explored
...When you write an expression ({{aModel}}), behind the scenes Angular sets up a watcher on the scope model...
And this is exactly what happened on your view here:
<div ng-if="log('page 1')"></div>
The ng-if directive is now part of the digest cycles and is checked if its value did not change. When it happens? How often?:
How Many Times Does the $digest Loop Run?
...The answer is that the $digest loop doesn’t run just once. At the end of the current loop, it starts all over again to check if any of the models have changed. This is basically dirty checking, and is done to account for any model changes that might have been done by listener functions. So, the $digest cycle keeps looping until there are no more model changes, or it hits the max loop count of 10...
And that's it. You've triggered some action (click on the link, with directive ui-sref). The angular environment has started to do its job. No error... No issue in UI-Router
You've just not selected the right place to expect to be evaluated only once
I have a basic app, that fetches some data through the $http service, however it doesnt render the data correct in the template, when the template is served from the template cache. My code looks like this:
angular.module('app', [])
api service:
.factory('api', function($http, $q) {
return {
getCars: function() {
return $http.get('api/cars');
}
};
})
the controller using the service:
.controller('carsCtrl', function($scope, api) {
api.getCars().success(function(data) {
$scope.cars = data;
});
})
the route setup:
.config(function($routeProvider) {
$routeProvider.when('/cars', {
templateUrl: 'cars.html',
controller: 'carsCtrl'
});
});
and the template cars.html
<div ng-repeat="car in cars">
{{ car }}
</div>
this works the first time the browser hits /cars, however, if I push the back on forward button in the browser to hit the url a second time without a page reload, the {{car}} is not being rendered. If the cars.html is put in the templateCache like this:
angular.module('app').run(function($templateCache) {
$templateCache.put('cars.html', '<div ng-repeat="car in cars">{{ car }}</div>');
});
the {{car}} binding is not rendered either.
I suspect this has something to do with Angular not unwrapping promises in templates anymore, but not totally sure. Does anyone know what I am doing wrong and how to write this code correctly?
Well, I saw some syntax errors in your code (maybe you didn't copy the code but typed it manually for SO not sure). Also you returned deferred instead of deferred.promise. What you trying to achieve works just fine:
Plnkr Example
I've this routes.
// index.html
<div ng-controller="mainCtrl">
<a href='#/one'>One</a>
<a href='#/two'>Two</a>
</div>
<div ng-view></div>
And this is how I'm loading the partials into my ng-view.
// app.js
var App = angular.module('app', []);
App.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/one', {template: 'partials/one.html', controller: App.oneCtrl});
$routeProvider.when('/two', {template: 'partials/two.html', controller: App.twoCtrl});
}]);
When I click the links, it shows me the appropriate markup inside the ng-view. But when I try to include partials/two.html inside partials/one.html using ng-include, it shows it properly but creates a different scope so I'm not able to interact with it.
// partials/two.html - markup
<div ng-controller="twoCtrl">I'm a heading of Two</div>
// partials/one.html - markup
<div ng-controller="oneCtrl">I'm a heading of One</div>
<div ng-include src="'partials/two.html'"></div>
How do I resolve this problem? Or Is there any other way to achieve the same result?
You can write your own include directive that does not create a new scope. For example:
MyDirectives.directive('staticInclude', function($http, $templateCache, $compile) {
return function(scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http.get(templatePath, { cache: $templateCache }).success(function(response) {
var contents = element.html(response).contents();
$compile(contents)(scope);
});
};
});
You can use this like:
<div static-include="my/file.html"></div>
The documentation for ngInclude states "This directive creates new scope." so this is by design.
Depending on the type of interaction you are looking for you may want to take a look at this post for one way to share data/functionality between the two controllers via a custom service.
So this isn't an answer to this question but i made it here looking for something similar and hopefully this will help others.
This directive will include a partial without creating a new scope. For an example you can create a form in the partial and control that form from the parent controller.
Here is a link to the Repo that i created for it.
good luck :-)
-James Harrington
You can actually do this without using a shared service. $scope.$emit(...) can dispatch events to the $rootScope, which can listen for them and rebroadcast to the child scopes.
Demo: http://jsfiddle.net/VxafF/
Reference:
http://www.youtube.com/watch?v=1OALSkJGsRw (see the first comment)