Angular passes variable as undefined to function - javascript

I'm building an app on top of MEANJS. Here is my view:
<section data-ng-controller="PostsController" data-ng-init="findOne()">
...
<span data-ng-controller="FavsController" ng-init="isFaved(post._id)">
<button type="button" ng-hide="faved[post._id]"></button>
</span>
...
</section>
And here is my function located in FavsController:
$scope.isFaved = function(postId) {
console.log(postId);
// other stuff. create an array named faved etc.
};
Problem is, post._id passes as undefined to function. But when I put say {{ post._id }} in my view, it prints the current post id, no matter where in the html. When I provide post id by typing (I mean like isFaved(123)), it is working and the inner button's ng-hide is also working properly. I'm using same html and same js with some other views, but only this one throws an error. Any ideas why is this happening?

The ng-init is great for pre-setting values, and is intended to be used for aliasing properties inside of an ng-repeat.
What you would really want is to either tie the function call to an appropriate event like...
ng-click="isFaved(post._id)"
Or initialize the values or variables in the controller so you can ensure that they will exist with a value. The use of ng-init in this situation isn't really how it was intended so you can expect the unexpected.

Related

Ionic, List populating via search

I am using a backemnd service (parse in this case but that doesn't really matter for this question) and wanted to simply search it. I have a textbox that upon text being entered searches the server and returns an array of matchs.
My next step is to simply display my returned objects nicely in a list. Easy enough with ng-repeat but because the view has already been loaded the UI won't update to reflect the array being loading into the list. Does that make sense?
I was wondering if there was a technique to Refresh the list and show the returned search elements, and hopefully I am not being to greedy here but doing it in a way that looks good and not clunky.
I did a lot of googling with NO luck :( any advice would be amazing.
Without any code provided it is hard to guess what is wrong. Angular has two-way binding, so view should be updated automatically after changing content of an array. If it's not, it means that you probably did something wrong in your code. I present an example code which should work in this case.
Controller
angular.module('moduleName')
.controller('ViewController', ['ViewService', ViewController]);
function ViewController(ViewService) {
var self = this;
self.arrayWithData = [];
self.searchText = "";
// ---- Public functions ----
self.searchData = searchData;
// Function which loads data from service
function searchData(searchText) {
ViewService.getData(searchText).then(function(dataResponse) {
// Clear the array with data
self.arrayWithData.splice(0);
// Fill it again with new data from response
angular.forEach(dataResponse, function(item) {
self.arrayWithData.push(item);
});
});
}
// --- Private functions ---
// Controller initialization
function _initialize() {
self.searchData(self.searchText);
}
_initialize();
}
View
<div ng-controller="ViewController as view">
<input type="text" ng-model="view.searchText" />
<input type="button" value="Search!" ng-click="view.searchData(view.searchText)" />
<!-- A simple ngRepeat -->
<div ng-repeat="item in view.arrayWithData">
<!-- Do what you want with the item -->
</div>
</div>
Conclusion
By using splice() and push() you make sure that reference to your array is not changed. If you are using controllerAs syntax (as in the example), assigning new data with '=' would probably work. However, if you are using $scope to store your data in controller, losing reference to the array is the most probable reason why your code doesn't work.

pathFor for iron router meteor

I am pretty confused about this issue.
I have template which has two paths as follows:
Router.route('/companyDataManagement',{
path:['/companyDataManagement','/companyDataManagement/:_id'],
name: 'companyDataManagement',
yieldTemplates:{
'companyData':{to:'showCompanyData'},
'companyDetails':{to:'showCompanyDetails'}
}
});
This works perfectly fine. But how do I use pathFor for this template.
Click does not work
Can you confirm if the companyDataManagement in the link is a name being passed from a helper or if you intend this to be the name of the route called? if it is the latter it needs to be encapsulated in single quotation marks like below
Click
If you want to then pass the :_id into the pathFor this comes from the data context which the link is in, if the data context does not supply the id you need to declare an object to pass into the template inside a helper:
Template.yourTemplate.helpers({
myContextHelper: function(){
return {_id:'XXXXXXXXX'}
}
});
{{#with myContextHelper}}
Click
{{/with}}
Which should give you /companyDataManagement/XXXXXXXXX
You can also pass in the query, hash and data variables using for example query="q=1" or query=qstring where qstring is an object from a helper or a field in the myContextHelper object.
Click
Additionally and not strictly to do with the question but is hopefully helpful, it looks from your code like you are just having the :id as an optional route part in your path and that the templates themselves do not require an :_id to be specified, in which case you can just use a ? to make the part optional:
path:'/companyDataManagement/:_id?',
You can also use this for your opening argument for the route to eliminate having to specify the path in the function:
Router.route('/companyDataManagement/:_id?',{
Hope this helps! Let me know if the above doesn't work happy to help troubleshoot if you can post a bit more of the code surrounding it

How to take value from input and take it to AJAX request link?

So I have done this AJAX request with angularJS:
http://jsfiddle.net/c0Lkja0h/1/
When I use link like for example $http.get('http://api.wunderground.com/api/KEY/forecast/geolookup/conditions/q/San_Francisco.json') (without '+ city +' in the link) it works great. But when I add that variable, add to ng-model="city" and hit submit, then it says
city is not defined
How can I make it take city name from that input and use it in my newAJAXreq function when I click "Find Weather" button ? Also it would be cool that default link would be with static link (when person first visited the site), and after that city is taken from input if user wants. THANKS!
There are a couple of things wrong with your approach.
Don't name your controller. It should be an anonymous function that take as parameters the modules you want to inject, such as .controller(function($http){ //do something }); or .controller(['$http',function($http){ //do something }]) to avoid losing reference to your variables when you minify your js files.
Inside your controller, define the function you want that takes as parameter the city, such as $scope.sendAjax = function(city){ //do something }. In your view you are gonna call this function with only the argument you need ng-submit="sendAjax(city)"
Avoid using scope. From the great book AngularJS:Up and Running, by Shyam Seshadri and Brad Green:
If you used AngularJS prior to 1.2, you might have expected the $scope variable to be injected into the controller, and the variables helloMsg and goodbyeMsg to be set on it. In AngularJS 1.2 and later, there is a new syntax, the controllerAs syntax, which allows us to define the variables on the controller instance using the this keyword, and refer to them through the controller from the HTML.
The advantage of this over the earlier syntax is that it makes it explicit in the HTML which variable or function is provided by which controller and which instance of the controller. So with a complicated, nested UI, you don’t need to play a game of “Where’s Waldo?” to find your variables in your codebase. It becomes immediately obvious be‐ cause the controller instance is present in the HTML.
The alternative is in your view use the controller as syntax: "ng-controller="WeatherController as weather", and then always refer to the alias when you need to access Weather Controller scope, such as ng-submit="weather.sendAjax()".
In your controller, you will assign the function to this, as in this.sendAjax = function(){ \\do something }. A good practice is to assign this to another variable, since this can get overriden. So in the first line of your controller you can do var self=this and refer to the controller as self from this point on, as in self.sendAjax = function(){ \\do something }

If I set something in the controller, how do I access it in the view?

I created this in a the company controller.
$scope.$watch('companyName', function () {
console.log($scope.company.name);
});
I wanted to get the company name, how do I call it where the ng-controller matches the controller name in the view?
Would it be something along the lines of this?
<span ng-init="companyName"> {{ companyName() }} </span>
I'm not sure I understand how to get the information from that function in the view. I think I've confused myself.
Yes, your sample seems a bit strange.
Anyway, to bind a $scope property in the view, you use its name.
So in the controller:
$scope.company = { name: "Acme" };
And in the view:
<span> {{ company.name }} </span>
There is no need to create a $watch manually. Angular will create a watch for any property used in the view (for you).
You also, don't have to use ng-init, unless you want to initialize something in the view.
To see the watch in action, simple add a text input next to the span:
<input type="text" ng-model="company.name"/>
If you change the value in the text box, you will see the changes reflected in the span.
Here is a plunker to play with that demonstrates.
I think you have confused yourself!
In the company controller all you need to do is:
$scope.companyName = "ACME";
To access it in the view:
{{ companyName }}
When binding to values, it is assumed to be relative to $scope. In this case (assuming the value you want is the same as what you specify in your $watch), you want to init an item to an object, and it should look something like this:
<span ng-init="company = {name: 'whatever' }">{{company.name}}</span>
For various reasons, it's not recommended to use ng-init except in things like ng-repeat. If it works, it works, but I'd recommend getting used to initializing things in your controller (just add the statement, such as $scope.company = {name: 'whatever'}; somewhere before your controller function ends). One reason is to make it easier to test your controller in unit tests, because it's annoying to make your tests dependent on a particular view).
The simplest was to do it:
controller:
$scope.companyName = "My company";
view:
<span>{{companyName}}</span>
$scope.$watch is just a function to monitor changes in the value of CompanyName, I don't think you need it.

KnockoutJS data update writes internal function to UI

I'm having this odd issue when I update my viewmodel...basically with every update, there appears to be a random chance that each observable will contain this data:
function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
observable.valueHasMutated();
}
return this; // Permits chained assignments
} else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return _latestValue;
}
}
I've been using KnockoutJS for a while, and I've never seen anything like this. My guess is that it has something to do with my template binding, but I'm really not sure. I'm going to dig into it, but I figured I'd post it here in case anyone else is having this issue, or has a solution. Like I said, it doesn't happen consistently, only on occasion.
//// More Information ////
So Matt below referenced this (http://stackoverflow.com/questions/9763211/option-text-becomes-a-function-string-after-updated-with-fromjs), which is roughly the same issue. The only difference is that I'm using the native template binding in a style like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<!--ko foreach: $data-->
<div data-bind="text: title"></div>
</script>
It was my assumption that KnockoutJS handled the unwrapping by itself when you pass the observableArray into the template binder. I know I can't say "title()" in this example, because that doesn't exist. Am I supposed to be binding with a command like $root.title()?
//// Even More Information ////
It appears that this problem occurs as a result of having two "applyBindings" on one page. My application contains an external widget which adds it's DOM to the host page DOM at runtime. That widget is using the ko.applyBindings(vm, ROOTNODE) syntax which should allow for the host page to run it's own ko.applyBindings(hostVm).
In fact, it does, and it works correctly every refresh. The problem however is when the host page does a viewModel update with no refresh. Somehow, the UI rendering spits out this internal function on EVERY data-bound node. I've debugged through KnockoutJS and actually confirmed that the viewModel and rootNode are correct...something outside of the actual binding is taking over.
This has something to do with the "()" appended onto the data object in the template. What I've found is that during the first render (page load) writing the template like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<div data-bind="text: title"></div>
</script>
works just fine. However, once you run the update on the observableArray my "title" object becomes that function. If I write the template using this style:
<div data-bind="text: title()"></div>
It seems to work on every update.
I am not certain why this is the solution. From the looks of it, the data object being passed to the Knockout binder is the exact same on both page load and update. I'll post this as an answer, but I'm not marking it as an answer until I understand why this is happening.

Categories