Angular is not rendering before print function? - javascript

Problem is that print function is called BEFORE angular variables are loaded so in view i get something like this {{ticketPin}} and so on...Any suggestion how can i render angular parameters before print function is called ?
I have accountContentController where i have this :
$scope.printFunction = function ()
{
localStorage.setItem("payoutTime", $scope.testpayoutTime);
localStorage.setItem("payoutAmount", $scope.testpayoutAmount);
localStorage.setItem("pin", $scope.testticketPin);
$window.open("/print");
}
I have printController where i have this :
$window.print();

Use ng-cloak or ng-bind to get rid of {{ticketpin}}
<span ng-bind="ticketpin"></span>

Id depends if you are loading to a scope from promise or other way.
But in both cases, it's possible to set watch on these specific parameters.
Watch is having arguments of new and old variables and you can check if old value ( suppose null or undefined ) is exchanged by new value ( suppose this value you want to print ).
So set watch:
$scope.$watch(function(){
return $scope.testpayoutAmount;
}, function(newVal,oldVal) {
if ( newVal != oldVal ) {
$window.print();
}
});
The problem is, that if you have three variables and you want to know if each is changed.
You can set a counter to count to three:
$scope.$watch(function(){
return $scope.testpayoutAmount + $scope.var2 + $scope.var3;
}, function(newVal,oldVal) {
if ( newVal != oldVal ) {
$scope.count++;
}
if ( $scope.count == 3 )
$window.print();
});
Here I assume, that one variable my be changed only one time, so from undefined to some value. If there is possible to change it twice or more, then this solution doesn't make sense, because here you must know sum of changes before running app.

Related

How to monitor variable change in JavaScript

I receive value from server every 2 seconds. I want to run some code when the value will change. What is the best approach for this problem? Variable from server may change endlessly.
Thanks.
I'm assuming that, for whatever reason, you don't get to run code when you get the value from the server. If you do get to run code, then just compare what you get with what you have and take action if they're different
If you don't get to run code directly, you may be able to run code indirectly if you can control what the server writes to — specifically, if you can change it from yourVar = ... to obj.yourVar = .... If so, then instead of a variable, use an object property with a getter and setter. In the setter, compare the new value to the value you already have and, if it's different, take action:
let value = null;
const obj = {
get yourVar() {
return value;
},
set yourVar(newValue) {
if (value !== newValue) {
value = newValue;
// Take action here
}
}
};
Receive the value from the server using obj.yourVar = ....
I don't recommend it, but it's possible to do this using a global variable, which would let code using yourVar = ... trigger your handler, by creating the accessor property on the global object (which you can access via window on browsers):
let value;
Object.defineProperty(window, "yourVar", {
get() {
return value;
},
set(newValue) {
if (value !== newValue) {
value = newValue;
console.log("New value received: " + value);
}
}
});
// In the code writing to it that you don't control:
yourVar = 1; // Outputs "New value received: 1"
yourVar = 1; // No output, it's not different
yourVar = 2; // Outputs "New value received: 2"

How to implement reverse one time bind ng-if expression in AngularJS?

I have a custom AngularJS component which might be used on a single web page over 200 times. The page ends up implementing over 4000 watchers -- which is more than AngularJS's prefered maximum amount of watchers -- and makes the page really slow.
The actual problem is that there is a lot of unneeded watchers left from some ng-if and other AngularJS expressions inside the component template which no longer where going to change their values.
For normal ng-if's the fix was easy:
<div ng-if="::$ctrl.isInitialized()">Ready!</div>
...where $ctrl.isInitialized() would either return a true (when the component was initialized) or undefined (until it was).
Returning undefined here will make AngularJS keep the watcher active until it returns something else, in this case the value true, and then will add the div in the DOM.
There is no ng-not="expression" like there is ng-hide. This works well with ng-hide, except of course the div is still in the DOM after the controller has been initialized, which is not the perfect solution.
But how can you implement it so, that the <div> will be in the DOM until the controller has been initialized and will be removed after?
Although there is no ng-not directive, it was easy to implement from AngularJS source code:
var ngNotDirective = ['$animate', '$compile', function($animate, $compile) {
function getBlockNodes(nodes) {
// TODO(perf): update `nodes` instead of creating a new object?
var node = nodes[0];
var endNode = nodes[nodes.length - 1];
var blockNodes;
for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
if (blockNodes || nodes[i] !== node) {
if (!blockNodes) {
blockNodes = jqLite(slice.call(nodes, 0, i));
}
blockNodes.push(node);
}
}
return blockNodes || nodes;
}
return {
multiElement: true,
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
$$tlb: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
$scope.$watch($attr.ngNot, function ngNotWatchAction(value) {
if (!value) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = $compile.$$createComment('end ngNot', $attr.ngNot);
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when its template arrives.
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
} else {
if (previousElements) {
previousElements.remove();
previousElements = null;
}
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
previousElements = getBlockNodes(block.clone);
$animate.leave(previousElements).done(function(response) {
if (response !== false) previousElements = null;
});
block = null;
}
}
});
}
};
}];
This is the same implementation as ng-if except it has reverted if (!value) check.
It can be used like this:
<div ng-not="::$ctrl.isInitialized() ? true : undefined">Loading...</div>
It is easy to verify that there is no useless watchers by adding a console.log() in $ctrl.isInitialized() -- this function will be called just few times until it returns true and the watcher is removed -- as well as the div, and anything inside it.
kind of quick patch: angular allows ternary operator in expressions after v1.1.5 I guess.
So you can make something like:
<div ng-if="::$ctrl.isInitialized() === undefined? undefined: !$ctrl.isInitialized()">
As far as I can see undefined does not have special meaning in angular expression - it's treated as another (not defined yet) variable in $scope. So I had to put it there explicitly:
$scope = undefined;
Alternative option is writing short helper:
function isDefined(val) {
return angular.isDefined(val) || undefined;
}
To use it later as
ng-if="::isDefined($ctrl.isInitialized()) && !$ctrl.isInitialized()"
But since you say there are too many places for doing that - for sure making own component as you coded above looks better

Creating custom angular filters

i have a json of items and a barcode scanner, the barcode scanner inputs directly into my app.
Now the items have a primary part number and a secondary part number, more often than not the result from the barcode scanner will be the primary part number, but i have to check with both to be sure.
I'm trying to implement a custom filter to do this, but it doesn't seem to be working, can anyone maybe let me know what i'm doing wrong ?
storeApp.filter('barcodeScanner', function() {
return function(parts, barcode) {
angular.forEach(parts, function (vals, key) {
if( !angular.isUndefined(vals.Part_Number) && vals.Part_Number !== null )
if (angular.equals(vals.Part_Number,barcode))
return parts[key];
});
angular.forEach(parts, function(vals, key) {
if ( !angular.isUndefined(vals.Other_Part_Number) && vals.Other_Part_Number !== null )
if (angular.equals(vals.Other_Part_Number,barcode))
return parts[key];
});
};
});
i then call the filter later in the controller,
$scope.addItemToCart = function() {
$scope.shoppingCart.push($filter('barcodeScanner')($scope.parts, $scope.ItemToAdd));
console.log($scope.Cart.itemToAdd);
console.log($filter('barcodeScanner')($scope.parts, $scope.Cart.itemToAdd));
$scope.Cart.itemToAdd = "";
console.log($scope.shoppingCart);
};
however the result from the filter keeps returning undefined. i know for a fact that the entry i want does exist, because when i use a normal $filter('filter') it works fine, but i cannot risk such a widespread filter for my app.
thanks for any help :)
I believe the problem lies in the forEach part of your function. A forEach function does not return a value. You are returning a value to your iterator function and since the forEach is not returning that returned value from the iterator then you will have nothing to push in your $scope.shoppingCart.push($filter('barcodeScanner')($scope.parts, $scope.ItemToAdd));
Saving to a variable ie. matchedPart declared inside the anonymous factory(wrapper) function and returning it outside of the forEach function should solve the undefined:
storeApp.filter('barcodeScanner', function() {
return function(parts, barcode) {
// declare a variable here
var matchedPart;
angular.forEach(parts, function (vals, key) {
if( !angular.isUndefined(vals.Part_Number) && vals.Part_Number !== null )
if (angular.equals(vals.Part_Number,barcode))
// save it to new variable
matchedPart = parts[key];
});
angular.forEach(parts, function(vals, key) {
if ( !angular.isUndefined(vals.Other_Part_Number) && vals.Other_Part_Number !== null )
if (angular.equals(vals.Other_Part_Number,barcode))
// save it to new variable
matchedPart = parts[key];
});
// return it outside the forEach function
return matchedPart;
};
});
last note:
I would also think you should refactor by combining your forEach functions. Not have 2 separate ones. Combining your isUndefined check with !angular.isUndefined(vals.Part_Number) && !angular.isUndefined(vals.Other_Part_Number) && vals.Part_Number...
Instead of .equals you need to check == because string will not exactly equal to number
angular.equals is nothing but strongly check in javascript === which check both values are equal with their type or not.
if(angular.equals(vals.Other_Part_Number,barcode))
Changed to
if(vals.Other_Part_Number == barcode)
If you want to strictly check then you need to convert both the value to number using parseInt and then check
if(angular.equals(parseInt(vals.Other_Part_Number),parseInt(barcode)))
Hope this could help you. Thanks.

Angular $watch | returning the item from function

I'm interested to find out why i always have to do this
$scope.$watch( function() {
return $scope.someData;
}, function( value ) {
console.log( value );
});
for angular to actually watch the data, why do I have to do this, this is one of the things that really bug me because it looks pointless.
If I do something like this
$scope.$watch($scope.someData, function( value ) {
console.log( value );
});
Which is nicer, it never works?
I also use this a lot with factories
say that $data is a factory I have to do
$scope.$watch( function() {
return $data.someData;
}, function( value ) {
console.log( value );
});
I guess it's worth mentioning that passing a function to $watch is useful when you want to monitor a condition:
$scope.$watch(function() {
return $scope.data.length > 0;
}, function() {
// Do something every time $scope.data.length > 0 changes
});
or
$scope.$watch(function() {
return $scope.prop1 && $scope.prop2;
}, function() {
// Do something every time $scope.prop1 && $scope.prop2 changes
});
This works:
$scope.$watch("someData", function( value ) {
console.log( value );
});
With a factory, you need to watch a function because if you pass a string, Angular will evaluate it as an expression against the $scope. Since $data.someData is not defined on your $scope, it won't work.
To elaborate on #Codezilla's comment, you could assign your factory data to some $scope property, and then watch that:
$scope.data = $data.someData;
$scope.$watch('data', function(newValue) { ... });
Since the how-to-do answer is already given, I'll try to explain you what goes on actually and why it didn't work the way you tried at first time.
First of all this code sure works,
$scope.$watch(function() {
return $scope.someData;
}, function(value) {
console.log(value);
});
But this is NOT the perfect way. To be more precise $watch injects the scope in the function, like this,
$scope.$watch(function(injectedScope) {
return injectedScope.someData;
}, function(value) {
console.log(value);
});
Previously it works because $scope and injectScope are one and the same thing.
Now as this article explains,
Since $scope is passed into the watch function, it means that we can
watch any function that expects the scope as an argument and returns a
value in response. A great example of this is $interpolate
function.
So in your case, we can also make use of $interpolate as following:
$scope.$watch($interpolate("{{someData}}"), function(value) {
...
});
Now this is where we come to the short-hand method of using just a watch expression.
$watch can also accept an expression, which actually is interpolated.
Thus by providing $scope.$watch("someData", ... ),
someData will be:
interpolated as {{someData}}
using the scope injected by $watch function
This is a nice, clean, readable, short-hand way of writing the expression instead of the actual function. But finally after all such compilations, it is ultimately the function which returns a value to watch.

Passing parameters to a jQuery closure not working on Multisuggest plugin

I have a question of which someone might find this much simpler than I do, but alas, I don't have much experience with custom jQuery plugins.
The previous developer at my place of work left me with a lot of left-over plugins that don't seem to work very well, most which I've been able to fix but this which has been bugging me for a while.
It is a custom Multiple Suggestion plugin (called multisuggest) written in jQuery, and it has a set of functions that it uses internally (*e.g. setValue to set the value of the box, or lookup to update the search)*
It seems he's tried to call these plugin functions from an external script (this exteranl script specifically imports newly created suggestions into the multisuggest via user input and sets the value) like this:
this.$input.multisuggest('setValue', data.address.id, address);
This seems to call the function as it should, except the second and third parameters don't seem to be passed to the function (setValue receives nothing), and I don't understand how I can get it to pass these. It says it is undefined when I log it in the console. The functions are set out like this (I've only including the one I'm using and an internal function from multisuggest called select that actually works):
MultiSuggest.prototype = $.extend(MultiSuggest, _superproto, {
constructor : MultiSuggest,
select: function () { // When selecting an address from the suggestions
var active, display, val;
active = this.$menu.find('.active');
display = active.attr('data-display');
val = active.attr('data-value');
this.setValue(display, val, false); // This works, however when I do it as shown in the above example from an external script, it doesn't. This is because it doesn't receive the arguments.
},
setValue : function(display, value, newAddress) { // Setting the textbox value
console.log(display); // This returns undefined
console.log(value); // This returns undefined
if (display && display !== "" &&
value && value !== "") {
this.$element.val(this.updater(display)).change();
this.$hiddenInput.val(value);
this.$element.addClass("msuggest-selected");
}
if(newAddress === false){
return this.hide();
}
},
});
Why does it listen to the function, but not the values passed to it? Do I need to include an extra line of code somewhere to define these arguments?
Anyone with jQuery experience would be of great help! This is bottlenecking progress on a current project. Thanks for your time!
EDIT:
I've missed out the code of how the arguments are trying to be passed from the external script to the internal function of the plugin. Here is the plugin definition with how the external call is handled, can anyone see a problem with this?
$.fn.multisuggest = function(option) {
return this.each(function() {
var $this = $(this), data = $this.data('multisuggest'), options = typeof option === 'object' && option;
if (!data) {
$this.data('multisuggest', ( data = new MultiSuggest(this, options)));
} else if (typeof(option) === 'string') {
var method = data[option];
var parameters = Array.prototype.slice.call(arguments, 1);
method.apply(this, parameters);
}
});
};
The "usual" plugin supervisor looks like this :
// *****************************
// ***** Start: Supervisor *****
$.fn.multisuggest = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
}
};
// ***** Fin: Supervisor *****
// ***************************
All the looping through this should be inside the method functions, not in the supervisor.
I'm a little worried that new MultiSuggest(...) appears in the current supervisor. That sort of thing is totally unconventional. The original author clearly had something in mind.
You need to extend the jQuery plugin function which is attached to $.fn['multisuggest'], that function is probably only taking and passing one parameter.

Categories

Resources