AngularJs how to use $scope in callback method - javascript

Hi I'm new to AngularJs and have some issues with the controller
For me this is working great:
var m= angular.module('m', []);
m.controller('myC', function myC($scope) {
$scope.myVal = [{/*..*/}]; //assigning values directly
});
used e.g. like this
<div data-ng-app="m">
<div ng-controller="myC">
{{myVal}}
</div>
<div>
but I have a bit more complex method to acquire the data I want to use in myVal. Therfore I try to transfer the $scope to use it in an callback method (a web request is performed there, which take some time, but values get returned!). My approach is the following:
var m= angular.module('m', []);
var s;
m.controller('myC', function myC($scope) {
s = $scope;
bigFunction("foo1", "foo2", myCallback);
});
function myCallback(a, b) {
s.myVal = b; //trying to assign the value to $scope
}
but Angular is not working anymore. The same (as above listed) html snippit is not working anymore. But myCallback is called!
Have I missed something obviously? Or: How can I access $scope in the callback method in order to use it in the Angular within an HTML page?

You could use .bind to set the value of this inside myCallback to $scope:
m.controller('myC', function myC($scope) {
bigFunction("foo1", "foo2", myCallback.bind($scope));
});
function myCallback(a, b) {
this.myVal = b; //trying to assign the value to $scope
}
Or, you could use .bind to set the first parameter:
m.controller('myC', function myC($scope) {
bigFunction("foo1", "foo2", myCallback.bind(null, $scope));
});
function myCallback($scope, a, b) {
$scope.myVal = b; //trying to assign the value to $scope
}

Related

Updating a 'this' value in a service via a function

I'm quite new to Angular and am trying to understand how everything works. I've been poking around and couldn't find any information on how to do this. So, I've got a service that defines
this.totalCount = 0;
In my controller, my get request retrieves some emails and then executes a function called addMessage for each message it retrieves. The addMessage function is in my service.
The function in my service looks like this:
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
}
Basically, I am trying to increment this.totalCount each time this function is executed so that it will update and then can be displayed in the view. I have it displaying in the view currently, however its number always remains 0.
I've tried the following:
1.
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
this.totalCount++;
}
2.
var count = this.totalcount
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
count++; //and then attempted to display this value in the view but with no luck
}
Any tips would be greatly appreciated.
try this:
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
}
I assume that you're binding the var this way in your controller and your view
Service :
this.totalCount = 0;
this.totalCount++;
Controller :
$scope.totalCount = service.totalCount;
view :
{{totalCount}}
And if you're actually doing it like this, you should face this kind of trouble.
The main problem is that totalCount is a primitive var and doing this.totalCount++ will break the reference. If you want to keep some var you should bind it as a sub-object.
This way :
Service :
this.utils = {};
this.utils.totalCount = 0;
this.utils.totalCount++;
Controller :
//This is the most important part. You bind an object. Then even if you loose the totalCount reference, your object will keep its own reference.
$scope.myServiceUtils = service.utils;
View :
{{myServiceUtils.totalCount}}
Actually in service (it's a matter of taste) i prefer a lot to use the object syntax instead of this (as "this" can be confusing)
This way :
var service = {};
service.utils.totalCount = 0;
service.addItem = function(){
...
}
return service;
Hope that was your issue.
You pass argument to another function which has different scope than your service. It is trick with assigning current object to variable, which is visible from function.
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
that.totalCount++;
}
Should work.
So you assign that variable with current object, which is visible in inner function scope.
In a function addMessage body, this refers to function scope which is new, and there is no compiler error, but messagesList is a null object and totalCount is incremented, but after program leave function, it's not visible in service, because it is in a function scope which isn't assigned to any variable.
To update service variable as it changes in your controller, use $watch.
$scope.$watch(function() {
return messagesService.totalCount;
}, function(new,old){
$scope.totalmessagecount = messagesService.totalCount;
});
First parameter of $watch if function which return observed for change element. Another is standard function to perform operation after update.

AngularJS - Creating a compile function

I'm trying to create a custom compile function, to make it easier to dynamically add HTML to a page.
The argument htmlStr is the incoming HTML to compile. The argument value is a variable that can be added to the scope. The argument compiledHTMLFunc is a function that will be executed with the compiled object. Here's my code:
function compileHTML (htmlStr, value, compiledHTMLFunc)
{
var $injector = angular.injector (["ng", "angularApp"]);
$injector.invoke (function ($rootScope, $compile)
{
$rootScope.value = value;
var obj = angular.element (htmlStr);
var obj2 = $compile (obj)($rootScope);
if (compiledHTMLFunc != null)
compiledHTMLFunc (obj2);
});
}
Here's how I use the function:
compileHTML ("<button class = \"btn btn-primary\">{{ value }}</button>", "Ok", function (element)
{
$(document.body).append (element);
});
Whenever I try to compile the following HTML, the inline {{ value }} doesn't get compiled. Even if I simply change it to {{ 1+1 }}. Why is this?
Update: I dunno why I didn't create a fiddle earlier, here's an example: http://jsbin.com/vuxazuzu/1/edit
The problem appears to be pretty simple. Since you invoke compiler from outside of angular digest cycle you have to invoke it manually to boost the process, for example by wrapping compiledHTMLFunc into $timeout service call:
function compileHTML (htmlStr, scope, compiledHTMLFunc) {
var $injector = angular.injector(["ng", "angularApp"]);
$injector.invoke(function($rootScope, $compile, $timeout) {
$rootScope = angular.extend($rootScope, scope);
var obj = $compile(htmlStr)($rootScope);
if (compiledHTMLFunc != null) {
$timeout(function() {
compiledHTMLFunc(obj);
});
}
});
}
compileHTML('<button class="btn btn-primary">{{value}}</button>', {value: 'Ok'}, function(element) {
angular.element(document.body).append(element);
});
I also improved your code a little. Note how now compileHTML accepts an object instead of single value. It adds more flexibility, so now you can use multiple values in template.
Demo: http://plnkr.co/edit/IAPhQ9i9aVVBwV9MuAIE?p=preview
And here is your updated demo: http://jsbin.com/vuxazuzu/2/edit

Updating angular.js service object without extend/copy possible?

I have 2 services and would like to update a variable in the 1st service from the 2nd service.
In a controller, I am setting a scope variable to the getter of the 1st service.
The problem is, the view attached to the controller doesn't update when the service variable changes UNLESS I use angular.extend/copy. It seems like I should just be able to set selectedBuilding below without having to use extend/copy. Am I doing something wrong, or is this how you have to do it?
controller
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
$scope.building = BuildingsService.getSelectedBuilding();
});
service 1
app.factory('BuildingsService', function() {
var buildingsList = [];
var selectedBuilding = {};
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
angular.extend(selectedBuilding, _.find(
buildingsList, {'building_id': buildingId})
);
};
var getSelectedBuilding = function() {
return selectedBuilding;
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
service 2
app.factory('AnotherService', function(BuildingsService) {
...
// something happens, gives me a building id
BuildingsService.setSelectedBuilding(building_id);
...
});
Thanks in advance!
When you execute this code:
$scope.building = BuildingsService.getSelectedBuilding();
$scope.building is copied a reference to the same object in memory as your service's selectedBuilding. When you assign another object to selectedBuilding, the $scope.building still references to the old object. That's why the view is not updated and you have to use angular.copy/extend.
You could try the following solution to avoid this problem if you need to assign new objects to your selectedBuilding:
app.factory('BuildingsService', function() {
var buildingsList = [];
var building = { //create another object to **hang** the reference
selectedBuilding : {}
}
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
//just assign a new object to building.selectedBuilding
};
var getSelectedBuilding = function() {
return building; //return the building instead of selectedBuilding
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
With this solution, you have to update your views to replace $scope.building bindings to $scope.building.selectedBuilding.
In my opinion, I will stick to angular.copy/extend to avoid this unnecessary complexity.
I dont believe you need an extend in your service. You should be able to watch the service directly and respond to the changes:
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
// first function is evaluated on every $digest cycle
$scope.$watch(function(scope){
return BuildingsService.getSelectedBuilding();
// second function is a callback that provides the changes
}, function(newVal, oldVal, scope) {
scope.building = newVal;
}
});
More on $watch: https://code.angularjs.org/1.2.16/docs/api/ng/type/$rootScope.Scope

Access scope variables from a filter in AngularJS

I am passing date value to my custom filter this way:
angular.module('myapp').
filter('filterReceiptsForDate', function () {
return function (input, date) {
var out = _.filter(input, function (item) {
return moment(item.value.created).format('YYYY-MM-DD') == date;
});
return out;
}
});
I would like to inject a couple of scope variables there too, like what I can do in directives. Is that possible to do this without having to passing these vars explicitly as function arguments?
Apparently you can.
Usually you would pass scope variables to the filter as function parameter:
function MyCtrl($scope){
$scope.currentDate = new Date();
$scope.dateFormat = 'short';
}
<span ng-controller="MyCtrl">{{currentDate | date:dateFormat}}</span> // --> 7/11/13 4:57 PM
But, to pass the current scope in, you'd have to pass this:
<span ng-controller="MyCtrl">{{currentDate | date:this}}</span>
and this will be a reference to current scope:
Simplified:
app.controller('AppController',
function($scope) {
$scope.var1 = 'This is some text.';
$scope.var2 = 'And this is appended with custom filter.';
}
);
app.filter('filterReceiptsForDate', function () {
return function (input, scope) {
return input + ' <strong>' + scope.var2 + '</strong>';
};
});
<div ng-bind-html-unsafe="var1 | filterReceiptsForDate:this"></div>
<!-- Results in: "This is some text. <strong>And this is appended with custom filter.</strong>" -->
PLUNKER
Warning:
Be careful with this and use scope only to read the values inside the filter, because otherwise you will easily find your self in $digest loop.
Filters that require such a "heavy" dependency (the whole scope) tend to be very difficult to test.
I found that this references local $scope. Not sure if this is safe way of accessing it.

Watch multiple $scope attributes

Is there a way to subscribe to events on multiple objects using $watch
E.g.
$scope.$watch('item1, item2', function () { });
Starting from AngularJS 1.3 there's a new method called $watchGroup for observing a set of expressions.
$scope.foo = 'foo';
$scope.bar = 'bar';
$scope.$watchGroup(['foo', 'bar'], function(newValues, oldValues, scope) {
// newValues array contains the current values of the watch expressions
// with the indexes matching those of the watchExpression array
// i.e.
// newValues[0] -> $scope.foo
// and
// newValues[1] -> $scope.bar
});
Beginning with AngularJS 1.1.4 you can use $watchCollection:
$scope.$watchCollection('[item1, item2]', function(newValues, oldValues){
// do stuff here
// newValues and oldValues contain the new and respectively old value
// of the observed collection array
});
Plunker example here
Documentation here
$watch first parameter can also be a function.
$scope.$watch(function watchBothItems() {
return itemsCombinedValue();
}, function whenItemsChange() {
//stuff
});
If your two combined values are simple, the first parameter is just an angular expression normally. For example, firstName and lastName:
$scope.$watch('firstName + lastName', function() {
//stuff
});
Here's a solution very similar to your original pseudo-code that actually works:
$scope.$watch('[item1, item2] | json', function () { });
EDIT: Okay, I think this is even better:
$scope.$watch('[item1, item2]', function () { }, true);
Basically we're skipping the json step, which seemed dumb to begin with, but it wasn't working without it. They key is the often omitted 3rd parameter which turns on object equality as opposed to reference equality. Then the comparisons between our created array objects actually work right.
You can use functions in $watchGroup to select fields of an object in scope.
$scope.$watchGroup(
[function () { return _this.$scope.ViewModel.Monitor1Scale; },
function () { return _this.$scope.ViewModel.Monitor2Scale; }],
function (newVal, oldVal, scope)
{
if (newVal != oldVal) {
_this.updateMonitorScales();
}
});
Why not simply wrap it in a forEach?
angular.forEach(['a', 'b', 'c'], function (key) {
scope.$watch(key, function (v) {
changed();
});
});
It's about the same overhead as providing a function for the combined value, without actually having to worry about the value composition.
A slightly safer solution to combine values might be to use the following as your $watch function:
function() { return angular.toJson([item1, item2]) }
or
$scope.$watch(
function() {
return angular.toJson([item1, item2]);
},
function() {
// Stuff to do after either value changes
});
$watch first parameter can be angular expression or function. See documentation on $scope.$watch. It contains a lot of useful info about how $watch method works: when watchExpression is called, how angular compares results, etc.
how about:
scope.$watch(function() {
return {
a: thing-one,
b: thing-two,
c: red-fish,
d: blue-fish
};
}, listener...);
$scope.$watch('age + name', function () {
//called when name or age changed
});
Here function will get called when both age and name value get changed.
Angular introduced $watchGroup in version 1.3 using which we can watch multiple variables, with a single $watchGroup block
$watchGroup takes array as first parameter in which we can include all of our variables to watch.
$scope.$watchGroup(['var1','var2'],function(newVals,oldVals){
console.log("new value of var1 = " newVals[0]);
console.log("new value of var2 = " newVals[1]);
console.log("old value of var1 = " oldVals[0]);
console.log("old value of var2 = " oldVals[1]);
});

Categories

Resources