AngularJS: Dynamically change expression after $watch - json

The main thing I want to do here is:
When the form is submitted, a http get request will be done (VerbController)
On success, a JSON string is returned
I copy the JSON string into a factory object (called VerbFactory)
I want to output the content of the JSON string in a div through another controller (OutputController), I took the attribute "name" as an example here.
To achieve this (point 4), I watched for a change in the VerbFactory object and when the JSON string after requesting gets loaded into the object, I want to store it in a variable of the OutputController, so that I can make an expression for it in my HTML.
But it does not work right now. It seems that this.verb is in another scope than the controller scope. I have difficulties understand the difference between $scope and this here, even though I have read a decent amount of articles about the difference between those two.
How do I solve this problem? Do I miss something obvious?
NB: I added some jQuery that puts the attribute "name" of the JSON into a debug div, and it works as expected. But the AngularJS expression {[{outputCtrl.verb["#attributes"]["name"]}]} does not work.
HTML:
<div class="container">
<div class="row">
<div id="debug" class="col-xs-12 col-sm-12">
</div>
<div class="col-xs-12 col-sm-12" ng-controller="OutputController as outputCtrl">
{[{outputCtrl.test}]}
{[{outputCtrl.verb["#attributes"]["name"]}]}
</div>
</div>
</div>
JS:
(function() {
var app = angular.module('LG', []).config(function($interpolateProvider){
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
});
app.factory("VerbFactory", function(){
var json = {};
var available = false;
return {
getJSON: function() {
return json;
},
setJSON: function(newObj) {
angular.copy(newObj, json);
available = true;
},
isAvail: function() {
return available;
},
resetAvail: function() {
available = false;
}
};
});
app.controller("VerbController", ['$http', 'VerbFactory', function($http, VerbFactory){
this.verb = "";
this.requestVerb = function() {
VerbFactory.resetAvail();
var that = this;
$http.get('/request/' + that.verb).
success(function(data) {
VerbFactory.setJSON(data);
}).
error(function() {
});
this.verb = "";
};
}]);
app.controller("OutputController", ['$scope', 'VerbFactory', function($scope, VerbFactory){
this.test = "Test!";
$scope.$watch(VerbFactory.isAvail, function(){
this.verb = VerbFactory.getJSON();
$('#debug').append('<p>'+ this.verb["#attributes"]["name"] +'</p>');
});
}]);
})();

this inside of $scope.$watch callback refers to the callback scope, not the outer scope of OutputController. Use var self = this to refer to the OutputController.
ControllerAs Syntax
OutputController.js
var self = this
$scope.$watch(VerbFactory.isAvail, function(){
self.verb = VerbFactory.getJSON();
//etc
});
Regular Controller Syntax
OutputController.js
$scope.$watch(VerbFactory.isAvail, function() {
$scope.verb = VerbFactory.getJSON();
//etc
});

Related

Angular5 Bindings not working with function call in object [hidden]

I currently have the hidden attribute of one of my divs binded to a boolean in my typescript. But, when I am changing the value of the boolean in one of my function calls nested within an object the dom is not updating on the front end?
typescript
hideSymbols = true;
bindings = {
enter: {
key: 13,
handler: function() {
console.log('enter pressed');
this.hideSymbols = !this.hideSymbols;
console.log(this.hideSymbols);
}
}
};
html
<div [hidden]="hideSymbols">
<button id="equalsBtn" class="symbolBtn">=</button>
<button id="impliesBtn" class="symbolBtn">=></button>
</div>
It works if I am not making the call in this handler but I need to in order for my ngx-quill instance to update how the enter key works. Essentially, why is hideSymbols getting updated but on my web view the element does not disappear and reappear?
Make that:
handler: () => {...
...rather than use function. A function defined using function has its own this.
I'm not sure this is the whole problem without more context, but it's probably at least part of the problem.
this.hideSymbols = !this.hideSymbols; is executing in the wrong scope.
This is what you have:
var result1 = null;
var exhibitA = {
execute: function(){
this.result1 = "hello";
}
};
exhibitA.execute();
console.log({ exhibitA, result1 });
This is what you want:
var result2 = null;
var exhibitB = {
execute: () => {
this.result2 = "hello";
}
};
exhibitB .execute();
console.log({ exhibitB, result2 });

Call angular function inside ng-repeat and return array

I searched for this but I did not get any answer as I want, please give me a solution, I want to use ng-init inside ng-repeat, ng-init should give me different response at every loop here is my HTML
<html>
<body ng-app="crmApp">
<div ng-controller="customerDetailController">
<div ng-repeat="clientDetail in client">
<p>{{clientDetail.name}}</p>
<div ng-init="seoDetails = getCustDetail(clientDetail.name)">
<p>{{seoDetails.cust_Name}}</p>
</div>
</div>
</div>
</body>
</html>
and my js is
<script>
var crmMain = angular.module('crmApp', ['ngRoute','ngMaterial']);
crmMain.controller('customerDetailController',function customerDetailController($scope, $http, customerDetailFactory,$window) {
$scope.client = [];
$scope.init = function () {
$scope.getCustomerData();
};
$scope.getCustomerData = function () {
customerDetailFactory.getCustomerDetailData().then(function
(response) {
$scope.client = response.data;
});
};
$scope.getCustDetail = function (Name) {
var custDetail = [];
custDetail = customerDetailFactory.getCustomerDetailData(Name).then(function (response) {
alert(response.data.cust_Name);
return response.data;
});
return custDetail;
};
$scope.init();
});
crmMain.factory('customerDetailFactory', ['$http', function ($http) {
var factory = {};
var url = 'phpFile/customerDetail.php';
factory.getCustomerDetailData = function (Name) {
return $http({
method: 'POST',
url: url,
data: {
'functionName': 'clientDetailPage',
'customerName': Name
}
});
};
return factory;
}]);
</script>
In inside getCustDetail function I was given alert in there it 'll show name, but I don't know why it not showing in HTML.is anything wrong I did?
I have got one solution for this, I think I have to use Promises for this, but I don't know how to use it can anyone help me in this?
You cannot use ng-init for this purpose.
You've to do the data fetching inside the controller itself. That is like,
customerDetailFactory.getCustomerDetailData()
.then(function(response) {
$scope.client = response.data;
// for each clients, fetch 'seoDetails'
$scope.client.forEach(function(client) {
customerDetailFactory.getCustomerDetailData(client.name)
.then(function (response) {
// I hope response.data contains 'cust_Name'
client.seoDetails = response.data;
})
});
});
Now, in the view, you can directly use the seoDetails property
<div ng-repeat="clientDetail in client">
<p>{{clientDetail.name}}</p>
<p>{{clientDetail.seoDetails.cust_Name}}</p>
</div>

AngularJs #how to pass scope variable in on change event in directive in input file

I am not able to pass data to controller's function through angular directive, directive has one change event. In which i want to pass my dynamic id.
In controller i have myArray
$scope.myArray = [1,2,3,4,5];
I have following html.
<div ng-repeat="data in myArray track by $index">
<input type="file" ng-upload-change="uploadFile($event, $id)" my-id="$index">
<div>
In Controller:
$scope.uploadFile = function($event, $id){
var files = $event.target.files;
console.log("id:"+$id);
};
In directive:
app.directive('ngUploadChange', function() {
return{
scope:{
ngUploadChange:"&",
myId:"="
},
link:function($scope, $element, $attrs){
$element.on("change",function(event){
$scope.ngUploadChange({$event: event, $id : $scope.myId});
})
$scope.$on("$destroy",function(){
$element.off();
});
}
}
});
As you can see that when i pass uploadFile function to ngUploadChange directive, it always pass first id (in this case it is 1) to controllers function.
I am not getting updated id every time.
Thanks in advance.
When you want to pass parameters through the function, you can use "=" instead of "&" for that attr binding, and in your HTML, you can specify like this:
<input type="file" ng-upload-change="uploadFile" ... />
And, I changed the way you were passing an object of params since there is no need to create that object.
Now, if you see the code snippet below, it correctly logs id: x (0-based) on each file upload.
var myApp = angular.module('myApp', []);
//myApp.directive('myDirective', function() {});
myApp.controller('MyCtrl', function MyCtrl($scope) {
$scope.myArray = [1, 2, 3, 4, 5];
$scope.uploadFile = function($event, $id) {
var files = $event.target.files;
console.log("id: " + $id);
};
});
myApp.directive('ngUploadChange', function() {
return {
scope: {
ngUploadChange: "=",
myId: "="
},
link: function($scope, $element, $attrs) {
$element.on("change", function(event) {
$scope.ngUploadChange(event, $scope.myId);
})
$scope.$on("$destroy", function() {
$element.off();
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<div ng-repeat="data in myArray">
<input type="file" ng-upload-change="uploadFile" my-id="$index">
</div>
</div>
It is better to write the directive without isolate scope.
app.directive('ngUploadChange', function() {
return{
/*
scope:{
ngUploadChange:"&",
myId:"="
},*/
link:function($scope, $element, $attrs){
$element.on("change",function(event){
var locals = { $event: event,
$id : $scope.$eval($attrs.myId)
};
$scope.$eval($attrs.ngUploadChange, locals);
});
/*
$scope.$on("$destroy",function(){
$element.off();
});*/
}
}
});
Use $scope.$eval instead of isolate scope bindings.
Isolate scope adds a scope and additional watchers which have been known to cause digest cycle delays that fight the ngModelController.

Angular factory does not return array of objects but a single object

I am new to angular and I am trying to load a CSV list inside a factory and then convert it to json. I am using Papaparse (CSV to json library) inside the factory. When I console log the factory I get the array of objects which is exactly what I want but when I pass it inside my controller I get a single object which holds all the data.
This is my factory
(function() {
var app = angular.module('test');
app.factory('testFactory', ['$http', function($http) {
var url = 'my-list.csv';
var getContact = function() {
return $http.get(url).success(function(data) {
Papa.parse(data, {
header: true,
complete: function(results) {
console.log(results.data);
return results.data;
}
});
});
};
return {
getContact: getContact
};
}]);
}());
And this is my controller
(function() {
var app = angular.module('test');
app.controller('testCtrl', ['$scope', 'testFactory', function($scope, testFactory) {
testFactory.getContact().then(function(data) {
$scope.contacts = data;
console.log(data);
});
}]);
}());
I want be able to do something like this inside my view
{{ contact.firstname }}
The issue is the order of resolution. Inspecting the console statements shows that you're assigning $scope.contacts to the resolution of the $http.get promise, and not the actual parsing.
Instead of returning the $http.get promise, return a deferred promise and resolve at the end of parsing:
var parsePromise = $q.defer();
$http.get(url).success(function(data) {
Papa.parse(data, {
header: true,
complete: function(results) {
console.log(results.data);
parsePromise.resolve(results.data);
}
});
});
return parsePromise.promise;
See working demo here.
Update: As per the comments, you could use .then to chain promises instead of creating a new deferred. The plunkr has both, you can use the changelog to toggle methods.

angular sortable with model data from json

I wanna use angular sortable on my app. But my model is dynamically populated from several json files with $http.get() function. All that ngSortable see from the model is just an empty array. And it won't get the new data from the JSON file. Is there any workaround for this?
$scope.jsons = ["data1.json", "data2.json"];
$scope.abc = [];
angular.forEach($scope.jsons, function(value, key){
$http.get(value).success (function(data){
$scope.abc.push(data);
});
});
$scope.sortableOptions = {
accept: function (sourceItemHandleScope, destSortableScope) {return true}
};
<div ng-model="abc" as-sortable="sortableOptions">
<div ng-repeat="x in abc" as-sortable-item>
<div as-sortable-item-handle>{{x.name}}</div>
</div>
</div>
I had the same problem with ng-sortable: everything worked fine with static data but not with JSON data that come asynchronous from $http.get().
There are two solutions:
Leave the controller as it and, in the html part, replace both occurrences of "abc" with "$parent.abc"
Instead of directly access the 'abc' array, use an intermediate object, like this:
$scope.tmpObject = {};
$scope.tmpObject.abc=[];
...
$http.get(value).success (function(data){
$scope.tmpObject.abc.push(data);
});
...
<div ng-model="tmpObject.abc" as-sortable="sortableOptions">
<div ng-repeat="x in tmpObject.abc" as-sortable-item>
<div as-sortable-item-handle>{{x.name}}</div>
</div>
</div>
use service $q
like
//first make a factory using $q
yourappname.factory("factoryname",function($http, $q){
var _getDetails=function(value){
var deferred = $q.defer();
$http.get(value).then(function(modal){
deferred.resolve(modal);
});
return deferred.promise;
};
return {
_getDetails:_getDetails
};
});
// then in your controller use this factory
$scope.jsons = ["data1.json", "data2.json"];
$scope.abc = [];
/**angular.forEach($scope.jsons, function(value, key){
$http.get(value).success (function(data){
$scope.abc.push(data);
});
});
***/
angular.forEach($scope.jsons, function(value, key){
var promiseData= factoryname._getDetails(value);
promiseData.then(function(result){
if(result.data)
{
$scope.abc.push(result.data);
}
});
});
$scope.sortableOptions = {
accept: function (sourceItemHandleScope, destSortableScope) {return true}
};
<div ng-model="abc" as-sortable="sortableOptions">
<div ng-repeat="x in abc" as-sortable-item>
<div as-sortable-item-handle>{{x.name}}</div>
</div>
</div>