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!
Related
In my controller I want to be notified when a variable value is changed. My requirement is when value of a variable will change a function will be invoked. So I am using $watch. My code is as follow.
var Canvas = angular.module('canvas');
Canvas.controller("excelOperation",function($scope,Data,SharedData,$http){
$scope.tbody=$scope.$parent.shared.previews;
$scope.$watch('$parent.shared.previews',function(newVal,oldVal){
console.log("WORKING");
})
setInterval(function(){
console.log($scope.$parent.shared.previews);
},1000)
/**
* Populate table function to populate excel table
*/
$scope.populateTable=function()
{
}
})
angular.bootstrap(document.getElementById("page"), ['canvas']);
But issue is $watch is working only when I refresh my page. Though the setInterval is printing the changed value of the variable $watch is not being invoked.
N.B. $scope.$parent.shared.previews is an object
What am I doing wrong?
And what I told to achieve, is this a good way to do?
You are watching a object's property change, deep watch is required. Second, to watch parent scope variable, maybe you'd better write like this $scope.$parent.$watch(...).
var deepWatch = true;
$scope.$watch('$parent.shared.previews', function(newVal, oldVal) {
console.log("WORKING");
}, deepWatch);
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 trying to get songs from soundcloud, I am using some input to set value and send it to my factory to get all the related list of songs and display it.
The issue is the the first time all works correctly, but when I am trying to input new values I am getting same results as first time.
My code looks like:
.controller('DashCtrl', function ($scope, SongsService) {
$scope.formData = {};
$scope.searchSong = function () {
SongsService.setData($scope.formData.songName);
};
UPDATE
the factory :
.factory('SongsService', function ($rootScope) {
var List = {};
List.setData = function (tracks) {
var page_size = 6;
SC.get('/tracks', {limit: page_size, linked_partitioning: 1}, function (tracks) {
// page through results, 100 at a time
List = tracks;
$rootScope.$broadcast('event:ItemsReceived');
});
};
List.getItems = function () {
return List;
};
return List;
}).value('version', '0.1');
Thanks for help!
It's hard to tell without a plunkr reproducing the issue and showing all your relevant code, but I think your problem is that you're overwriting the List variable in the async answer, and this List (I assume) is the object you originally returned from your factory.
I see two noteworthy concepts here:
the fact that angular factories are effectively singletons
and that javascript objects are passed by reference-by-value (see call-by-sharing, or one of many stackoverflow discussions).
An angular factory is a singleton, meaning the factory function will only be called once, before the first injection, and every controller it's injected into will work with the same object reference it returned. If you overwrite this object reference, well, the previous value (which the controller has) is still a reference to the original object.
Edit: In fact, by overwriting List you're creating a new object which doesn't even have a setData method anymore!
You probably want to make List private to SongsService, and return a more complex object from the factory that captures List in a closure, and offers some public getter/setter methods for it. (If you insist on replacing the contents of the returned List variable, empty the object and extend it with the new properties - including this method again. But this is much more work, and not a nice solution.)
In Angular Service constructors and Factory methods are singleton objects. You need to return a method that you can call. Your code examples are incomplete so it is hard to tell what is going on. What is returned by your factory method, the List object?
If so, when the first call is completed, it overwrites the List object so that the setData method can't be called a second time. What is the SC object, I can not see in your example how you are injecting it. You probably want to fix that too.
Consider this possible solution.
Service
Songs.$inject = ['$http'];
function Songs($http) {
this.$http = $http;
}
Songs.prototype.getSongs = function(searchTerm) {
return this.$http.get('http://someendpoint/songs', {searchTerm: searchTerm});
}
service('songs', Songs);
Controller
DashController.$inect = ['songs'];
functionDashController(songs) {
this.songs = songs;
this.results = [];
}
DashController.prototype.searchSongs = function(searchTerm) {
var self = this;
this.songs.getSongs(searchTerm).then(function(results) {
this.results = results;
});
}
controller('DashController', DashController);
This is example uses the best practice controllerAs syntax explained here: http://toddmotto.com/digging-into-angulars-controller-as-syntax/
I found the issue,
I got same results all the time because I didnt use cooreclty the api of soundcloud, I didnt send the title on the api... also you are correct, I should not set the list as empty..I should set some value to the list...
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/
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.)