Angular value function call on compile - angularjs

The Sample Code For The Question Is Here!
I have some calculated data for some fields. And I get the data using the "total" approach in my example. I understand that the function will be called when the controller is initialized once and then again to get the value itself. But what I realized is the "total" function is being called twice for each when I execute the compile function with the button click. Where total has nothing related with the compile or the data which is used in compile. What is the reason these useless(for my case:) ) calls?
// JS CODE
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.val1 = 1;
$scope.val2 = 2;
$scope.serverData = 99;
$scope.total = function() {
console.log('I am total');
return $scope.val1 + $scope.val2;
}
$scope.compileData = function(){
var injector = angular.element($('#serverDataHere')).injector(),
compile =injector.get('$compile');
$('#serverDataHere').append(compile('<div>{{serverData}}</div>')($scope));
};
}
//// HTML CODE
<div ng-controller="MyCtrl">
<table border="1">
<tbody>
<tr>
<td>{{val1}}</td>
<td>{{val2}}</td>
<td>{{total()}}</td>
<td>{{total()}}</td>
<td>{{total()}}</td>
</tr>
<tr>
<td>Computer</td>
<td>Computer</td>
<td>Computer</td>
<td id="serverDataHere"></td>
<td>X Games</td>
</tr>
</tbody>
</table>
<button ng-click="compileData()">Compile Data</button>
</div>

You are kicking off a digest cycle, which runs ALL evaluations on your page. The {{ }}.
On a side note, you should not do DOM manipulation in a controller, that is why directives exist.
There is also never a reason to access the $injector, $compile, or any service directly from the module. You should inject them using the standard injection of Angular.
myApp.controler('controllerName', function($scope, $compile) {
return ctrlFunctions;
});

(Not sure if I understand your question correctly.)
But you can just run your total() function once in the controller, assign it to a scope variable and then bind it to the view.
var totalFcn = function() {
console.log('I am total');
return $scope.val1 + $scope.val2;
}
$scope.total = totalFcn();
Then in your view, just put the total variable instead of the function call.
<td>{{total}}</td>

Related

JSON Data not loading on ng-init on div, it's loading on ng-click on button

I have data attached to $scope object in controller, just like this
$scope.result = [];
$scope.jsonData = function () {
var result = [];
var keys = $scope.data[0];
for (var i = 1; i < $scope.data.length; i++) {
var item = {};
item[keys[0]] = $scope.data[i][0];
item[keys[1]] = $scope.data[i][1];
item[keys[2]] = $scope.data[i][2];
$scope.result.push(item);
}
console.log($scope.result);
};
I am able to access this data only while clicking button in HTML using ng-click directive
<button ng-click="jsonData()">
<table border="1">
<tr ng-repeat="x in result">
<td>{{x.Name}}</td>
<td>{{x.Age}}</td>
<td>{{x.Address}}</td>
</tr>
</table>
</button>
However, I am unable to access json data using ng-init directive. Am I doing anything wrong?
<div ng-init="jsonData()">
<table border="1">
<tr ng-repeat="x in result">
<td>{{x.Name}}</td>
<td>{{x.Age}}</td>
<td>{{x.Address}}</td>
</tr>
</table>
</div>
Why are you using ng-init this way? Take a look at the documentation:
ng-init documentation
This doesn't look like a valid use case for ng-init unless there is more code to your controller I cannot see.
I would get rid of the ng-init on your div and just run the function directly in your controller by calling:
$scope.jsonData();
right after you define the function. Like this:
$scope.result = [];
$scope.jsonData = function () {
var result = [];
var keys = $scope.data[0];
for (var i = 1; i < $scope.data.length; i++) {
var item = {};
item[keys[0]] = $scope.data[i][0];
item[keys[1]] = $scope.data[i][1];
item[keys[2]] = $scope.data[i][2];
$scope.result.push(item);
}
console.log($scope.result);
};
$scope.jsonData();
The likely cause of the problem is that the ng-init directive is executing before the data arrives from the server.
In the controller, chain from the promise that puts the data on scope:
$http.get(url).then(function(response) {
$scope.data = response.data;
}).then(function() {
$scope.jsonData();
});
In an MV* framework, the controller creates the model and then the framework maps the model to the DOM. The framework then collects events from the user and informs the controller which then updates the model.
Having the DOM initiate, by ng-init, a change to the model is a violation of the ZEN of Angular.
It is a very good idea to decouple DOM manipulation from app logic. This dramatically improves the testability of the code. By coupling the app sequencing and logic to the ng-init directive, the code becomes fragile, difficult to test, hard to maintain, and error prone.

Controller inheritance with injection

I'm having a hard time trying to understand the best way to achieve inheritance with my controllers. I've seen a few other posts here about these but I still donĀ“t get some things.
Here's what I have:
- 2 controllers which are 80% similar. I already have a factory which both use to get the data which will be displayed.
- I use the controllerAs notation, with var vm = this
- there's a mix of vars and functions which will be used in the view and therefore are created inside vm, and some other internal vars and functions which are not.
- so I tried to create a single parent controller with all this and then use injection to create these 2 controllers, overwriting only what I need, but this is not working as I expected and also I'm not sure this is the right thing to do
Here is a simplified version of the code.
(function() {
angular
.controller('ParentController', ParentController)
ParentController.$inject = [ '$scope', '$location' ];
function ParentController($scope, $location) {
var vm = this; // view model
var URL = $location.url();
var isDataLoaded = false;
vm.predicate = 'order';
vm.reverse = false;
vm.results;
vm.isDataReady = isDataReady;
vm.setOrder = setOrder;
function isDataReady() {
return isDataLoaded;
}
function setOrder(p) {
vm.reverse = (p === vm.predicate) ? !vm.reverse : false;
vm.predicate = p;
}
$scope.$on('READ.FINISHED', function() {
isDataLoaded = true;
})
}
})();
-
(function() {
angular
.controller('ChildController', ChildController)
ChildController.$inject = ['$controller', '$scope', 'myFactory'];
function ChildController($controller, $scope, myFactory) {
$controller('ParentController', {$scope: $scope});
var TEMPLATE = 'SCREEN';
// ************** M A I N **************
myFactory.getResults(URL, vm);
}
})();
This is now working as I expected.
When I inject ChildController with ParentController, do I really need to inject the $scope? I'm actually using vm. Also, do I need to inject also $location? In this example when I execute my code I'm forced to use var URL = $location.url(); again in my ChildController, I expected to inherite the value from ParentController.
So the thing is, am I only getting values from $scope if I work like this? what about vm? and what about those vars/functions declared outside vm like var isDataLoaded?
I'd appreciate some insight about this. Would this be the right way to do it?
Many thanks.
EDIT: Ok, I found out how to use my ControllerAs syntax with this. Code in the child controller would be like this:
function ChildController($controller, $scope, myFactory) {
$controller('ParentController as vm', {$scope: $scope});
var vm = $scope.vm;
var TEMPLATE = 'SCREEN';
// ************** M A I N **************
myFactory.getResults(URL, vm);
}
But I still need to get a way to also recover the regular var/functions inside the parent controller. Any ideas? Can it be done cleanly?
I recommend you to implement usual javascript's inheritance mechanism between two classes. At ChildController constructor you will execute ParentController constructor and pass to it injected parameters (without using $controller service).
I created simple example (very far from your logic but consider it as patten). I have two controllers: ParentController and ChildController, that inherited from first one. I used only ChildController with $scope, $interval and custom service with name "myservice" (all of them needed only for example). You can see that I used methods and fields from parent and child controllers. Logic of my app is very simple: you can add new items to collection (by means of ParentController) and remove them(by means of ChildController) with logging.
At that case I recommend you use ChildController as ctrl for Data Binding instead of $scope, because it more in line inheritance paradigm (we inherite controllers(ctrl) not $scope).
P.S. If you be going to use inheritance very often I recommend you to use TypeScript - it gives very simple and flexible solution of this problem in c# style.
Controllers.js
function ParentController(myservice, $scope, $interval) {
this.newOne={};
this.lastacivity={};
this.$interval = $interval;
this.items = myservice();
}
ParentController.prototype.Log = function(item){
var self = this;
this.$interval(function(){
console.log(item);
self.lastacivity = item;
}, 100, 1);
}
ParentController.prototype.AddNew = function (Name, Age) {
var newitem = {
name: this.newOne.Name,
age: this.newOne.Age
}
this.items.push(newitem);
this.Log(newitem);
}
function ChildController(myservice, $scope, $interval) {
//Transfering myservice, $scope, $interval from ChildController to ParentController constructor,
//also you can pass all what you want: $http, $location etc.
ParentController.apply(this, [myservice, $scope, $interval]);
}
ChildController.prototype = Object.create(ParentController.prototype)
//your ChildController's own methods
ChildController.prototype.Remove = function (item) {
this.items.splice(this.items.indexOf(item), 1);
this.Log(item);
}
script.js
(function(angular) {
'use strict';
angular.module('scopeExample', []).factory('myservice', function() {
var items = [
{name:"Mike",age:21},
{name:"Kate",age:22},
{name:"Tom",age:11}
];
return function(){
return items;
}
}).controller('ChildController', ['myservice', '$scope', '$interval', ChildController]);
})(window.angular);
HTML
<body ng-app="scopeExample">
<div ng-controller="ChildController as ctrl">
<label>Name</label>
<input type='text' ng-model='ctrl.newOne.Name'/>
<label>Age</label>
<input type='text' ng-model='ctrl.newOne.Age'/>
<input type='button' value='add' ng-click='ctrl.AddNew()' ng-disabled="!(ctrl.newOne.Name && ctrl.newOne.Age)"/>
<br>
<br>
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in ctrl.items">
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<input type='button' value='X' ng-click='ctrl.Remove(item)'/>
</td>
</tr>
</tbody>
</table>
<p>{{ctrl.lastacivity | json}}</p>
</div>
</body>

function not executing on different view

I am new to angular JS. I have created a simple page using ngRoute.
In the first view I have a table, and on clicking it redirects to second view.
But I have called two functions using ng-click the changeView functions is running fine. But the second function fails to execute on the second view.
But Its running fine if using on the first view itself.
Heres the code for first view
Angular.php
<div ng-app="myApp" ng-controller="dataCtr">
<table class='table table-striped'>
<thead>
<tr>
<td>Title</td>
<td>Stream</td>
<td>Price</td>
</tr>
</thead>
<tbody>
<tr ng-repeat = "x in names | filter:filtr | filter:search" ng-click=" disp(x.id);changeView()">
<td >{{ x.Title }}</td>
<td>{{ x.Stream }}</td>
<td>{{ x.Price }}</td>
</tr>
</tbody>
</table>
</div>
heres the second View
details.php:
<div ng-app="myApp" ng-controller="dataCtr">
<div class="container">
SELECTED:<input type="textfield" ng-model="stxt">
</div>
</div>
heres the js file:
var app = angular.module("myApp", ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider
.when('/angular', {
templateUrl: 'angular.php',
controller: 'dataCtr'
})
.when('/details', {
templateUrl: 'details.php',
controller: 'dataCtr'
})
.otherwise({
redirectTo: '/angular'
});
});
app.controller('dataCtr', function($scope ,$http ,$location ,$route ,$routeParams) {
$http({
method: "GET",
url: "json.php"})
.success(function (response) {$scope.names = response;});
$scope.changeView = function()
{
$location.url('/details');
};
$scope.disp = function(id)
{
$scope.stxt = $scope.names[id-1].Title;
};
});
The disp function is working fine on the angular view. But not being routed on the second view. I think the syntax for calling the two views in ng click is correct. OR if there any other method to call the associated table cell value to the second view. Please Help.
After a lots of research i figured it out.I used a factory service
app.factory('Scopes', function ($rootScope) {
var mem = {};
return {
store: function (key, value) {
$rootScope.$emit('scope.stored', key);
mem[key] = value;
},
get: function (key) {
return mem[key];
}
};
});
Added this to JS.
Because The scope gets lost on second Controller ,Services help us retain the scope value and use them in different controllers.Stored the scope from first controller
app.controller('dataCtr', function($scope ,$http ,$location,$rootScope,Scopes) {
Scopes.store('dataCtr', $scope);
//code
});
and loaded in the seconded controller.
app.controller('dataCtr2', function($scope ,$timeout,$rootScope,Scopes){
$scope.stxt = Scopes.get('dataCtr').disp;
});
Second view is not working because you cannot use $scope.$apply() method.
$apply() is used to execute an expression in angular from outside of the angular framework.$scope.$apply() right after you have changed the location Angular know that things have changed.
change following code part ,try again
$location.path('/detail');
$scope.$apply();

ng-repeat ng-click when the click function has already been called earlier in the code

First off, I read the plethora of other questions and answers regarding ng-click, ng-repeat, and child and parent scopes (especially this excellent one.)
I think my problem is new.
I'm trying to call a function using ng-click within a table. The app allows for the sorting of Soundcloud likes. The problem is that when I try to call the ng click function using new data, it still tries to call the function using the old data. Let me explain better with the example:
Controller:
function TopListCtrl($scope, $http) {
$scope.sc_user = 'coolrivers';
$scope.getData = function(sc_user) {
var url = 'http://api.soundcloud.com/users/'+ $scope.sc_user +'/favorites.json?client_id=0553ef1b721e4783feda4f4fe6611d04&limit=200&linked_partitioning=1&callback=JSON_CALLBACK';
$http.jsonp(url).success(function(data) {
$scope.likes = data;
$scope.sortField = 'like.title';
$scope.reverse = true;
});
}
$scope.getData();
$scope.alertme = function(permalink) {
alert(permalink);
};
}
HTML
<div id="topelems">
<p id="nowsorting">Now sorting the Soundcloud likes of <input type=text ng-model="sc_user"><button class="btn-default" ng-click="getData(sc_user);">Sort</button></p>
<p id="search"><input ng-model="query" placeholder="Filter" type="text"/></p>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Song</th>
<th>Artist</th>
<th>Likes</th>
<th>Played</th>
<th>Tags</th>
</tr>
</thead>
<tr ng-repeat="like in likes.collection | filter:query | orderBy:sortField:reverse">
<td width="30%"><a href="{{ like.permalink_url }}">{{like.title}}</td>
(Trying to call it here) <td>{{like.user.username}}</td>
<td>{{like.favoritings_count}}</td>
<td>{{like.playback_count}}</td>
<td>{{like.tag_list}}</td>
</tr>
</table>
</div>
</body>
</html>
So I have that function getData. I use it to load the data in using the soundcloud api. I have a form at the top that uses getData to load a new user's Soundcloud data in. I also call the getData function in the controller so that there is an example on the page upon loading.
The problem is when I try to load a new user's data from a <td> I want to be able to click on the user to see and sort their likes.
How do I 'clear' the function or the global namespace (am I even refering to the right thing)? How can I reuse the getData function with a new variable?
Working Jsfiddle for this
In your getData function you have this line:
var url = 'http://api.soundcloud.com/users/'+ $scope.sc_user +'/favorites.json...
but you are passing in the variable sc_user to your getData function and should be using it like this (no $scope):
var url = 'http://api.soundcloud.com/users/'+ sc_user +'/favorites.json...
That being said... your initial data load fails because you are calling:
$scope.getData();
and not:
$scope.getData($scope.sc_user);

Refresh data on click outside controller in angular

I am trying to grasp the idea behind angular and ran into my first obstacle involving accessing data from outside the scope (the app?)
Here is a very simple example of what I'm trying to do:
HTML:
<div class=logo>
<a href='/refresh'>Refresh</a>
</div>
<div ng-app ng-controller="threadslist">
<div class='thread_list_header'>
<table class='thread_list_table'>
<tr class='table_header'>
<!--1--><td class='table_c1'></td>
<!--2--><td class='table_c2'>{{name}}</td>
<!--3--><td class='table_c3'>Cliq</td>
<!--4--><td class='table_c4'>Last Poster</td>
<!--5--><td class='table_c5'><a class="clickme">Refresh</a></td>
</tr></table>
</div>
<table class='thread_list_table' >
<tr class="thread_list_row" ng-repeat="user in users">
<!--1--><td class='table_options table_c1'></td>
<!--2--><td class='table_subject table_c2' title="">{{user.subject}}</td>
<!--3--><td class='table_cliq table_c3'></td>
<!--4--><td class='table_last table_c4'><span class="thread_username"><a href=#>{{user.username}}</a></span></td>
<!--5--><td class='table_reply table_c5'><abbr class="thread_timestamp timeago" title=""></abbr></td>
</tr>
</table>
</div>
JS:
function threadslist($scope, $http) {
$scope.name = 'Ricky';
// Initialising the variable.
$scope.users = [];
$http({
url: '/cliqforum/ajax/ang_thread',
method: "POST",
}).success(function (data) {
console.log(data);
$scope.users = data;
});
// Getting the list of users through ajax call.
$('.table_header').on('click', '.clickme', function(){
$http.get('/cliqforum/ajax/ang_thread').success(function(data){
$scope.users = data;
});
});
}
This is the part I can't figure out. My logo is supposed to clear whatever filter is on the current 'user' data. However, it sits outside the scope (and I imagine I shouldn't expand the scope to be the entire page?)
I have read something about scope.$spply but can't quite figure out what I'm supposed to do:
$('.logo').on('click', 'a', function() {
scope.$apply(function () {
$scope.users = data;
});
});
It's not quite necessary that I do it THIS way...I would just like to do what is correct!
Thanks!
and I imagine I shouldn't expand the scope to be the entire page?
Why not? That's definitely the way to do it. Just include the logo into the scope and you can then access it from your application, and use ng-click to add a click handler.
In fact, you should avoid using jQuery click handlers within your application. You could transform your JavaScript like so:
$scope.tableHeaderClick = function() {
$http.get('/cliqforum/ajax/ang_thread').success(function(data){
$scope.users = data;
});
});
Then update the HTML like so:
<tr class='table_header' ng-click="tableHeaderClick()">
it is an angular anti-pattern to include DOM elements in controller. you want to use the ng-click directive to respond to click events
see this plnkr:
http://plnkr.co/edit/KRyvifRYm5SMpbVvWNfc?p=preview

Resources