AngularJS Variable in service is not updating in view? - angularjs

I have spent a couple of days banging my head against the table, reading blog posts, and SO questions around my issue. I tried several variations of the code I have below and none has worked so far. I would appreciate any help.
The taskList variable inside the service does update, but the one in the controller does not.
Controller
angular.module("TaskManager").controller("mainController", function ($scope, API) {
$scope.init = function () {
console.log("Initializing app");
API.getTasks();
}
$scope.tasks = API.taskList;
$scope.$watch(function(){return API.taskList}, function(newVal, oldVal){
alert("This is tasklist in controller" + newVal);
}, true)
});
Service
angular.module("TaskManager").service("API", function ($http, $rootScope) {
const apiKey = "PUH";
var taskList = [1,2,3,4];
const getTasks = function () {
$http.get("https://api.mlab.com/api/1/databases/jquerytaskmanager/collections/tasks?apiKey=" + apiKey).then(function (data) {
taskList = data;
console.log(taskList);
});
};
$rootScope.$watch(function(){return taskList}, function(newVal, oldVal){
alert(taskList);
taskList = newVal;
console.log(taskList);
}, true)
return {
getTasks,
taskList
}
});
View
<div class="row">
<div class="col s12">
<ul id="mainMenu" class="right hide-on-med-and-down">
<li><a class="waves-effect waves-light btn blue darken-4"><i class="fa fa-bars" aria-hidden="true"></i> Menu</a></li>
<li><a class="waves-effect waves-light btn blue darken-4"><i class="fa fa-plus" aria-hidden="true"></i> Add Task</a></li>
<li><a class="waves-effect waves-light btn blue darken-4"><i class="fa fa-pencil" aria-hidden="true"></i> Manage Categories</a></li>
</ul>
</div>
</div>
<div class="mainContainer container">
<div class="row">
<div class="col 6">
<h2>Tasks</h2>
</div>
</div>
<ul>
<li class="taskContainer" ng-repeat="task in tasks" >
<div class="row">
<div class="col s6">
<span class="taskName"> {{task.task_name}}</span>
</div>
<div class="col s6">
<button class="btn blue darken-4 waves-effect waves-light ">Edit</button> <button class="btn red darken-4 waves-effect waves-light ">Delete</button>
</div>
</div>
</li>
</ul>
</div>

Return the $http promise from the service:
app.service("API", function($http) {
this.getTasks = function() {
return $http.get(url);
};
});
Then in the controller, extract the data from the promise:
app.controller("mainController", function($scope, API) {
this.$onInit = function () {
console.log("Initializing app");
API.getTasks().then(function(response) {
$scope.tasks = response.data;
});
};
});
For more information, see AngularJS $http Service API Reference.

Related

angular JS adding and removing movie to a array of favorites

I'm quite new to angular js and I am having a hard time trying to implement a function who adds a movie to an array of favorites and removes it from the array in case it's already there (difficult part).
Those are my controllers bellow. The SearchController and DetailsController refer to the search.html and details.html respectively, also bellow. Can I get any help?
Thanks in advance
var myControllers = angular.module("myControllers", []);
myControllers.controller(
"SearchController",
function MyController($scope, $http) {
$scope.searchByTitle = function (title) {
$http
.get("http://www.omdbapi.com/?apikey=d4458e16&s=" + title)
.then(function (data) {
$scope.movies = data.data.Search;
});
};
$scope.wishlist = JSON.parse(localStorage.getItem("wishlist"));
}
);
myControllers.controller(
"DetailsController",
function MyController($scope, $http, $routeParams) {
$http
.get("http://www.omdbapi.com/?apikey=d4458e16&i=" + $routeParams.itemId)
.then(function (data) {
$scope.movies = data.data;
});
$scope.favList = JSON.parse(localStorage.getItem("wishlist")) || [];
$scope.isFavorite = false; //JSON.parse(localStorage.getItem("isFavorite")) || false;
$scope.addMovieToFavList = function (item) {
/*if ($scope.favList.includes(item)) {
console.log("movie is on favorites and will be removed");
//$scope.favList.pop(item);
} else {
console.log("movie is not on favorites and will be added");
//$scope.favList.push(item);
}*/
!$scope.isFavorite ? $scope.favList.push(item) : $scope.favList.pop();
$scope.isFavorite = !$scope.isFavorite;
localStorage.setItem("wishlist", JSON.stringify($scope.favList));
//localStorage.setItem("isFavorite", JSON.stringify($scope.isFavorite));
};
}
);
search.html:
<div class="container-fluid">
<h1>Film App<h1>
<div class="row">
<h2>Search</h2>
<input
ng-model="title"
class="form-control"
placeholder="Search for a film"
value="Search"
/>
<button ng-click="searchByTitle(title)" class="btn btn-primary btn-block">
Search for a movie
</button>
<ul class="list-group">
<li ng-repeat="movie in movies | filter:title" class="list-group-item">
<a href="#!/details/{{movie.imdbID}}">
<img ng-src="{{movie.Poster}}" width="30px" />
{{movie.Title}}<span>, {{movie.Year}}</span>
</a>
</li>
</ul>
</div>
</div>
<div class="container-fluid">
<h1>My Favorites<h1>
<ul class="list-group">
<li ng-repeat="favouriteMovie in wishlist" class="list-group-item">
<a href="#!/details/{{favouriteMovie.imdbID}}">
<img ng-src="{{favouriteMovie.Poster}}" width="30px" />
{{favouriteMovie.Title}}<span>, {{favouriteMovie.Year}}</span>
</a>
</li>
</ul>
</div>
details.html:
<div class="container">
<div class="row">
<div class="col-12 mt-3">
<div class="card" ng-model="movies">
<div
class="card-header d-flex align-items-start justify-content-between"
>
<a href="#!">
<button>Back Home</button>
</a>
<button ng-click="addMovieToFavList(movies)">
{{isFavorite==false?'Add to favorites':'Remove from favorites'}}
</button>
<h1 class="card-title my-0">{{movies.Title}}</h1>
<img ng-src="{{movies.Poster}}" />
</div>
<div class="card-body">
<div class="card-text text-secondary">
<h4>Year</h4>
<p>{{movies.Year}}</p>
<h4>Cast</h4>
<p>{{movies.Actors}}</p>
<h4>Plot</h4>
<p>{{movies.Plot}}</p>
<h4>Rating</h4>
<p>{{movies.imdbRating}}/10</p>
</div>
</div>
</div>
</div>
</div>
</div>
You will want to check for the movie via an identifier (like an ID) not by comparing whole objects. So, assuming the object has a property called 'ID' we can check for that. Also, to remove an item from your array, you can use splice
$scope.addMovieToFavList = function(item) {
let index = $scope.favList.findIndex(movie => movie.ID == item.ID);
if (index === -1) {
// movie doesn't exist in favorites yet
$scope.favList.push(item)
$scope.isFavorite = true;
} else {
// movie exists, we will splice it out
$scope.favList.splice(index, 1)
$scope.isFavorite = false
}
localStorage.setItem("wishlist", JSON.stringify($scope.favList));
};

How to use controller as syntax in the following scenario

controller as syntax problem
I am a newbie to angular, so please forgive me if i'm wrong. I have a problem where a service is used for certain operation. In this case, adding books to cart.
I have followed the recommended way using controller as syntax instead of $scope. But in kart-list.html, i'm not able to access kart details, since this operation is done by BookListCtrl.
app.js
var app = angular.module("app", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("/books", {
templateUrl: "partials/book-list.html",
controller: "BookListCtrl",
controllerAs: 'booklist'
})
.when("/kart", {
templateUrl: "partials/kart-list.html",
controller: "KartListCtrl",
})
.otherwise({
redirectTo: "/books"
});
});
app.factory("kartService", function() {
var kart = [];
return {
getKart: function() {
return kart;
},
addToKart: function(book) {
kart.push(book);
},
buy: function(book) {
alert("Thanks for buying: ", book.name);
}
};
});
app.factory("bookService", function() {
var books = ['some data'];
return {
getBooks: function() {
return books;
},
addToKart: function(book) {
}
}
});
app.controller("KartListCtrl", function(kartService) {
var self = this;
self.kart = kartService.getKart();
self.buy = function(book) {
kartService.buy(book);
};
});
app.controller("HeaderCtrl", function() {
var self = this;
self.appDetails = {};
self.appDetails.title = "BooKart";
self.appDetails.tagline = "We have collection of 1 Million books";
});
app.controller("BookListCtrl", function(bookService, kartService) {
var self = this;
self.books = bookService.getBooks();
self.addToKart = function(book) {
kartService.addToKart(book);
};
});
book-list.html
<div id="bookListWrapper">
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search here..." />
</div>
</form>
<ul class="list-unstyled">
<li class="book" ng-repeat="book in booklist.books" style="background: white url('imgs/{{book.imgUrl}}') no-repeat">
<div class="book-details clearfix">
<h3>{{book.name}}</h3>
<p>{{book.price}}</p>
<ul class="list-unstyled list-inline">
<li>Rating: {{book.rating}}</li>
<li>Binding: {{book.binding}}</li>
<li>Publisher: {{book.publisher}}</li>
<li>Released: {{book.releaseDate}}</li>
</ul>
<p>{{book.details}}</p>
<button class="btn btn-info pull-right" ng-click="addToKart(book)">Add to Kart</button>
</div>
</li>
</ul>
</div>
kart-view.html
<div id="bookListWrapper">
<p>Please click on buy button to buy the book.</p>
<ul class="list-unstyled">
<li class="book" ng-repeat="book in kart" style="background: white url('imgs/{{book.imgUrl}}') no-repeat">
<div class="book-details clearfix">
<h3>{{book.name}}</h3>
<p>{{book.price}}</p>
<ul class="list-unstyled list-inline">
<li>Rating: {{book.rating}}</li>
<li>Binding: {{book.binding}}</li>
<li>Publisher: {{book.publisher}}</li>
<li>Released: {{book.releaseDate}}</li>
</ul>
<p>{{book.details}}</p>
<button class="btn btn-info pull-right" ng-click="buy(book)">Buy</button>
</div>
</li>
</ul>
</div>
Problem is how use controller as syntax for kart-list.html near ng-repeat to access kart details?
Your'e using the controller as feature but you haven't covered all view references.
book-list.html
<button ... ng-click="addToKart(book)">Add to Kart</button>
Change to
<button ... ng-click="booklist.addToKart(book)">Add to Kart</button>
kart-view.html
<li ... ng-repeat="book in kart" ...
...
<button ... ng-click="buy(book)">Buy</button>
Change to
<li ... ng-repeat="book in KartListCtrl.kart" ...
...
<button ... ng-click="KartListCtrl.buy(book)">Buy</button>
You have to prefix booklist while calling addToKart() method in book-list.html.
Use below file which will resolve your issue:
book-list.html
<div id="bookListWrapper">
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search here..." />
</div>
</form>
<ul class="list-unstyled">
<li class="book" ng-repeat="book in booklist.books" style="background: white url('imgs/{{book.imgUrl}}') no-repeat">
<div class="book-details clearfix">
<h3>{{book.name}}</h3>
<p>{{book.price}}</p>
<ul class="list-unstyled list-inline">
<li>Rating: {{book.rating}}</li>
<li>Binding: {{book.binding}}</li>
<li>Publisher: {{book.publisher}}</li>
<li>Released: {{book.releaseDate}}</li>
</ul>
<p>{{book.details}}</p>
<button class="btn btn-info pull-right" ng-click="booklist.addToKart(book)">Add to Kart</button>
</div>
</li>
</ul>
</div>

Modal closes and new record appears to the screen despite of problems saving in a non-blocking call to the server

Problem Statement: How to prevent model to close and caller screen to update when there is a problem in async server call? For example, say I have a screen that list books on a screen like this. It also allows to perform CRUD operations. While creating and updating a new modal opens with a new controller.
Here is the HTML:
<li ng-repeat="book in books">
<div ng-click="updateBookModelOpen('lg', book, $index)">
<p class="text-center">{{book.name}}</p>
</div>
<div class="row text-center">
<a ng-click="remove(book)" class="undecorated-link">
<button class="btn btn-danger">Delete</button>
</a>
</div>
</li>
<button ng-click="createBookModelOpen('lg')" class="btn btn-primary">Add Your Book</button>
Here is logic for opening the modal, say this is inside BooksController:
this.createBookModelOpen = function(size) {
var modalInstance = $modal.open({
templateUrl: 'create-book.html',
controller: 'CreateBookModalController',
backdrop: 'static',
keyboard: false,
size: size,
scope: $scope
});
modalInstance.result.then(function(book) {
this.books.push(book);
}, function() {
$log.info('Modal dismissed at: ' + new Date());
});
};
Here is the logic for modal controller:
.controller('CreateBookModalController', ['$scope', '$modalInstance',
function($scope, $modalInstance) {
$scope.ok = function(book) {
$modalInstance.close(book);
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}
])
Here is the modal HTML:
<section data-ng-controller="CreateBooksController as createBooksCtrl" ng-init="createBooksCtrl.init()">
<div class="modal-header">
<h3 class="modal-title">Enter Book Details</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<form class="form-horizontal" name="createBookForm" novalidate>
<fieldset>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<input type="text" data-ng-model="book.name" id="name" class="form-control" placeholder="Book Name" required>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<div class="row">
<div class="col-md-12">
<div class="pull-right">
<button class="btn btn-primary" ng-click="cancel()">Don't Save & Close</button>
<button class="btn btn-primary" ng-click="createBooksCtrl.create(book); ok(book)">Save & Close</button>
</div>
</div>
</div>
</div>
</section>
Here is my CreateBooksClientController:
this.create = function(book) {
book.$save(function(response) {
}, function(errorResponse) {
this.errors.push(errorResponse.data.messages);
});
};
The current behavior is that when I try to save an a book with the name that is rejected by the server. I get an error from the server but since this call is nonblocking, execution to ok() proceeds because ok() is next in sequence here.
<button class="btn btn-primary" ng-click="createBooksCtrl.create(book); ok(book)">Save & Close</button>
The book does not get saved in the database. But my modal closes and the new book is displayed to the screen because of this:
modalInstance.result.then(function(book) {
this.books.push(book);
}, function() {
$log.info('Modal dismissed at: ' + new Date());
});
};
Ideally it should not have been displayed in the list here as it has not yet been saved to the database.
<li ng-repeat="book in books">
<div ng-click="updateBookModelOpen('lg', book, $index)">
<p class="text-center">{{book.name}}</p>
</div>
</li>
Expected Result: Modal should not close, ok() call should not proceed when there is an error from the service. List should not display the new book if it is not saved to the database. How do you guys handle such scenario? Any help would be highly appreciated.
If you don't need to specifically use CreateBooksClientController, you could call book.$save in the modal controller as follows:
.controller('CreateBookModalController', ['$scope', '$modalInstance', '$http'
function($scope, $modalInstance, $http) {
$scope.ok = function(book) {
book.$save(function(response) {
$modalInstance.close(book);
}, function(errorResponse) {
// Error handling
})
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}
])
The error handling will probably have to be changed accordingly. Then the ok button will be just:
<button class="btn btn-primary" ng-click="ok(book)">Save & Close</button>

AngularJS Factory and Parse.com User Authentication

I am playing around with AngularJS using Parse.com as it's backend and am having an issue when I try go authenticate and deuthenticate with Parse. I am abe to do both these functions, but my views don't update.
app.factory('hsUser', ['$http', function($http){
var dataObject = null;
return {
get: function()
{
var tmpData = Parse.User.current();
console.log(tmpData);
if(tmpData && dataObject == null)
{
dataObject = {};
keys = Object.keys(tmpData);
keys.forEach(function(value)
{
dataObject[value] = tmpData[value];
});
console.log(dataObject);
}
return dataObject;
},
authenticate: function(username, password)
{
console.log('auth');
Parse.User.logIn(username, password, {
success: function(userData) {
dataObject = {};
keys = Object.keys(userData);
keys.forEach(function(value)
{
dataObject[value] = userData[value];
});
},
error: function(userData, errorData) {
console.log(errorData);
}
});
},
deauthenticate: function()
{
Parse.User.logOut();
dataObject = Parse.User.current();
},
doRegister: function(registration)
{
console.log(registration);
return {
success: true
}
}
}
}]);
Here are my controllers.
app.controller('HeaderController', ['hsConfig','hsUser', function( hsConfig, hsUser)
{
this.config = hsConfig.get();
this.user = hsUser.get();
}]);
app.controller('AuthController', ['hsUser', function( hsUser)
{
this.user = hsUser.get();
this.authUser = {
emailAddress: "testinguser",
password: "testinguser",
remember: false,
}
this.authenticate = function()
{
hsUser.authenticate(this.authUser.emailAddress, this.authUser.password);
}
this.deauthenticate = function()
{
this.user = hsUser.deauthenticate();
console.log('Deauthenticated')
}
}]);
Here is the HTML for the directive that creates the navbar
<div class="header" ng-controller="HeaderController as headCtrl">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">{{ headCtrl.config.brand }}</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right" ng-hide="headCtrl.user">
<li class="dropdown" ng-controller="AuthController as authCtrl">
<a class="dropdown-toggle" href data-toggle="dropdown">
Sign In
<strong class="caret"></strong>
</a>
<div class="dropdown-menu auth-form-container" style="padding: 15px; padding-bottom: 0px;" >
<form role="form" ng-submit="authCtrl.authenticate();">
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" placeholder="Enter email" ng-model="authCtrl.authUser.emailAddress" >
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" placeholder="Password" ng-model="authCtrl.authUser.password">
</div>
<div class="form-group checkbox">
<label>
<input type="checkbox" ng-model="authCtrl.authUser.remember"> Remember Me
</label>
</div>
<div class="form-group">
<button class="btn btn-primary btn-full">Sign In</button>
</div>
</form>
</div>
</li>
<li class="divider-vertical"></li>
<li>Register</li>
</ul>
<ul class="nav navbar-nav navbar-right" ng-show="headCtrl.user">
<li>Profile</li>
<li class="divider-vertical"></li>
<li ng-controller="AuthController as authCtrl">
<a href ng-click="authCtrl.deauthenticate();">Sign Out</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
The entire project including Parse keys can be found here. I'll delete the keys once I figure out wtf my problem is.
https://github.com/thenetimp/hackerspace
If anyone can point me into what I am doing wrong and explain it so I can understand what I need to do with other Models I would greatly appreciate it.
whenever you update the model outside of angulars context, I.e in the callback of a promise, you need to use $scope.apply to notify angular.

How can I add a class on click with AngularJS and Angular Bootstrap/UI?

I'm fighting against my own grain on this, trying to learn.
I have a Bootstrap menu... Here's part of it:
<div id="language_menu" class="dropdown" on-toggle="toggled(open)">
<a class="dropdown-toggle" ng-model="clicked"> <span class="flag-xs flag-us"></span> <span class="small va-m">US</span> <i class="fa fa-angle-down"></i> </a>
<ul class="dropdown-menu">
<li><span class="flag-xs flag-in"></span> Hindu</li>
<li><span class="flag-xs flag-tr"></span> Turkish</li>
<li><span class="flag-xs flag-es"></span> Spanish</li>
</ul>
</div>
Here's the part of my controller in question:
$scope.toggled = function(open) {
if (open) {
$('#language_menu').find('.dropdown-menu').addClass('animated-shortest animated flipInX');
} else {
$('#language_menu').find('.dropdown-menu').removeClass('animated-shortest animated flipInX');
}
};
I'm confused as to the best method for this. ngClick vs dealing with it on the controller in the toggled function? You can see what I'm trying to achieve just by the jQuery. I know this is wrong, or maybe I'm skipping the angular way of handling it...
Please excuse me as I'm entirely new to Angular.
---- Update ----
Would it be considered improper to handle it this way?
<div id="language_menu" class="dropdown" on-toggle="toggled(open)" >
<a class="dropdown-toggle" href="#"> <span class="flag-xs flag-us"></span> <span class="small va-m">US</span> <i class="fa fa-angle-down"></i> </a>
<ul class="dropdown-menu {{theClass}}">
<li><span class="flag-xs flag-in"></span> Hindu</li>
<li><span class="flag-xs flag-tr"></span> Turkish</li>
<li><span class="flag-xs flag-es"></span> Spanish</li>
</ul>
</div>
$scope.toggled = function(open) {
if (open) {
$scope.theClass = 'animated-shortest animated flipInX';
} else {
$scope.theClass = '';
}
};
ngClass will help. Here's a working example: http://jsfiddle.net/tgg4eq4j/
Some code:
HTML:
<div ng-app="TestApp" ng-controller="TestCtrl">
<button ng-click="toggle()">Toggle</button>
<span ng-class="getClass()">I'm toggled</span>
</div>
And the JS:
var app = angular.module("TestApp", []);
app.controller("TestCtrl", ["$scope", function ($scope) {
var flag = true;
$scope.getClass = function () {
return flag ? "whitesmoke": "white";
}
$scope.toggle = function () {
flag = !flag;
};
$scope.class = "whitesmoke";
}]);
Use ngClass:
<div id="language_menu" ng-class="{'animated-shortest animated flipInX': open}" on-toggle="toggled(open)">
</div>

Resources