Background
I have the most basic "newbie" AngularJS question, forgive my ignorance: how do I refresh the model via code? I'm sure it's answered multiple times somewhere, but I simply couldn't
find it.
I've watched some great videos here http://egghead.io and went quickly over the tutorial, but still I feel I'm missing something very basic.
I found one relevant example here ($route.reload()) but I'm not sure I understand how to use it in the example below
Here is the setup
controllers.js
function PersonListCtrl($scope, $http) {
$http.get('/persons').success(function(data) {
$scope.persons = data;
});
}
index.html
...
<div>
<ul ng-controller="PersonListCtrl">
<li ng-repeat="person in persons">
Name: {{person.name}}, Age {{person.age}}
</li>
</ul>
</div>
...
This all works amazingly well, each time the page is reloaded I see the list of people as expected
The questions
Let's say I want to implement a refresh button, how do I tell the model to reload programmatically?
How can I access the model? it seems Angular is magically instantiating an instance of my controller, but how do I get my hands on it?
EDIT added a third question, same as #1 but how can it be done purely via JavaScript?
I'm sure I'm missing something basic, but after spending an hour trying to figure it out, I think it deserves a question. Please let me know if it's duplicate and I'll close + link to it.
You're half way there on your own. To implement a refresh, you'd just wrap what you already have in a function on the scope:
function PersonListCtrl($scope, $http) {
$scope.loadData = function () {
$http.get('/persons').success(function(data) {
$scope.persons = data;
});
};
//initial load
$scope.loadData();
}
then in your markup
<div ng-controller="PersonListCtrl">
<ul>
<li ng-repeat="person in persons">
Name: {{person.name}}, Age {{person.age}}
</li>
</ul>
<button ng-click="loadData()">Refresh</button>
</div>
As far as "accessing your model", all you'd need to do is access that $scope.persons array in your controller:
for example (just puedo code) in your controller:
$scope.addPerson = function() {
$scope.persons.push({ name: 'Test Monkey' });
};
Then you could use that in your view or whatever you'd want to do.
Before I show you how to reload / refresh model data from the server programmatically? I have to explain for you the concept of Data Binding. This is an extremely powerful concept that will truly revolutionize the way you develop. So may be you have to read about this concept from this link or this seconde link in order to unterstand how AngularjS work.
now I'll show you a sample example that exaplain how can you update your model from server.
HTML Code:
<div ng-controller="PersonListCtrl">
<ul>
<li ng-repeat="person in persons">
Name: {{person.name}}, Age {{person.age}}
</li>
</ul>
<button ng-click="updateData()">Refresh Data</button>
</div>
So our controller named: PersonListCtrl and our Model named: persons. go to your Controller js in order to develop the function named: updateData() that will be invoked when we are need to update and refresh our Model persons.
Javascript Code:
app.controller('adsController', function($log,$scope,...){
.....
$scope.updateData = function(){
$http.get('/persons').success(function(data) {
$scope.persons = data;// Update Model-- Line X
});
}
});
Now I explain for you how it work:
when user click on button Refresh Data, the server will call to function updateData() and inside this function we will invoke our web service by the function $http.get() and when we have the result from our ws we will affect it to our model (Line X).Dice that affects the results for our model, our View of this list will be changed with new Data.
Related
What is the recommended Angular way of adding custom directives conditionally? No google post or SO article has seemed to really answer my question.
The app allows the user to display and interact with customer information. In one view, the person can click on the order details, which makes an API call to return the data. If the order comes back as a certain type, I want to add an indicator to the already rendered sidebar. It seems like a pretty simple and common use case so I'm wondering what I'm missing.
I'm using Angular 1.4x,
Angular Bootstrap
It is a big app but here is a contrived example of the relevant code:
sidebar.html - before API call:
<div>
Customer Info
Notes
</div>
sidebar.html - what I want to have after API call:
<div>
Customer Info
<a href="#/orders" id="ordertype">
<order-type-directive uib-tooltip="Phone Order" tooltip-trigger="mouseenter" tooltip-placement="top"/>
</a>
Notes
</div>
main-controller.js
angular.module('app.main')
.controller('mainCtrl', function(){
var vm = this;
vm.orderType;
...
vm.getOrderData(memberID)
.then(function(data){
vm.orderType = data.type;
//This is where my question arises
})
})
So I can make a directive:
.directive('orderTypeDirective', function(){
...
})
Or I can just do something like this:
var el = angular.element('#orderType')
.html('<span uib-tooltip="Phone Order" popover-trigger="mouseenter"></span>');
var aNewScope = $scope.$new();
$compile(el)(aNewScope)
But I feel like the above is ugly and hacky. As to how I insert it into the DOM after creating the element, the documentation and all googling assumes I already know how to do that.
For the directive option I've tried to use an ng-if but it doesn't update when the API call comes back and updates vm.orderType.
For the create-element option, I could do something like this inside an if block in mainCtrl:
el.replaceWithEl(el);
But I know there must be a better, more "Angular" way and that I'm missing something glaring.
Have you tried maybe with a ng-if directive ? like:
<div>
Customer Info
<a href="#/orders" id="ordertype">
<order-type-directive uib-tooltip="Phone Order"
tooltip-trigger="mouseenter"
tooltip-placement="top"
data-ng-if="mainCtrl.orderType && mainCtrl.orderType.length>0"/>
</a>
Notes
</div>
Or you can put any other condition in your ng-if directive that maybe match well your case
I've been trying to solve this for hours, and have tried to find a working solution on stack overflow and other sites, but none worked so far.
The Issue
I am building a travelogue web app that allows users to log and view their journeys (e.g. a road trip). At the moment I am implementing the feature that lets users view a particular journey in a separate view which they have selected from a list of journeys. I pass down the id of the selected journey and retrieve an Object from MongoDB. I implemented this using POST. It works in that the _id of the selected journey is passed in the request, then used to identify the document with Model.findById - then the response yields the data. The data is bound to $scope.selection.
But while $scope.selection contains the data (when logged to console), I cannot seem to bind it to the view (called view_journey). Meaning, whenever I want to access, e.g. selection.name in my view_journey.html, the expression or ng-bind is left empty.
app.js
$scope.viewJourneybyId = function(id) {
var selectOne = { _id : id };
$http.post('http://localhost:8080/view_journey', selectOne).
success(function(data) {
$scope.selection = data;
$scope.$apply();
console.log("POST found the right Journey");
console.log($scope.selection);
}).error(function(data) {
console.error("POST encountered an error");
})
}
server.js
app.post("/view_journey", function(request, response, next) {
Journeys.findById(request.body._id, function(error, selection) {
if (error)
response.send(error)
response.json({ message: 'Journey found!', selection });
});
});
index.html
<tr ng-repeat="journey in journeys">
<td>
<a href="#/view_journey" ng-click="viewJourneybyId(journey._id)">
{{journey.name}}</a>
</td>
<td>...</td>
</tr>
view_journey.html
<div class="panel panel-default">
<div class="panel-heading">
<h2 ng-bind="selection.name"></h2>
<!-- For Debugging -->
ID <span ng-bind="selection._id">
</div>
<div class="panel-body">
<table class=table>
<caption>{{selection.desc}}</caption>
...
</table>
</div>
</div>
Feedback
This is my first question on stack overflow, so please also tell me if I phrased my question in a way that could be misunderstood, and whether or not I should supply more details, e.g. console output. Thank you ;)
After fiddling with your code I can confirm that when triggering the route you are getting a new instance of the controller that has a new, clean scope. This is the expected behavior with AngularJS.
You can verify this by adding a simple log message as the first line of your controller:
console.log($scope.selected);
You will notice that it always logs out "undefined" because the variable has never been set (within viewJourneyById). If you leave that logging in and test the code you will see the logging fire in viewJourneyById but then immediately the "undefined" as it loads the view_journey.html template into ng-view and creates the new instance of mainCtrl. The presence of the "undefined" after the new view loads shows that the controller function is being executed again on the route change.
There are a couple of ways to address this. First you could create a factory or service, inject it into your controller, and have it store the data for you. That is actually one of the reasons they exist, to share data between controllers.
Factory:
travelogueApp.factory('myFactory',function() {
return {
selected: null,
journeys: []
};
});
Controller:
travelogueApp.controller('mainCtrl', ['$scope','$http','$location','myFactory', function ($scope, $http, $location, myFactory) {
// put it into the scope so the template can see it.
$scope.factory = myFactory;
// do other stuff
$scope.viewJourneybyId = function(id) {
var selectOne = { _id : id };
$http.post('http://localhost:8080/view_journey', selectOne)
.success(function(data) {
$scope.factory.selection = data;
console.log("POST found the right Journey");
console.log($scope.factory.selection);
})
.error(function(data) {
console.error("POST encountered an error");
})
}
}]); // end controller
Template:
<div class="panel panel-default">
<div class="panel-heading">
<h2>{{factory.selection.name}}</h2>
</div>
<div class="panel-body">
<table class=table>
<caption>{{factory.selection.desc}}</caption>
...
</table>
</div>
</div>
More or less something like that. Another way to do it would be to construct the link with the journey id as part of the query string and then in the controller check for the presence of the journey id and if you find one, look up the journey. This would be a case of firing the route, loading a new instance of the controller and then loading the data once you're on the view_journey page. You can search for query string parameters in the controller like this:
var journey_id = $location.search().id;
Either way works. The factory/service method allows you to minimize web service calls over time by storing some data. However, then you have to start considering data management so you don't have stale data in your app. The query string way would be your quickest way to solve the problem but means that every route transition is going to be waiting a web service call, even if you are just going back and forth between the same two pages.
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.
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.
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/