Angular.js missing $scope update on page load - angularjs

I've got a bit of an interesting situation where I have a weather app, and on loading the app, I want to get the users last position, and the last forecast they saw, and display that, while the app goes and gets the updated forecast.
In my controller, I construct the forecast view like this
function constructWeather(forecast){
if(!forecast.data.list){
$scope.loading='weather-error';
return;
}
var today = forecast.data.list[0];
console.log($scope.date);
var date = $scope.date;
$scope.print_date = $filter('date')(date,'EEEE, MMMM, d');
$scope.print_time = $filter('date')(date,'h:mm a');
$scope.weather = formatWeather(today,date,true);
$scope.city = $scope.position.name || forecast.data.city.name;
$scope.forecast = (function(){
var day_list=[];
for(var d=1; d<6; d++){
var day_weather = formatForecast(forecast.data.list[d],date,false);
day_list.push(day_weather);
}
return day_list;
})();
$scope.loading = 'weather-loaded';
}
This works fine when getting a new weather forecast, but when I am trying to reload the old forecast, all the $scope.print_date, $scope.weather, etc. etc. work fine, but ONLY the $scope.forecast the one executed within the anonymous function, does not display.
I've output to the console, and I see the day_weather is being built, but angular isn't updating.
In the template, it is loaded via
<div class="day" ng-repeat="day in forecast">
<div class="date">{{ day.print_date }}</div>
<div class="long-day">{{ day.long_day }}</div>
<div class="img {{weather.conditions | lowercase}}"></div>
<div class="temps">
<span class="degrees">{{ day.weather.temps.max }}</span>
<span class="min degrees">{{ day.weather.temps.min }}</span>
</div>
</div>
and of course, no divs are added to the page.
I've tried to run a $scope.$apply but it returns an error that I'm already in a digest cycle.
Is there another way to tell Angular that the $scope.forecast has new data?

Using closure like you did, works like that:
function declaration: (function() {...})
function execution: ()
So to the $scope.forcast the result of 2. is assigned. It is a completely static object.
If you want to execute this function many times, declare it this way:
$scope.forecast = function(){ ... };
and in you mark-up use it like that:
ng-repeat="day in forecast()"
PLNKR that shows it in action!
UPDATE
$scope.forecast = (function(){...})() can be replaced with:
$scope.getForecast = function () {...};
$scope.forecast = $scope.getForecast();
Then in your mark-up you can do something like that:
ng-repeat="day in (forecast=getForecast())"
For sure, it cannot be anything worse than what you had previously. If there is still some delay due to -- for example -- some asynchronous call, you should manually synchronize properties you want to be updated together.

Related

Is there any way to load individual data and display it rather loading and display all data using angularjs?

Consider I have 10 locations and I have to display each location's weather conditions. It is easy to fetch all locationids and feed it to a function to get all 10 location's data at a time and display it. But I want to load individually from server side and feed it to angular section. ie when first location data loaded then display it then second and so on.. Is that possible?
This is my angular code. This working fine. I want to change to above mentioned logic
var locations = [1,2,3,...,10];
locationService.UpdateDashBoard(locations).then(function (result) {
$scope.results.push(result.data);
});
And Html code is
<li gridster-item="widget" ng-repeat="widget in results">
<div class="text-center wrap-text">
<span ng-show="!editMode">{{ widget.name }}</span>
<label style="cursor: move" ng-show="editMode">{{ widget.name }}</label>
<div class="pull-right" ng-show="editMode && widget.Source">
Location - {{widget.location}}
temperature - {{widget.Source.temperature}}
</div>
</div>
</li>
While your locationService.UpdateDashBoard function may accept multiple ids, as you stated in your question, passing a list of ids will result in all data being returned at once. Given your requirement to load each location individually, you could simply call the service once for each id:
var locations = [1,2,3,...,10];
locations.forEach(function(location) {
locationService.UpdateDashBoard([location]).then(function (result) {
$scope.results.push(result.data);
});
});
After each call responds the location's data is pushed in the $scope.results array. This is where angular's magic kicks in...as you have bound the ng-repeat directive to your results array - the UI will automatically update after each new location is added.
It sounds like that you wanted to do a sequencing request by chaining each of the id one after another. I would recommend to use $q to do that.
var locations = [1,2,3,...,10];
// create a "start" promise
var promiseChain = $q.when(function(){});
// loop each locations
locations.forEach(function (location) {
// promisify the locationService function
var promiselink = function () {
var deferred = $q.defer();
locationService.UpdateDashBoard([location])
.then(function (result) {
$scope.results.push(result.data);
deferred.resolve()
});
return deferred.promise;
}
// promiseChain will wait until promiselink return with the promise
promiseChain = promiseChain.then(promiselink);
})
The promiseChain.then(promiselink); should do the trick since it waits for promise to be resolved from promiselink function.
If I understand this correctly, you want to get all data in one hit (results) but add them to the DOM incrementally. I'm not sure MS_AU's answer will provide the right functionality you need - it looks like it will call the server for every id, but pass the entire array and returns all data from the server every time. You'd end up with 100 items in $scope.results.
Edit: If my understanding of your question is correct you want to call the server for each id, so you should change your service method to accept one id, and iterate over the ids and call the function.
If you return the promise in a private function you can call .then() inside the forEach loop and push the result. If you don't return the $promise in the service function, you'll need to handle the .$promise in the controller.
var locations = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var updateDashboard = function(id) {
return locationService.UpdateDashBoard(id);
};
locations.forEach(function(id) {
updateDashboard(id).then(function (result) {
$scope.results.push(result.data);
});
});
And your HTML:
<li gridster-item="widget" ng-repeat="widget in results">
<!-- fade is just an ng-animate class I picked, you can choose your own or none -->
<div class="text-center wrap-text fade">
<span ng-show="!editMode">{{ widget.name }}</span>
<label style="cursor: move" ng-show="editMode">{{ widget.name }}</label>
<div class="pull-right" ng-show="editMode && widget.Source">
Location - {{widget.location}}
temperature - {{widget.Source.temperature}}
</div>
</div>
</li>

ng-repeat only updating after clicking another tab or typing in a textbox

I'm pretty new to angular and have been having a bit of a problem in trying to create a basic "to-do" list sort of app.
There are various categories in the sidebar, and the user can click a button that brings up a modal prompting the user for the name of a new category. This name is used to create a new category, which is pushed onto the preexisting array.
However, the new category is only appearing after I start typing in another text-box on the screen or click on another tab.
The code that should be relevant:
var list = this;
$(document).on("click", ".prompt", function(e) {
bootbox.prompt("What do you want your new category to be?", function(result) {
if(result !== null) {
list.addCategory(result);
}
});
});
this.addCategory = function(result) {
if(result.trim() != "") {
var newCategory = new Category(result);
list.categories.push(newCategory);
this.setCategory(newCategory);
}
};
I can't seem to figure out how to post HTML as a code block, but here's the directives used to list out the categories (with categoryCtrl being the controller in question): ng-class="{active: categoryCtrl.isSet(category) }" ng-repeat="category in categoryCtrl.categories" ng-init="categoryCtrl.currCategory = categoryCtrl.categories[0]"
I know that the array is being updated immediately - if I add an alert 'bootbox.alert(list.categories[list.categories.length-1].name)' the alert gives me whatever the input was like it's supposed to. It's just not showing up in the ng-repeat.
Another interesting observations is that the ng-init overrides the this.setCategory(newCategory) - so it seems that when the list does update, it is reverting to its ng-init value.
Other places where I have an ng-repeat of an array, it's updated automatically when something new is pushed onto it. I'm wondering if it may have something to do with the modal/usage of bootbox - everywhere else things are added either by a checkbox or keying something into a textbox on screen, this is the only place where a modal is used.
Here is a working plunker based on your code.
The app looks like below. I initialize an array with dummy data for the example, but an empty array would work too. I used the vm = this syntax similar to what you have. When calling $nbBootbox.prompt it returns a promise so you need to use the then() syntax like below:
var app = angular.module('plunker', ['ngBootbox']);
app.controller('MainCtrl', ['$scope', '$ngBootbox', function($scope, $ngBootbox) {
var vm = this;
vm.name = 'World';
vm.categories = ['Category 1', 'Category 2'];
vm.prompt = function() {
$ngBootbox.prompt('Enter a new category?')
.then(function(result) {
console.log('Prompt returned: ' + result);
vm.categories.push(result);
}, function() {
console.log('Prompt dismissed!');
});
}
}]);
To make your HTML more angular like I changed it to this and also use the ControllerAs syntax:
<body ng-controller="MainCtrl as vm">
<p>Hello {{vm.name}} !</p>
<ul>
<li ng-repeat="c in vm.categories">{{c}}</li>
</ul>
Add Category
</body>
So, the link calls the prompt() function... it opens the modal and if you enter in the category, I push it to the categories array and it is added automatically to the page as a new bullet point in the list of categories.
From the documentation:
$ngBootbox.prompt(msg)
Returns a promise that is resolved when submitted and rejected if dismissed.
Example
$ngBootbox.prompt('Enter something')
.then(function(result) {
console.log('Prompt returned: ' + result);
}, function() {
console.log('Prompt dismissed!');
});
Hope this helps. let us know.

FirebaseArray changing $id? [duplicate]

Here is the relevant code in my view:
p(ng-repeat="t in todos")
input(
type="checkbox",
ng-model="t.done",
ng-click="clearItem($event)"
)
{{t.text}} done? {{t.done}}
When the checkbox is clicked, I want the appropriate object in the todos array to be removed from the database.
My clearItem function is as follows:
$scope.clearItem = function(event) {
todoRef.remove($scope.t);
}
However, this removes all the entries in my database. I want it to remove only the specific object in question. Is there anyway for me to do this?
Ok, figured it out.
When looping using ng-repeat, use (id, t) in todos. This allows you to send id as the parameter to the ng-click function, and $scope.todos.$remove(id) works just fine.
To provide a more complete example for anyone else that lands here, according to Firebase's documentation for AngularFire this would be the preferred way, and I believe the easiest way to remove an object:
// Create an app. Synch a Firebase array inside a controller
var myApp = angular.module("myApp", ["firebase"]);
// inject $firebaseArray
myApp.controller("TodoCtrl", ["$scope", "$firebaseArray", function($scope, $firebaseArray) {
// bind $scope.todos to Firebase database
$scope.todos = $firebaseArray(myFirebaseRef.child("todo"));
// create a destroy function
$scope.removeTodo = function(todo) {
$scope.todos.$remove(todo);
};
}]);
In your view, you could do something like below. Note that you could bind the removeTodo function to a checkbox as the question specifies, or a regular old <a href> element:
// In your view
<div ng-controller="TodoCtrl">
<ul>
<li ng-repeat="todo in todos">
{{ todo.text }} : <a href ng-click="removeTodo(todo)">X</a>
</li>
</ul>
</div>
Hope that helps!
A better solution would be to have $scope.clearItem() take the object t as an argument, instead of $event.
HTML - <p ng-repeat="t in todos"><input... ng-click="clearItem(t)">
JS - $scope.clearItem = function(obj) {todoRef.$remove(obj)};
The only way I'm able to remove the item is using a loop on the array we get from firebase.
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
var arr_ref=$firebaseArray(ref);
for(var i=0;i<arr_ref.length;i++){
if(key==arr_ref[i].$id){
console.log(arr_ref[i]);
arr_ref.$remove(i);
}
}
The easiest way to remove the object would be
scope.clearItem = function(event) {
todoRef.$loaded().then(function(){
todoRef.$remove($scope.t)
});
The asynchronous nature of the beast has gotten me a few times.

multiple inputs based on array

My angular experience is basically about 3 days part time, so there's probably something simple I'm missing here.
I'm trying to create a dynamic list of multiple inputs based on an array, which I then want to reference from elsewhere in the app. What I've tried is loading a template from a custom directive, then $compile-ing it.
<input data-ng-repeat="term in query" data-ng-model="term">
My controller contains $scope.query = [""] which successfully creates the first empty input box. But the input box doesn't seem to update $scope.query[0] when I modify it. This means that when I try to create another empty input box with $scope.query.push(""); (from a keypress listener looking for the "/" key) I get a "duplicates not allowed" error.
I've tried manually listening to the inputs and updating scope.$query based on their value, but that doesn't feel very "angular", and results in weird behaviour.
What do I need to do to link these values. Am I along the right lines or way off?
I made a simple jsfiddle showing how to use an angular model (service) to store the data. Modifying the text inputs will also modify the model. In order to reference them somewhere else in your app, you can include TestModel in your other controllers.
http://jsfiddle.net/o63ubdnL/
html:
<body ng-app="TestApp">
<div ng-controller="TestController">
<div ng-repeat="item in queries track by $index">
<input type="text" ng-model="queries[$index]" />
</div>
<br/><br/>
<button ng-click="getVal()">Get Values</button>
</div>
</body>
javascript:
var app = angular.module('TestApp',[]);
app.controller('TestController', function($scope, TestModel)
{
$scope.queries = TestModel.get();
$scope.getVal = function()
{
console.log(TestModel.get());
alert(TestModel.get());
}
});
app.service('TestModel', function()
{
var queries = ['box1','box2','box3'];
return {
get: function()
{
return queries;
}
}
});

AngularJS - Using a Service as a Model, ng-repeat not updating

I'm creating an ajax search page which will consist of a search input box, series of filter drop-downs and then a UL where the results are displayed.
As the filters part of the search will be in a separate place on the page, I thought it would be a good idea to create a Service which deals with coordinating the inputs and the ajax requests to a search server-side. This can then be called by a couple of separate Controllers (one for searchbox and results, and one for filters).
The main thing I'm struggling with is getting results to refresh when the ajax is called. If I put the ajax directly in the SearchCtrl Controller, it works fine, but when I move the ajax out to a Service it stops updating the results when the find method on the Service is called.
I'm sure it's something simple I've missed, but I can't seem to see it.
Markup:
<div ng-app="jobs">
<div data-ng-controller="SearchCtrl">
<div class="search">
<h2>Search</h2>
<div class="box"><input type="text" id="search" maxlength="75" data-ng-model="search_term" data-ng-change="doSearch()" placeholder="Type to start your search..." /></div>
</div>
<div class="search-summary">
<p><span class="field">You searched for:</span> {{ search_term }}</p>
</div>
<div class="results">
<ul>
<li data-ng-repeat="item in searchService.results">
<h3>{{ item.title }}</h3>
</li>
</ul>
</div>
</div>
</div>
AngularJS:
var app = angular.module('jobs', []);
app.factory('searchService', function($http) {
var results = [];
function find(term) {
$http.get('/json/search').success(function(data) {
results = data.results;
});
}
//public API
return {
results: results,
find: find
};
});
app.controller("SearchCtrl", ['$scope', '$http', 'searchService', function($scope, $http, searchService) {
$scope.search_term = '';
$scope.searchService = searchService;
$scope.doSearch = function(){
$scope.searchService.find($scope.search_term);
};
$scope.searchService.find();
}]);
Here is a rough JSFiddle, I've commented out the ajax and I'm just updating the results variable manually as an example. For brevity I've not included the filter drop-downs.
http://jsfiddle.net/XTQSu/1/
I'm very new to AngularJS, so if I'm going about it in totally the wrong way, please tell me so :)
In your HTML, you need to reference a property defined on your controller's $scope. One way to do that is to bind $scope.searchService.results to searchService.results once in your controller:
$scope.searchService.results = searchService.results;
Now this line will work:
<li data-ng-repeat="item in searchService.results">
In your service, use angular.copy() rather than assigning a new array reference to results, otherwise your controller's $scope will lose its data-binding:
var new_results = [{ 'title': 'title 3' },
{ 'title': 'title 4' }];
angular.copy(new_results, results);
Fiddle. In the fiddle, I commented out the initial call to find(), so you can see an update happen when you type something into the search box.
The problem is that you're never updating your results within your scope. There are many approaches to do this, but based on your current code, you could first modify your find function to return the results:
function find(term) {
$http.get('/json/search').success(function(data) {
var results = data.results;
});
//also notice that you're not using the variable 'term'
//to perform a query in your webservice
return results;
}
You're using a module pattern in your 'public API' so your searchService returns the find function and an array of results, but you'd want to make the function find to be the responsible for actually returning the results.
Setting that aside, whenever you call doSearch() in your scope, you'd want to update the current results for those returned by your searchService
$scope.doSearch = function(){
$scope.searchService.results = searchService.find($scope.search_term);
};
I updated your jsfiddle with my ideas, is not functional but i added some commments and logs to help you debug this issue. http://jsfiddle.net/XTQSu/3/

Resources