Why is $scope not working in Angular 1.6.1? - javascript

Background
After following the AngularJS tutorial on codeSchool and reading some StackOverflow questions, I decided to start using $scope in order to avoid the hassle of having to define a var self = this; variable.
Problem
The problem is that $scope seems to not be binding anything and nothing works when I use it. I have no idea why, but if I use a var self = this; variable my code will work, even though in theory (according to what I know) $scope should do the same ...
Code
Let's say I have a page where I want to display a big list of numbers. Let's also say I have pagination, and that I want the default amount of Numbers per page to be 4. Let's also assume that after each request to the server, I set the amount of Numbers shown per page to 4 again.
app.js
/*global angular, $http*/
(function() {
var app = angular.module("myNumbers", []);
app.directive("numberFilter", function() {
return {
restrict: "E",
templateUrl: "number-filter.html",
controller: function($scope, $http) {
$scope.filter = {
itemsPerPage: 4
};
$scope.makeRequest = function(numberList) {
console.log("Received submit order");
$http({
method: 'GET',
url: 'https://myNumberServer.com/api/v1/getPrimes',
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
}).then(function successCallback(response) {
numberList= response.data.entries;
$scope.totalPages = response.data.totalPages;
$scope.filter = {itemsPerPage: 4};
console.log("success!");
}, function errorCallback(response) {
console.log('Error: ' + response);
});
};
},
controllerAs: "filterCtrl"
};
});
})();
number-filter.html
<form ng-submit="filterCtrl.makeRequest(myNumbers.numberList)">
<div >
<label for="items_per_page-input">Items per page</label>
<input type="number" id="items_per_page-input" ng-model="filterCtrl.filter.itemsPerPage">
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
There are two expected behaviors that should happen and don't:
When I click submit, I should see in the console "Received submit order" and I don't.
The input element should be initialized with 4 when I load the page.
Both of these behaviors will happen if I use the var self = this; trick and replace all mentions of $scope with self.
Questions:
Why is this not working? Am I missing some closure?

You are using controllerAs syntax so when you use that your model needs to be assigned to the controller object itself, not to $scope
Example
controller: function($scope, $http) {
var vm = this; // always store reference of "this"
// use that reference instead of $scope
vm.filter = {
itemsPerPage: 4
};
vm.makeRequest = function(numberList) {
console.log("Received submit order");
$http({
method: 'GET',
url: 'https://myNumberServer.com/api/v1/getPrimes',
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
}).then(function successCallback(response) {
numberList = response.data.entries;
vm.totalPages = response.data.totalPages;
vm.filter = {
itemsPerPage: 4
};
console.log("success!");
}, function errorCallback(response) {
console.log('Error: ' + response);
});
};
},

Related

Angular watch issue

It's the first time I'm using Angular's watch function and apparently I don't get it to work.
I have a service called apiService, which has a variable myFile. I am injecting the service into my controller and want to watch the apiService.myFile value for a change. Unfortunately the watch only gets called on opening the webpage, not when the apiService.myFile variable actually changes. Here is the code for the watch:
$scope.$watch(function(){return apiService.myFile}, function (newVal, oldVal, scope) {
console.log("service changed: "+newVal +" : "+oldVal+" : "+ scope);
});
Why isn't it being called when myFilechanges?
UPDATE 1:
this is how I update the value of apiService.myFile inside the service
ApiService.prototype.uploadFile = function(fileContent) {
$.ajax({
url: "/space_uploadFile/",
type: "POST",
dataType: "json",
data: {
fileContent: fileContent
},
contentType: "application/json",
cache: false,
timeout: 5000,
complete: function() {
//called when complete
console.log('process complete');
},
success: function(data) {
this.myFile =data;
console.log("this.myFile: "+this.myFile);
console.log('process success');
},
error: function() {
console.log('process error');
},
});
};
I added this inside a plunkr (as you didn't) and this works for me:
Sidenote: next time create an example demonstrating your problem (plunkr) so we can eliminate the case of your problem (e.g. typo).
var app = angular.module('app', []);
app.controller('mainController', function($scope, apiService) {
$scope.changeValue = function() {
apiService.myFile += "!";
};
$scope.$watch(function(){return apiService.myFile}, function (newVal, oldVal, scope) {
console.log("service changed: "+newVal +" : "+oldVal+" : "+ scope);
$scope.someValue = newVal;
});
});
app.service('apiService', function() {
this.myFile = "Test";
});
And the corresponding HTML:
<body ng-controller="mainController">
<h1>Hello Plunker!</h1>
{{someValue}}
<button ng-click="changeValue()">Click</button>
</body>
https://plnkr.co/edit/DpDCulalK1pZ8J0ykh2Z?p=preview
BTW: The $watch part is a copy from your question and it simply works for me.
Edit: Apparantly the OP was using $.ajax to do ajax calls and the value was updated inside the succeshandler (outside the Angular context). So there was no digest cycle triggered here. To fix this you should use the $http service provided by angular (or work your way around it without).
var self = this;
self.$http.post("/space_uploadFile/",
{ fileContent: fileContent },
{
cache: false,
timeout: 5000
})
.then(function (data) {
self.myFile = data;
console.log("self.myFile: " + self.myFile);
console.log('process success');
},
function () {
console.log('process error');
});
Edit2: Apparantly the OP was also using this in the succeshandler to acces variable on the controller. This won't work, so I used the self pattern in the sample above to solve it.
I am used to this syntax:
scope.$watch('name', function(newValue, oldValue) {
scope.counter = scope.counter + 1;
});
where name is the name of your property - in this case myFile

Angular Promise Response Checking

I am doing some http calls in Angular and trying to call a different service function if an error occurs. However, regardless of my original service call function return, the promise it returns is always "undefined". Here is some code to give context:
srvc.sendApplicantsToSR = function (applicant) {
var applicantURL = {snip};
var promise = $http({
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: 'POST',
url: applicantURL,
data: applicant
})
.success(function (data) {
return data;
})
.error(function (error) {
return [];
});
return promise;
};
Then, in the controller:
for (var applicant in $scope.applicants) {
$scope.sendATSError($scope.sendApplicantsToSR($scope.applicants[applicant]), applicant);
}
$scope.sendATSError = function (errorCheck, applicantNumber) {
if (angular.isUndefined(errorCheck)) {
console.log(errorCheck);
AtsintegrationsService.applicantErrorHandling($scope.applicants[applicantNumber].dataset.atsApplicantID);
}
};
However, it is always sending errors because every response is undefined. How can I differentiate between the two returns properly? Thank you!
Looking at angular documentation, the sample code is
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
based on that - your first code snippet should be
srvc.sendApplicantsToSR = function(applicant) {
var applicantURL = {
snip
};
return $http({
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: 'POST',
url: applicantURL,
data: applicant
});
};
As others have said, $http's .success() and .error() are deprecated in favour of .then().
But you don't actually need to chain .then() in .sendApplicantsToSR() as you don't need (ever) to process the successfully delivered data or to handle (at that point) the unsuccessful error.
$scope.sendApplicantsToSR = function (applicant) {
var applicantURL = {snip};
return $http({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST',
url: applicantURL,
data: applicant
});
};
Now, in the caller (your line of code in the for loop), a promise is returned (not data) and that promise will, on settling, go down its success path or its error path. Exactly what happens on these paths is determined entirely by the callback functions you write in one or more chained .thens .
So what you need to write is a kind of inside-out version of what's in the question - with $scope.sendApplicantsToSR() on the outside and $scope.sendATSError() on the inside - and linked together with a .then().
for (var prop in $scope.applicants) {
var applicant = $scope.applicants[prop];
$scope.sendApplicantsToSR(applicant).then(null, $scope.sendATSError.bind(null, applicant));
}
// Above, `null` means do nothing on success, and
// `function(e) {...}` causes the error to be handled appropriately.
// This is the branching you seek!!
And by passing applicant, the error handler, $scope.sendATSError() will simplify to :
$scope.sendATSError = function (applicant) {
return AtsintegrationsService.applicantErrorHandling(applicant.dataset.atsApplicantID); // `return` is potentially important.
};
The only other thing you might want to know is when all the promises have settled but that's best addressed in another question.
You should return your promisse to be handled by the controller itself.
Simplifying:
.factory('myFactory', function() {
return $http.post(...);
})
.controller('ctrl', function(){
myFactory()
.success(function(data){
// this is your data
})
})
Working example:
angular.module('myApp',[])
.factory('myName', function($q, $timeout) {
return function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve('Foo');
}, 2000);
return deferred.promise;
}
})
.controller('ctrl', function($scope, myName) {
$scope.greeting = 'Waiting info.';
myName().then(function(data) {
$scope.greeting = 'Hello '+data+'!';
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ctrl">
{{greeting}}!
</div>

When try to call function from Controller it doesn't respond

I'm trying to call a function in my jointController from other javascript file.
var app1 = angular.module('jointApp',[]);
var createStateBox = function(label,color){
var state = new uml.State({
position: {x: 0, y: 0},
size: {width: 200, height: 100},
name: "<<"+label+">>",
events: [label+" box"],
attrs:{rect:{fill:color},path:{"stroke-width":0}}
});
app1.controller('jointController', function($scope) {
$scope.setDecision(state);
alert("This is reached");
});
paper.model.addCell(state);
}
Here is the code in jointMod.js which contains jointController
var app = angular.module('jointApp', []);
function JointController($scope, $http, $filter) {
$scope.list = [];
$scope.newMsg = 'hello';
$scope.newDecision;
$scope.setMsg = function(msg) {
$scope.newMsg = msg;
}
$scope.sendPost = function() {
var data = $.param({
json: JSON.stringify({
msg: $scope.newMsg
})
});
$scope.setDecision = function(decision){
$scope.newDecision = decision;
alert("one two one two")
console.log(decision);
}
$http({
method: 'POST',
url: 'http://213.65.171.121:3000/decision',
data: $scope.newMsg,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(obj) {
var str = [];
str.push(encodeURIComponent("action") + "=" + encodeURIComponent(obj));
return str.join("&");
}
}).success(function(data, status, header, config) {
$scope.list.push(status);
}).error(function(data, status, header, config) {
$scope.list.push(status);
});
};
};
I have the alert and console log in there to make sure if they can be reach but they do not responed.
The code you provided will not function as you may think. Atleast not if you use both together.
Both codes introduce a new module jointApp and actually only the first one defines a controller. The $scope of that controller is NOT the same of the function in your second code.
If you want to call a method from outside of a controller take a look at events in angular. That would be the best way to archive this. You could also use a dummy object and two-way-bind it (scope: { dummy: '=' }) and then call the method you create on that object in your directive from other code-parts.
Take a look at this plnkr demonstrating both approaches.
http://plnkr.co/edit/YdcB10UpXGqKAxYzXISL?p=preview

AngularJS UI Not updating after $.ajax Success

I'm new to AngularJS and I'm trying to modify the example at http://www.codeproject.com/Articles/576246/A-Shopping-Cart-Application-Built-with-AngularJS
I've implemented a new payment service in the example to call a Json method on my server which works fine, but when I call the method clearItems(); to remove the items from the cart on success it removes the items as expected in the background (as I can see from refreshing the page and the cart it empty) - my problem is that it does not clear the cart in the UI (without refreshing)
If I call this.clearItems(); after the Json call it clears the cart items in the UI as expected, but this is not what I requiquire as I only want to clear the items after success.
Can anyone suggest how I can get this to work?
My Json call is listed below
var me = this;
//this.clearCart = clearCart == null || clearCart;
$.ajax({
cache: false,
type: "POST",
url: '/pos/JsonCashPayment',
contentType: 'application/json',
dataType: "json",
data: JSON.stringify(this.items),
success: function (data) {
me.clearItems();
alert('success function called');
// Do something with the returned data
// This can be any data
}
});
//this.clearItems();
Thanks
Mark
EDIT - Problems running $http
Further to the advice form marck I understand that to do this I need to use $http instead of $json. The problem with doing this is that I need to do this in the shoppingCart.js class (as part of the payments section) which is attached to the controller via app.js (code below). When I try this though I get a JS error that $http doesn't exist.
Is there a way to use $http from the shoppingCart.js class?
app.js
var storeApp = angular.module('AngularStore', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/store', {
templateUrl: 'POS/store',
controller: storeController
}).
when('/products/:productSku', {
templateUrl: 'POS/product',
controller: storeController
}).
when('/cart', {
templateUrl: 'POS/shoppingCart',
controller: storeController
}).
otherwise({
redirectTo: '/store'
});
}]);
// create a data service that provides a store and a shopping cart that
// will be shared by all views (instead of creating fresh ones for each view).
storeApp.factory("DataService", function ($http) {
// create store
var myStore = new store($http);
// create shopping cart
var myCart = new shoppingCart("AngularStore");
controller.js
function storeController($scope, $http, $routeParams, DataService) {
// get store and cart from service
$scope.store = DataService.store;
$scope.cart = DataService.cart;
shoppingCart.js
function shoppingCart(cartName) {
this.cartName = cartName;
this.clearCart = false;
this.checkoutParameters = {};
this.items = [];
// load items from local storage when initializing
this.loadItems();
// save items to local storage when unloading
var self = this;
$(window).unload(function () {
if (self.clearCart) {
self.clearItems();
}
self.saveItems();
self.clearCart = false;
});
}
shoppingCart.prototype.checkoutCash = function (parms, clearCart, $scope, $http) {
// Need to be able to run $http here
$http({
url: '/pos/JsonCashPayment',
method: "POST",
data: this.items,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).success(function (data, status, headers, config) {
}).error(function (data, status, headers, config) {
});
}
To make your approach work, you need to inject $http into your controller, then pass it further to the function you've defined in shoppingcart.js, like so in controller.js:
'use strict';
// the storeController contains two objects:
// - store: contains the product list
// - cart: the shopping cart object
function storeController($scope, $routeParams, DataService, $http) {
// get store and cart from service
$scope.store = DataService.store;
$scope.cart = DataService.cart;
// use routing to pick the selected product
if ($routeParams.productSku != null) {
$scope.product = $scope.store.getProduct($routeParams.productSku);
}
$scope.cart.checkoutCash('parms', 'clearCart', $scope, $http);
}
Obviously, the first two arguments I sent to checkoutCash are filler and need to be replaced with more appropriate values.

AngularJS: Call a particular function before any partial page controllers

I want to call a particular function: GetSession() at the beginning of my application load. This function makes a $http call and get a session token: GlobalSessionToken from the server. This session token is then used in other controllers logic and fetch data from the server. I have call this GetSession()in main controller: MasterController in $routeChangeStart event but as its an asynchronous call, my code moves ahead to CustomerController before the $http response.
Here is my code:
var GlobalSessionToken = ''; //will get from server later
//Define an angular module for our app
var myApp = angular.module('myApp', ['ngRoute']);
//Define Routing for app
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/customer', {
templateUrl: 'partials/customer.html',
controller: 'CustomerController',
resolve: {
loadData: function($q){
return LoadData2($q,'home');
}
}
}).
otherwise({
redirectTo: '/home'
});
}]);
//controllers start here and are defined in their each JS file
var controllers = {};
//only master controller is defined in app.js, rest are in separate js files
controllers.MasterController = function($rootScope, $http){
$rootScope.$on('$routeChangeStart', function(){
if(GlobalSessionToken == ''){
GetSession();
}
console.log('START');
$rootScope.loadingView = true;
});
$rootScope.$on('$routeChangeError', function(){
console.log('ERROR');
$rootScope.loadingView = false;
});
};
controllers.CustomerController = function ($scope) {
if(GlobalSessionToken != ''){
//do something
}
}
//adding the controllers to myApp angularjs app
myApp.controller(controllers);
//controllers end here
function GetSession(){
$http({
url: GetSessionTokenWebMethod,
method: "POST",
data: "{}",
headers: { 'Content-Type': 'application/json' }
}).success(function (data, status, headers, config) {
GlobalSessionToken = data;
}).error(function (data, status, headers, config) {
console.log(data);
});
}
And my HTML has following sections:
<body ng-app="myApp" ng-controller="MasterController">
<!--Placeholder for views-->
<div ng-view="">
</div>
</body>
How can I make sure this GetSession() is always called at the very beginning of my application start and before any other controller calls and also called only once.
EDIT: This is how I added run method as per Maxim's answer. Still need to figure out a way to wait till $http call returns before going ahead with controllers.
//Some initializing code before Angular invokes controllers
myApp.run(['$rootScope','$http', '$q', function($rootScope, $http, $q) {
return GetSession($http, $q);
}]);
function GetSession($http, $q){
var defer = $q.defer();
$http({
url: GetSessionTokenWebMethod,
method: "POST",
data: "{}",
headers: { 'Content-Type': 'application/json' }
}).success(function (data, status, headers, config) {
GlobalSessionToken = data;
defer.resolve('done');
}).error(function (data, status, headers, config) {
console.log(data);
defer.reject();
});
return defer.promise;
}
Even though some of the solutions here are perfectly valid, resolve property of the routes definition is the way to go, in my opinion. Writing your app logic inside session.then in every controller is a bit too much , we're used such approach too in one of the projects and I didn't work so well.
The most effective way is to delay controller's instantiation with resolve, as it's a built-in solution. The only problem is that you have to add resolve property with similar code for every route definition, which leads to code duplication.
To solve this problem, you can modify your route definition objects in a helper function like this:
function withSession(routeConfig) {
routeConfig.resolve = routeConfig.resolve || {};
routeConfig.resolve.session = ['getSessionPromise', function(getSessionPromise) {
return getSessionPromise();
}]
return routeConfig;
}
And then, where define your routes like this:
$routeProvider.when('/example', withSession({
templateUrl: 'views/example.html',
controller: 'ExampleCtrl'
}));
This is one of the many solutions I've tried and liked the most since it's clean and DRY.
You can't postpone the initialisation of controllers.
You may put your controller code inside a Session promise callback:
myApp.factory( 'session', function GetSession($http, $q){
var defer = $q.defer();
$http({
url: GetSessionTokenWebMethod,
method: "POST",
data: "{}",
headers: { 'Content-Type': 'application/json' }
}).success(function (data, status, headers, config) {
GlobalSessionToken = data;
defer.resolve('done');
}).error(function (data, status, headers, config) {
console.log(data);
defer.reject();
});
return defer.promise;
} );
myApp.controller( 'ctrl', function($scope,session) {
session.then( function() {
//$scope.whatever ...
} );
} );
Alternative: If you don't want to use such callbacks, you could have your session request synchronous, but that would be a terrible thing to do.
You have not provided any details related to GetSession. For scenarios like this you should use the resolve property while defining your routes in $routeProvider. I see you are using resolve already.
What you can do now is to wrap the GlobalSessionToken into a Angular service like GlobalSessionTokenServiceand call it in the resolve to get the token before the route loads. Like
resolve: {
loadData: function($q){
return LoadData2($q,'home');
},
GlobalSessionToken: function(GlobalSessionTokenService) {
return GlobalSessionTokenService.getToken() //This should return promise
}
}
This can then be injected in your controller with
controllers.MasterController = function($rootScope, $http,GlobalSessionToken){

Categories

Resources