I have a js class that handles some logic, and trying to use it in angular so it can automatically update when the value changes does not work, only when I use angular functions works , the reference is ok, but I sure need something else to tell angular that I need it to listen when the array changes externally.
THANKS!
http://jsfiddle.net/9k2zw1ar/
//create class exposed to window
(function(w){
var Foo=function(){
this.add=function(){
Foo.arr.push('js-'+(Math.random() * 100));
}
}
Foo.arr=['a','b','c'];
w.Foo=Foo;
})(window);
//instantiate one class , used in button
var foo=new Foo;
//angular app
var App= angular.module('App',[]);
//add array reference using "Value"
App.value('arr',Foo.arr);
//angular controller
App.controller('ClientController', ['$scope','arr', function($scope,arr) {
$scope.markers = arr;
$scope.add=function(){
arr.push('ng-'+(Math.random() * 100));
};
}]);
here is the html code
<div ng-app="App" >
<div ng-controller="ClientController">
<div ng-repeat='marker in markers'>
<p> {{marker}}</p>
</div>
<button ng-click='add()'>add NG</button>
<button onclick="foo.add()">add js</button>
</div>
</div>
PROBLEM:
When I change the array with the button "add JS" it does not update automatically. but when I press the "add ng" button the array is shown and updated right away.
The problem is that when you change array from outside of the angular app, it doesn't know that something has changed. Normally you need to notify it, so the scope needs to update its watchers. All you need to do is to trigger digest loop manually, for example by calling $apply method on the root scope.
The only tricky part is that you need to access the scope somehow from outside. To access the root scope of the application you need to call scope method of the angular.element(appRoot) element. Where appRoot is a DOM element on which application is registered (ng-app attribute). For example something like this:
var Foo = function () {
this.add = function () {
Foo.arr.push('js-' + (Math.random() * 100));
this.apply();
};
this.apply = function() {
var appRoot = document.querySelector('[ng-app]');
angular.element(appRoot).scope().$apply();
};
}
Demo: http://jsfiddle.net/9k2zw1ar/1/
Using your existing setup without any substantial refactor it also works this way...
Change this
<button onclick="foo.add()">add js</button>
To this
<button ng-click='addJs()'>add js</button>
Note: change 'addJs' to the name of your liking
Add this line to your controller
$scope.addJs = foo.add;
Angular will update the ng-repeat on the 'arr' array all by itself as it is already attached to scope via $scope.markers.
Here is the working fiddle for THIS example...
jsfiddle Link
As #dfsq point out, using $apply works.
Or instead of using onclick you can use the angular version ng-click. ng-click automatically handles triggering a digest cycle (https://github.com/angular/angular.js/wiki/When-to-use-%24scope.%24apply%28%29).
<button ng-click="foo.add()">add js</button>
For this to work foo needs to be visible on your scope. Adding this to your controller will accomplish that:
$scope.foo = foo;
Updated fiddle: http://jsfiddle.net/kmmuao7u/
Related
I have a client who is based in china and requires specialised captcha that works there. The captcha I need to use is here https://open.captcha.qq.com/
Basically there are 4 steps to get it working:
In the label of html, add this line:
<script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
Add id and property to any DOM element that we want to activate captcha, such as button, div or span. Sample code as below:
<button id="TencentCaptcha"
data-appid="2090807227"
data-cbfn="callback"
>验证</button>
Then create callback function in javascript:
function callback(res){
console.log(res)
if(res.ret == 0){
alert(res.ticket) // ticket
}
}
From the callback, make a POST request to the server to validate the ticket
I'm struggling this to incorporate this into my UI which uses Angular 1.5.6.
My controller is:
.controller('MyCtrl', function($scope) {
$scope.oldCallback = function(){
console.log('in the old callback');
};
$scope.newCallback = function(){
// PASS THIS AS THE CALLBACK TO NEW REGISTER BUTTON
};
})
I have created a CodePen here.
The only way I can get it remotely working is if I pass in a method in the HTML e.g.
<button type="submit" id="TencentCaptcha"
data-appid="2090807227"
data-cbfn="(function(res){alert('res is ' + res)})">
Register
</button>
After clicking Register, the captcha library presents a popup with a challenge to the user. Once completed, the callback passed to data-cbfn is executed. How can I call my controller method from this callback, passing through the result?
I created a global function and was then able to call the correct method in the controller:
function callback(){
var scope = angular.element(document.getElementById("home")).scope();
scope.register();
}
You could also add your function to the window from your angular controller:
.controller('MyCtrl', function($scope, $window) {
$window.callback = function callback(res) {
$scope.register();
};
});
This way you don't have to request the document element which may change scope or id later on.
Also: $compileProvider.debugInfoEnabled(false); will actually disable the functionality to retrieve the scope from a document element like you've done.
You should be turning off the debugInfo functionality in production mode for performance and security reasons.
I have the following piece of code in angular
$scope.prepare = function(){
$scope.elems = [1,2,3];
};
$scope.action = function(){
var elem = $scope.elems[0]; //undefined
}
then, in my view, I use the directive ng-init="prepare()" and attach to a button the action function on click event
<button ng-click="action()">action</button>
Inthe action function the scope hasn't the elems array defined?
Can anybody tell me why this happen?
Thanks!
Since you are not showing the controller or the scope of the HTML where you are calling init() and action(), I can't even guess why you are having problems since the code you have posted works. This is a pluker proving that much: http://plnkr.co/edit/qMzPtJtp9t9CoNKkmWIc?p=preview
<div ng-init="prepare()"></div>
<input type="button" value="Call function" data-ng-click="action()" />
<p>Init Defined: {{elems}}</p>
<p>Function call: {{redefined}}</p>
$scope.prepare = function(){
$scope.elems = [1,2,3];
};
$scope.action = function(){
$scope.redefined = $scope.elems[0]; //undefined
}
With that said, you are not using ng-init() correctly. From the angluar documentation:
"This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat ... and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope."
Link to ng-init documentation: https://docs.angularjs.org/api/ng/directive/ngInit
You will be much better off initializing your array in the controller.
I am new to angularjs. I found the following example somewhere, its working fine. However, I don't understand how the data in the customized directive controller sync up with the factory data. Here is the code:
angular.module("cart",[]).factory("cart", function(){
var cartData = [];
return{
addProduct:function(id, name, price){
cartData.push({count:1, id:id, price:price, name:name})
},
getProduct: function(){
return cartData;
}
};
}).directive("cartSummary", function(cart){
return{
restrict: "E",
template:"cartSummary.html",
controller: function($scope){
var cartData = cart.getProduct();
$scope.totalPrice = function(){
var total = 0;
for(var i=0; i<=cartData.length; i++){
total+= (cartData[i].price * cartData[i] * count);
}
}
}
}
});
and in another module, I have the following code to update the cartData:
angular.module("store", ["cart"]).controller("storeCtrl", function($scope, cart){
/*some logic here*/
$scope.addToCart = function(){
cart.addProduct(product.id, product.name, product.price);
}
});
Here is the view:
<html ng-app="store">
<body ng-controller="storeCtrl">
<!--some html-->
<cart-summary/>
<!--some html-->
<button ng-click="addToCart(item)">Add To Cart</button>
</body>
</html>
The template of directive:
<div class="navbar-text">
{{totalPrice()}}
</div>
I understand the cartData in the factory will get updated every time user clicks the "Add to Cart" button, but I don't get the magic behind the fact of cartData in the factory always sync up with the data in the customized directive controller. How does the function $scope.totalprice() get called every time?
Can someone explain this to me? Thank you so much!
It's actually very simple. In javascript, all objects, arrays, and functions we use are actually used by reference, while the other data types are used by value.
It doesn't matter that you called a getter to get the reference of the object, what does matter is that it is in fact a reference and not the object itself, so when you add a new item you are adding it to the unique source of data.
Try this, on your directive put this code inside a new method and execute it having $scope.cartData as the reference you use on the view:
$scope.cartData = {}; //you are destroying the reference but not the real object
$scope.cartData = cart.cartData; //you will get all your items back on play as it refreshes the reference with the original one
With {{totalPrice()}} everything changes, here angular doesn't know if the result of the totalPrice function could change between two different digest cycles, so the framework must reexecute the function on each cycle to check.
In order not to make the application perform poorly, this kind of interpolations should be avoided, as they are considered a bad practice, specially if the function has heavy logic inside of it. The way to fix this is to precalculate the result of the function and assign it to a new attribute inside the scope, and make the interpolation listen to that attribute instead of executing a function.
Hope this explanation helps!
Cheers!
I have an issue with my angular.js directive.
It should be a kind of autocomplete, in directive's controller property I'm loading an array of values and inside link function compiling template to show the results.
But when I update scope inside link it doesn't reflect on controller and template, please take look at the example here - http://plnkr.co/edit/Lz3QGwklghPo3as2QTqU
Should I apply scope changes or smth similar?
Your code has two problems
Attach click event to document instead of body
Use $apply() inside bind
Below code will resolve your problem
$document.bind('click', function (e) {
scope.results = [];
scope.$apply();
});
I update your $body.bind('click',...) method to
$body.bind('change', function (e) {
scope.results = [];
});
and it seemed to work (I mean that after 0.5 sec I typed a letter, the list of name is re-displayed).
I'm a beginner in angularjs with a few questions about controllers.
Here's my example controller:
function exampleController($scope)
{
$scope.sampleArray = new Array();
$scope.firstMethod = function()
{
//initialize the sampleArray
};
$scope.secondMethod = function()
{
this.firstMethod();
};
};
Here are my questions:
How I can call firstMethod from secondMethod? Is the way I did it correct, or is better way?
How I can create a constructor for the controller? I need to call the secondMethod that call the firstMethod that initialize the sampleArray?
How I can call a specific method from html code? I found ng-initialize but I can't figure out how to use it.
You call a method the same way you declared it:
$scope.secondMethod = function() {
$scope.firstMethod();
};
Which you can also call from HTML like so:
<span>{{secondMethod()}}</span>
But controllers don't really have "constructors" - they're typically used just like functions. But you can place initialization in your controller function and it will be executed initially, like a constructor:
function exampleController($scope) {
$scope.firstMethod = function() {
//initialize the sampleArray
};
$scope.secondMethod = function() {
$scope.firstMethod();
};
$scope.firstMethod();
}
you call the first method by using $scope.
So
$scope.secondMethod = function()
{
$scope.firstMethod();
};
Not really sure what you mean in your second question.
For your third quesiton, you can either have the method run automatically "onload" on controller, OR run it via an front-end angular binding.
e.g.
Run Automatically
function exampleController($scope)
{
$scope.sampleArray = new Array();
$scope.firstMethod = function()
{
//initialize the sampleArray
};
$scope.secondMethod = function()
{
$scope.firstMethod();
};
$scope.secondMethod(); // runs automatically.
};
Run on binding
<div ng-controller="ExampleController"> <!-- example controller set up in namespace -->
<button class="btn" ng-click="secondMethod()">Run Second Method</button>
</div>
#Josh and #Christopher already covered your questions, so I won't repeat that.
I found ng-initialize but I can't know how to use that :-(
The directive is actually ng-init. Sometimes (e.g., if you are starting to use Angular in parts of an application and you still need to dynamically generate a view/HTML page server-side), ng-init can sometimes a useful way to initialize something. E.g.,
<div ng-controller="ExampleCtrl">
<form name="myForm">
<input type="text" ng-model="folder" ng-init="folder='Bob'">
Here's an example where someone needed to use ng-init: rails + angularjs loading values into textfields on edit
I'd also like to mention that controllers are not singletons. If you use ng-view, each time you go to a different route, a new controller is created. The controller associated with the view you are leaving is destroyed, and the controller associated with the view you are going to is executed. So that "initialization code" in a controller could get executed multiple times while an app is running. E.g, if you visit a page, go elsewhere, then come back, the same controller function (and its "initialization code") would be executed twice.
If you want something to truly run once, put it in a service or in a module's config() or run() methods. (Services are singletons, and hence each service is instantiated only once, so initialization code in a service is only run once.)