I'm creating a simple add post and comments app with https://thinkster.io/tutorials/mean-stack/wiring-everything-up. After finishing this section I should be able to add a post or comment and it save to the database and bind on the client side at the same time to keep the information current. However, it is only posting to the back end correctly and is not populating the post or comments array with the ng-model data. I believe the problem may be with the angular copy in the post service but do not see an error. The only difference I can see in my code and the tutorial is that it wanted to use .success for the callback of $http which I think is deprecated so I use .then instead. Below I will post the service, control, and ng-template, if you would like more information I can send a link to the repository.
Edit:Also I should note I know that it is working in the back end because I can see it in the collection or I can also manually refresh the browser and the new post is there. But obviously I shouldn't need to do a refresh.
Service
app.factory('posts', ['$http', function($http){
var o = {
posts:[]
}
o.get = function(id){
return $http.get('/posts/' + id).then(function(res){
return res.data;
});
};
o.getAll = function() {
return $http.get('/posts').then(function(data){
angular.copy(data.data, o.posts);
});
};
o.create = function(post) {
return $http.post('/posts', post).then(function(data){
o.posts.push(data);
});
};
o.upvote = function(post){
return $http.put('/posts/'+ post._id + '/upvote').then(function(data){
post.upvotes +=1;
});
}
o.addComment = function(id, comment){
return $http.post('/posts/' + id + '/comments', comment);
};
o.upvoteComment = function(post, comment){
return $http.put('/posts/' + post._id + '/comments/' + comment._id + '/upvote')
.then(function(data){
comment.upvotes += 1;
});
};
return o;
}])
Controller
app.controller('MainCtrl', ['$scope', 'posts', function($scope, posts){
$scope.test = 'Hello world!';
$scope.posts = posts.posts;
$scope.addPost = function(){
if(!$scope.title || $scope.title === '') { return; }
posts.create({
title: $scope.title,
link: $scope.link
});
$scope.title='';
$scope.link='';
}
$scope.incrementUpvotes = function(post) {
posts.upvote(post)
};
}]);
ng-template
<script type='text/ng-template' id='/home.html'>
<div class='page-header'>
<h1>Rawle News App</h1>
</div><!--End of page-header-->
<div ng-repeat='post in posts | orderBy: "-upvotes"'>
<span class="glyphicon glyphicon-thumbs-up"
ng-click="incrementUpvotes(post)"></span>
{{post.upvotes}}
<span class='link-titles'>
<a ng-show="post.link" href="{{post.link}}">
{{post.title}}
</a>
<span ng-hide="post.link">
{{post.title}}
</span>
<span>
Comments
</span>
</span>
</div>
<form class='post-form' ng-submit='addPost()'>
<div class='form-group'>
<input type='text' placeholder='title' ng-model='title'></input>
</div><!--End of form-group-->
<div class='form-group'>
<input type='text' placeholder='link' ng-model='link'></input>
</div><!--End of form-group-->
<button class='btn btn-primary' type='submit'>Post</button>
</form>
</script>
The post.create function is erroneous:
//ERRONEOUS
/*
o.create = function(post) {
return $http.post('/posts', post).then(function(data){
o.posts.push(data);
});
};
*/
//PUSH response.data
o.create = function(post) {
return $http.post('/posts', post).then(function(response){
o.posts.push(response.data);
//return value for chaining
return response.data
});
};
Try this in your controller.
$scope.addPost = function(){
if(!$scope.title || $scope.title === '') { return; }
posts.create({
title: $scope.title,
link: $scope.link
}).then(function(){
$scope.posts=posts.getAll();
});
Can you try this too please try this BEFORE:
$scope.addPost = function(){
if(!$scope.title || $scope.title === '') { return; }
posts.create({
title: $scope.title,
link: $scope.link
}).then(function(){
$scope.$apply();
});
o.getAll = function() {
return $http.get('/posts').then(function(data){
angular.copy(data.data, o.posts);
});
};
The above won't work as you're not returning anything from the then() statement.
o.getAll = function() {
return $http.get('/posts').then(function(data){
return angular.copy(data.data, o.posts);
});
};
Should get you closer.
Keep in mind that anytime you're in async land (returning promises etc) you need to handle it correctly in your controller/service/directive/whatever.
Side question, why are you still using $scope as opposed to controller instance as? This pattern has long been out of favor. Version of Angular?
Related
I have the following code and I have tried just bout everything to return a promise that would work with the directive. I have even tried returning the response data and return $q.when(data) and nothing. Ive tried reading on promises and this one is a bit different then Ive read. Something I'm missing?
myApp.controller('smsCtrl', function($scope){
$scope.sendSMS = function(){
let sms = {'number': $scope.number ,'message': $scope.message};
serviceNameHere.sendSMS(sms).then(function(response){
$scope.smsSuccessMsg = "Message Sent Successfully";
}, function(response){
$scope.smsErrorMsg = response.data['message'];
});
};
})
myApp.directive('onClickDisable', function() {
return {
scope: {
onClickDisable: '&'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
element.prop('disabled',true);
scope.onClickDisable().finally(function() {
element.prop('disabled',false);
})
});
}
};
});
The following html
<div ng-controller="smsCtrl">
<-- field inputs here --></-->
<button type="button" class="btn btn-primary" on-click-disable="sendSMS()">SEND</button>
</div>
JSfiddle Example
There is no need to have a special directive for this. Simply use ng-disabled and ng-click:
<div ng-controller="smsCtrl">
<!-- field inputs here -->
<button ng-click="sendSMS()" ng-disabled="pendingSMS">
SEND
</button>
</div>
In the controller:
myApp.controller('smsCtrl', function($scope, serviceNameHere){
$scope.sendSMS = function(){
let sms = {'number': $scope.number ,'message': $scope.message};
$scope.pendingSMS = true;
serviceNameHere.sendSMS(sms).then(function(response){
$scope.smsSuccessMsg = "Message Sent Successfully";
}, function(response){
$scope.smsErrorMsg = response.data['message'];
}).finally(function() {
$scope.pendingSMS = false;
});
};
})
When the SMS message starts, the controller sets the pendingSMS flag to disable the Send button. When the service completes, the flag is reset and the button is re-enabled.
When I load the html page, my controller retrieves data from an API end point regarding a course. The page gets populate with the data about the course. But at the same time I want to populate part of the page with data about the lecturer of the course (their image, name , description etc ...). I pass the lecturer name to the method using the ng-init directive but I get a
ReferenceError: lecturerFac is not defined.
I am not sure but I believe the issue is the way I am calling the getLecturer() function using the ng-init directive.
What I want to happen when the page loads is have the Lecturer's details displayed on the page along with the course details.
courses.html
<div class="container" ng-controller="CoursesDetailsCtrl">
<div class="row" >
<div class="col-4" ng-model="getLecturer(courses.lecturer)">
<div>
<h3>{{lecturer.name}}</h3>
<<div>
<img class="img-circle" ng-src="{{lecturer.picture}}" alt="" />
</div>
<p>{{lecturer.description}}</p> -->
</div>
</div>
<div class="col-8">
<div class="myContainer" >
<h2>{{courses.name}}</h2>
<div class="thumbnail">
<img ng-src="{{courses.picture}}" alt="" />
</div>
<div>
<p>{{courses.description}}</p>
</div>
</div>
</div>
</div>
</div>
CoursesDetailsCtrl
todoApp.controller('CoursesDetailsCtrl', ['coursesFac','lecturerFac','$scope','$stateParams', function CoursesCtrl(coursesFac, lecturerFac, $scope, $stateParams){
$scope.getLecturer = function(name){
lecturerFac.getLecturerByName(name)
.then(function (response) {
$scope.lecturer = response.data;
console.log($scope.lecturer);
}, function (error) {
$scope.status = 'Unable to load lecturer data: ' + error.message;
console.log($scope.status);
});
};
}]);
lecturerFac
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
lecturerFac.getLecturer = function () {
return $http.get(urlBase);
};
lecturerFac.getLecturerByName = function (name) {
return $http.get(urlBase + '/' + name);
};
return lecturerFac;
}]);
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
var service = {};
service.getLecturer = function () {
return $http.get(urlBase);
};
service.getLecturerByName = function (name) {
return $http.get(urlBase + '/' + name);
};
return service;
}]);
i Think the cause of this error is the lecturerFac variable is not initialize in the factory. Create an empty object call lecturerFac in the factory and return it.
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
var lecturerFac= {};/////////////////////
lecturerFac.getLecturer = function() {
return $http.get(urlBase);
};
lecturerFac.getLecturerByName = function(name) {
return $http.get(urlBase + '/' + name);
};
return lecturerFac;
}]);
Also avoid calling functions inside the ng-model. When you bind something with ng-model it must be available for both reading and writing - e.g. a property/field on an object. use ng init instead
My Web-App Should get images from server, show them and and give possibility to vote for Like it or Not.
Votes will be stored on DB.
my Controller :
$scope.beginSearch = function () {
$http
.get("http://example?q=" + $scope.search )
.then(function (response) {
$scope.url = response.data.data;
});
};
<tr ng-repeat="x in url">
<th>
<img src="{{x.images}}"></img>
<div class="well">
<i class="fa fa-thumbs-o-up fa-2x vertical-align" ng-click="vote_up(x.id)"></i>
<i class="fa fa-thumbs-o-down fa-2x vertical-align" ng-click="vote_down(x.id)" ></i>
</div>
</th>
</tr>
I was hoping to use a function in every ng-repeat, which would return
votes for like
{{ return_vote(x.id)}}
But it doesn't work, and as far I see, I should not use functions in html,
if they are not in ng-click functions.
ng-init also doesn't work.
Could anyone provide me help, how could I solve my problem?
Images are on some website, I just get them by using their WEB-API, so they doesn't have API for votes, I need to do it myself.
You can call your function inside brackets {{yourFunction(x.id)}} and add the logic to get the votes inside.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.url = [{
images: "http://lorempixel.com/g/100/100/",
id: 1
}, {
images: "http://lorempixel.com/100/100/",
id: 2
}]
$scope.getVotes = function(id){
//logic to get number of votes
return id * 5;
}
$scope.vote_up = function(id){
console.log("Vote up id " + id);
}
$scope.vote_down = function(id){
console.log("Vote down id " + id);
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="x in url">
<img src="{{x.images}}" />
<p>{{getVotes(x.id)}} votes</p>
<i class="fa fa-thumbs-o-up fa-2x vertical-align" ng-click="vote_up(x.id)"></i>
<i class="fa fa-thumbs-o-down fa-2x vertical-align" ng-click="vote_down(x.id)"></i>
</div>
</body>
Since you can't have the votes in the API you could alternatively create a service to get all the votes at once and then create some logic to match them to images.
e.g.
$scope.images = [];
var votes = [];
activate();
function activate() {
getImages().then(function () {
getVotes().then(function () {
matchVotesToImages();
//Now we have a property 'votes' in each image object which we can use in ng-repeat
});
});
}
function getVotes() {
var deferred = $q.defer();
$http.get('theUrl').then(success, fail);
function success(res) {
votes = res;
deferred.resolve();
}
function fail(res) {
console.log("Error");
console.log(res);
}
return deferred.promise;
}
function getImages() {
var deferred = $q.defer();
$http.get('theUrl').then(success, fail);
function success(res) {
$scope.images = res;
deferred.resolve();
}
function fail(res) {
console.log("Error");
console.log(res);
}
return deferred.promise;
}
function getIndex(array, property, valueToCompare) {
var i = 0;
var len = array.length;
for (i; i < len; i++) {
if (array[i][property] == valueToCompare) {
return i;
}
}
return -1;
}
function matchVotesToImages() {
var i = 0;
var len = $scope.images.length;
for (i; i < len; i++) {
//We pick need the votes of this specific image so
var indexAtVoteArray = getIndex(votes, 'id', $scope.images[i].id);
if (indexAtVoteArray != -1)
$scope.images.votes = votes[indexAtVoteArray];
else
$scope.images.votes = 0;
}
}
Thanks for answer.
$scope.vote_get = function (id) {
$http({
method: "GET",
url: "vote.php",
data: {
'id': id
},
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
.then(function (response) {
return response.data;
});
};
When I used this function to get votes, or return anything
, It does infinite loop.
Maybe I'am trying to do in wrong way, then please give me tips how to do that.
I'am just sure, that I need to send ID of image to .php file, this file will connect to database and return votes for provided ID.
Vote_up and Vote_down functions are same, they just POST data but seem to work ok.
Thanks
So, no one has idea :(?
I've created a small feature alowing users to search for movie titles. This does a JSON requests from tmdb.org which returns things like titles, dates and url's posters.
The controller:
angular.module('movieSeat')
.factory('moviesearchFactory', ['$http', '$q', '$rootScope', function ($http, $q, $rootScope) {
var factory = {};
function httpPromise(url) {
var deferred = $q.defer();
$http({
method: 'JSONP',
url: url
})
.success(function (data) {
deferred.resolve(data.results);
})
.error(function () {
deferred.reject();
});
return deferred.promise;
}
factory.getMovies = function (searchquery) {
return httpPromise('http://api.themoviedb.org/3/' + 'search/movie?api_key=a8f7039633f2065942cd8a28d7cadad4' + '&query=' + encodeURIComponent(searchquery) + '&callback=JSON_CALLBACK')
}
return factory;
}]);
The factory:
angular.module('movieSeat')
.controller('moviesearchCtrl', ['$scope', 'moviesearchFactory', function ($scope, moviesearchFactory) {
$scope.createList = function (searchquery) {
$scope.loading = true;
moviesearchFactory.getMovies(searchquery)
.then(function (response) {
$scope.movies = response;
})
.finally(function () {
$scope.loading = false;
});
}
}]);
The template:
<div ng-controller="moviesearchCtrl" id="movieSearch">
<div class="spinner" ng-show="loading">Loading</div>
<input ng-model="searchquery" ng-change="createList(searchquery)" ng-model-options="{ debounce: 500 }" />
{{ search }}
<ul>
<li ng-if="movie.poster_path" ng-repeat="movie in movies | orderBy:'-release_date'">
<span class="title">{{ movie.title }}</span>
<span class="release_date">{{ movie.release_date }}</span>
<img ng-src="https://image.tmdb.org/t/p/w300_and_h450_bestv2/{{ movie.poster_path }}" class="poster"/>
</li>
</ul>
</div>
The problem with this feature is that the spinner class only waits for the requested data. But just loading some JSON doesn't take long, it's downloading the images from the api in the browser that takes a while.
This causes 2 things. First of the spinner is removed before the images are rendered in the browser and because the images are all loaded async it causes a waterfall effect.
The easiest way to resolve this problem would to delay the .then call in the controller until the images are downloaded for the user and then go into the .finally call.
But I can't find a way to create something like that. Any tips?
Try this and let me know:
The idea is to use a directive to emit a render finished event:
dashboard.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.onFinishRender);
});
}
}
}
});
In the controller keep the event listener that wait for the image load promises:
$scope.$on('dataLoaded', function(ngRepeatFinishedEvent) {
// your code to check whether images has loaded
var promises = [];
var imageList = $('#my_tenants img');
for(var i = 0; i < imageList.length; i++) {
promises.push(imageList[i].on('load', function() {}););
}
$q.all(promises).then(function(){
// all images finished loading now
$scope.loading = false;
});
});
and in the html side:
<div id = "my_tenants">
<div ng-repeat="tenant in tenants" on-finish-render="dataLoaded">
// more divs
</div>
</div>
I'm using typeahead to get some suggestions on an input text, this is inside a div controlled by an Angular controller.
The code for the suggestion tasks works with a jQuery plugin, so when I select, something I'm trying to assign a value to $scope, however this is NEVER happening.
I already tried getting the scope of the element with var scope = angular.element($("#providersSearchInput").scope() and then assign it as suggested here but it didn't work.
This is what I'm trying:
<div class="modal-body" ng-controller="ProvidersController" ng-init="orderReviewTab = 'observations'">
<input type="text" id="providersSearchInput" data-provide="typeahead" class="form-control input-md" placeholder="Buscar proovedores">
{{currentProvider}}
</div>
The controller looks like this:
tv3App.controller('ProvidersController', function($scope, $rootScope, $http, $timeout) {
var resultsCache = [];
$("#providersSearchInput").typeahead({
source: function (query, process) {
return $.get("/search/providers/?query=" + query, function (results) {
resultsCache = results;
return process(results);
},'json');
},
matcher: function (item) {
var name = item.name.toLowerCase();
var email = item.email.toLowerCase();
var contact_name = item.contact_name.toLowerCase();
//console.log(name);
var q = this.query.toLowerCase();
return (name.indexOf(q) != -1 || email.indexOf(q) != -1 || contact_name.indexOf(q) != -1);
},
scrollHeight: 20,
highlighter: function (itemName) {
var selected = _.find(resultsCache,{name:itemName});
var div = $('<div></div>');
var name = $('<span ></span>').html('<strong style="font-weight:bold">Empresa: </strong> ' + selected.name);
var contact = $('<span ></span>').html(' <strong style="font-weight:bold">Contacto: </strong> ' + selected.contact_name);
var email = $('<span ></span>').html(' <strong style="font-weight:bold">e-mail:</strong> ' + selected.email);
return $(div).append(name).append(contact).append(email).html();
},
minLength: 3,
items: 15,
afterSelect: function (item) {
console.log(item);
$scope.$emit('providerSelected',item);
}
});
$scope.$on('providerSelected', function (event,provider) {
console.log(provider);
$scope.currentProvider = provider;
$scope.$apply();
});
});
Edit
I tried this to check any changes:
$scope.$watch('currentProvider',function (newValue,oldValue) {
console.log(oldValue);
console.log(newValue);
});
So when selecting something it actually triggers and $scope.currentProvider seems to be updated but its never getting rendered at view ...
get https://angular-ui.github.io/bootstrap/
once you do, in your code make sure
angular.module('myModule', ['ui.bootstrap']);
and for typeahead have
<input type="text" ng-model="currentProvider" typeahead="provider for provider in getProviders($viewValue) | limitTo:8" class="form-control">
In your controller make sure you have
$scope.getProviders = function(val){
return $http.get('/search/providers/?query=' + val).then(function(response){
return response.data;
})
}
This should do the trick although I haven't tested