Getting crazy with angular directive and scope update - javascript

I'm trying for 7 hours now to get something so work and currently I'm pulling my hair out!!!!
Unbelievable!
I just want to update an controller variable from an isolated directive.
And the crazy thing is, in the view everything works perfect, but in the controller he does not recognize the change!
The controller looks like this, because he is generated by TypeScript.
/*
ANGULAR CONTROLLER
*/
var CustomerdataTabCtrl = (function () {
function CustomerdataTabCtrl($scope) {
$scope.AddressAutocompleteOptions = {};
$scope.AddressAutocompleteOptions.watchEnter = true;
$scope.AddressAutocompleteDetails = '';
$scope.AddressAutocompleteAddress = null;
$scope.onRoleChange = function () {
if ($scope.Role == 'Provider') {
$('#orders-tab-header').css('display', 'none');
} else {
$('#orders-tab-header').css('display', 'block');
}
};
$scope.onAddressAutocompleteChange = function () {
alert('asd');
};
$scope.$watch('Role', function () {
return $scope.onRoleChange();
}, true);
$scope.$watch('AddressAutocompleteAddress', function () {
return $scope.onAddressAutocompleteChange();
}, true);
}
return CustomerdataTabCtrl;
})();
/*
ANGULAR DIRECTIVE
*/
angular.module("ngAutocomplete", [])
.directive('ngAutocomplete', function () {
return {
require: 'ngModel',
scope: {
ngModel: '=',
options: '=',
details: '=',
address: '='
},
link: function (scope, element, attrs, controller) {
//options for autocomplete
var opts;
var watchEnter = false;
//convert options provided to opts
var initOpts = function () {
opts = {};
if (scope.options) {
if (scope.options.watchEnter !== true) {
watchEnter = false
} else {
watchEnter = true
}
if (scope.options.types) {
opts.types = [];
opts.types.push(scope.options.types);
scope.gPlace.setTypes(opts.types)
} else {
scope.gPlace.setTypes([])
}
if (scope.options.bounds) {
opts.bounds = scope.options.bounds;
scope.gPlace.setBounds(opts.bounds)
} else {
scope.gPlace.setBounds(null)
}
if (scope.options.country) {
opts.componentRestrictions = {
country: scope.options.country
};
scope.gPlace.setComponentRestrictions(opts.componentRestrictions);
} else {
scope.gPlace.setComponentRestrictions(null);
}
}
};
if (scope.gPlace == undefined) {
scope.gPlace = new google.maps.places.Autocomplete(element[0], {});
}
google.maps.event.addListener(scope.gPlace, 'place_changed', function () {
var result = scope.gPlace.getPlace();
if (result !== undefined) {
if (result.address_components !== undefined) {
scope.$apply(function () {
scope.address = parseGoogleResponse(result.address_components);
scope.details = result;
controller.$setViewValue(element.val());
});
}
else {
if (watchEnter) {
getPlace(result)
}
}
}
});
//function to get retrieve the autocompletes first result using the AutocompleteService
var getPlace = function (result) {
...
};
var parseGoogleResponse = function(components) {
var result = {};
for (var i = 0; i < components.length; i++) {
var addressType = components[i].types[0];
result[addressType] = components[i]['long_name'];
}
return result;
};
controller.$render = function () {
var location = controller.$viewValue;
element.val(location);
};
//watch options provided to directive
scope.watchOptions = function () {
return scope.options
};
scope.$watch(scope.watchOptions, function () {
initOpts()
}, true);
}
};
});
HTML
<div ng-controller="CustomerdataTabCtrl">
<input type="text" id="customerdata_quick_auto_address" name="customerdata_quick_auto_address" class="form-control input-sm" ng-autocomplete ng-model="AddressAutocomplete" options="AddressAutocompleteOptions" details="AddressAutocompleteDetails" address="AddressAutocompleteAddress" />
{{AddressAutocompleteAddress.route}}
</div>
Like I said the view outputs the correct result, but the controller watch does only fire on init once, but then never again, but it is the same fu.... variable. The view {{AddressAutocompleteAddress.route}} uses the controller scope variable, I'm getting really crazy!
Kind regards

Like I mentioned in my comment, this is a problem with the scoping of the variables. The directive's isolate scope is making it's own instance of the AddressAutocompleteAddress variable since it is a "primitive" variable. The way to fix this is to use the "." (dot) notation.
What I would probably do to your code is to create an object called AddressAutocomplete and then add your other variables to that object.
function CustomerdataTabCtrl($scope) {
$scope.AddressAutocomplete = {};
$scope.AddressAutocomplete.Text = '';
$scope.AddressAutocomplete.Options = {};
$scope.AddressAutocomplete.Options.watchEnter = true;
$scope.AddressAutocomplete.Details = '';
$scope.AddressAutocomplete.Address = null;
...
}
And in your html:
<div ng-controller="CustomerdataTabCtrl">
<input type="text" id="customerdata_quick_auto_address"
name="customerdata_quick_auto_address"
class="form-control input-sm" ng-model="AddressAutocomplete.Text"
ng-autocomplete options="AddressAutocomplete.Options"
details="AddressAutocomplete.Details" address="AddressAutocomplete.Address" />
{{AddressAutocomplete.Address.route}}
</div>
Just remember to update the way you use the variables in your controller. Also I don't see where you use the ng-model variable in your directive so I'm not sure why you are using it there.
One more thing, you really should not use the ng- prefix for your directives, that is a reserved prefix for directives from the AngularJs library.

Related

Javascript & knockoutjs: how to refactor the following code to be able to access the properties outside the function

Im struggling to find a way to get the properties Override & Justification available outside of the function. The code is:
self.CasOverridesViewModel = ko.observable(self.CasOverridesViewModel);
var hasOverrides = typeof self.CasOverridesViewModel === typeof(Function);
if (hasOverrides) {
self.setupOverrides = function() {
var extendViewModel = function(obj, extend) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
extend(obj[property]);
}
}
};
extendViewModel(self.CasOverridesViewModel(), function(item) {
item.isOverrideFilledIn = ko.computed( function() {
var result = false;
if (!!item.Override()) {
result = true;
}
return result;
});
if (item) {
item.isJustificationMissing = ko.computed(function() {
var override = item.Override();
var result = false;
if (!!override) {
result = !item.hasAtleastNineWords();
}
return result;
});
item.hasAtleastNineWords = ko.computed(function() {
var justification = item.Justification(),
moreThanNineWords = false;
if (justification != null) {
moreThanNineWords = justification.trim().split(/\s+/).length > 9;
}
return moreThanNineWords;
});
item.isValid = ko.computed(function() {
return (!item.isJustificationMissing());
});
}
});
}();
}
I've tried it by setting up a global variable like:
var item;
or
var obj;
if(hasOverrides) {...
So the thing that gets me the most that im not able to grasp how the connection is made
between the underlying model CasOverridesviewModel. As i assumed that self.CasOverridesViewModel.Override() would be able to fetch the data that is written on the screen.
Another try i did was var override = ko.observable(self.CasOverridesViewModel.Override()), which led to js typeError as you cannot read from an undefined object.
So if anyone is able to give me some guidance on how to get the fields from an input field available outside of this function. It would be deeply appreciated.
If I need to clarify some aspects do not hesitate to ask.
The upmost gratitude!
not sure how far outside you wanted to go with your variable but if you just define your global var at root level but only add to it at the moment your inner variable gets a value, you won't get the error of setting undefined.
var root = {
override: ko.observable()
};
root.override.subscribe((val) => console.log(val));
var ViewModel = function () {
var self = this;
self.override = ko.observable();
self.override.subscribe((val) => root.override(val));
self.load = function () {
self.override(true);
};
self.load();
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

Unable to pass argument to http get request in angular js

I'm pretty much new in angular js. What I am trying to do is pass an integer argument to http get request in my controller. This is how my sample code looks like.
(function() {
angular
.module('myApp.directory', [])
.factory('NewsService', function($http)
{
return {
getallnews: function() {
return $http.get('get_all_news_feed.php?page='+pageNumber);
}
};
})
.factory('NewsFeed', function(directoryService) {
var NewsFeed = function() {
this.items = [];
this.busy = false;
this.pageNumber = 1;
};
NewsFeed.prototype.nextPage = function() {
if (this.busy) return;
this.busy = true;
NewsService.getallnews().success(function(data) {
var itemData = data;
for (var i = 0; i < itemData.length; i++) {
this.items.push(itemData[i]);
}
this.pageNumber++;
this.busy = false;
}.bind(this));
};
return NewsFeed;
})
.controller('MyController', function(NewsFeed, NewsService) {
var inst = this;
inst.news = new NewsFeed();
});
})();
I am building a news feed app. News is fetched from get_all_news_feed.php page and I want to pass a parameter pageNumber to it. This is while implementing infinte scrolling in angular.
I am getting undefined error. Any ideas?
Modify the factory method to accept pageNumber as parameter
getallnews: function(pageNumber) {
return $http.get('get_all_news_feed.php?page='+pageNumber);
}
Pass it when calling the method
NewsService.getallnews(this.pageNumber)

Calling a service from within another service in AngularJS

I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).

JavaScript: Accessing Nested Objects

The code looks like this
function Scripts() {this.FindById = function (id) {
this.FindById.constructor.prototype.value = function () {
return document.getElementById(id).value;
}}}
var Control = new Scripts();
Now when i say Control.FindById("T1").value(). I am not able to get the textInput("T1")'s value.
It seems that your code is a bit more complicated then it should be ;-)
Personally I would write it this way (not tested):
function Scripts() {
this.findById = function(id) {
var el = document.getElementById(id);
return {
value: function() {
return el.value;
}
}
}
}
The findById() now closes over a node and returns an interface that can return its value.
Also, your idea sounds a lot like Singleton, so you wouldn't even need the extra Scripts constructor:
var Control = {
findById: function(id) {
var el = document.getElementById(id);
return {
value: function() {
return el.value;
}
}
}
}
Working example: http://jsfiddle.net/YYkD7/
Try this:
function Scripts() {this.FindById = function (id) {
this.FindById.constructor.prototype.value = function () {
return document.getElementById(id).value
}}}
You didn't close the last "}" :-)

I have no idea how to test this with Qunit?

I want to test this function:
/js/lib/front.js
var Front = function(){
this.onSignUp = function(){
if (!Form.assertInput("email")) {
$("input[name=email]").focus();
this.showHiddenMessage("Email not set.");
return false;
}
}
}
I have in:
/js/lib/form.js
function Form() {
this.assertInput = function (name, defaultValue) {
var text = $("input[name=" + name + "]").val();
if (defaultValue != null) {
if (defaultValue && text == defaultValue)
return false;
}
if(this.trim(text)) return true;
return false;
}
}
This simple test passing:
test("Front", function() {
var front = new Front()
ok(front);
});
But if I write something like this:
test("On Sign Up ", function() {
var front = new Front()
equal(front.onSignUp(),false,"passing test");
});
I have error:
Died on test #1: Form.assertInput is not a function
I don't understand, what I need test in function like this and how include function inside another function?
I've saved a working fiddle here. As a side note, you might want to check out a tutorial on using qUnit, here.One thing that you need to pay attention to is when you're declaring your functions. It's saying Form.assertInput is not a function because you can't access it like that. You need to use the this keyword, which refers to current context. The code should be something like this:
var Form = function () {
//good to have assertInput first if you're using it in a later function
this.assertInput = function (name, defaultValue) {
var text = $("input[name=" + name + "]").val();
if (defaultValue != null) {
//safer to explicitly close your if statements with {}
if (defaultValue && text == defaultValue) {
return false;
}
}
if ($.trim(text)) { return true; }
return false;
};
this.showHiddenMessage = function (message) {
alert(message);
};
this.onSignUp = function() {
//this will point to the current context, in this case it will be Form class
if (!this.assertInput("email")) {
$("input[name=email]").focus();
this.showHiddenMessage("Email not set.");
return false;
}
};
};
Also in the example code that you gave you're missing the Front class. So I created a dummy one in my fiddle like this:
var Front = function() {};
Here are the tests that were run:
$(document).ready(function() {
test("Front", function() {
var front = new Front();
ok(front);
});
test("On Sign Up ", function() {
var form = new Form();
equal(form.onSignUp(), false, "passing test");
});
});

Categories