ng-repeat populating only after window click - AngularJS & PouchDB - javascript

I have the following function reading from a service:
var db = pouchService.db;
db.allDocs({startkey: 'move_', endkey: 'move_\uffff', include_docs: true})
.then(function (data) {
$scope.recordlist = data;
console.log($scope.recordlist);
});
Service:
angular.module('msfLogger').service('pouchService', PouchService);
function PouchService() {
var self = this;
self.db= new PouchDB('FleetDB');
};
On the front end, I'm trying to populate an ng-repeat from $scope.recordlist, but right now, when I load the page, the ng-repeat will be empty, and when I click anywhere on the page, it will get populated.
Also, when I add a new item in the DB, it will only show after page reload and click on window, not automatically.
<div class="row msf-row" ng-repeat="record in recordlist.rows">
<div class="col-md-1">{{record.doc.time}}</div>
<div class="col-md-1"><strong>{{record.doc.car}}</strong></div>
<div class="col-md-2">{{record.doc.driver}}</div>
<div class="col-md-2">{{record.doc.from}}</div>
...
</div>
What am I missing?

I think even if you have angular-pouchdb as a library, you need to inject it in the service, if not, you are using global PouchDB lib as normal and should be managing the $scope.$apply manually.
angular.module('msfLogger', ['pouchdb']).service('pouchService', PouchService);
function PouchService(pouchDB) {
var self = this;
self.db= pouchDB('FleetDB');
};

Related

Angulrjs: A controller doesn't send a value via a factory with the "as" statement

I've been teaching myself how to use the as statement of Angularjs's controller, but struggling to make controllers communicate with others, using the as syntax.
<script type="text/javascript">
angular.module('angularApp', [])
.factory('MessageService', function(){
var message = {
addedItem: "initialMessge"
};
return {
returnMessage: message//This is supposed to be the "var message" defined above
};
})
.controller('DiaplayingProductController', function(MessageService){
var instance = this;
this.data = {
message: MessageService.returnMessage.addedItem
};
})
.controller('ProductController', function($scope, $http, MessageService) {
var instance = this;
this.data = {
message: MessageService.message,
//There are other stuff here
};
this.addItem = function(productName) {
$http({
//other tasks
}).then(function addSucces(response) {
instance.data.message.addedItem = productName;
});
};
});
<span ng-controller="DiaplayingProductController as dpc" ng-bind="dpc.data.message"></span>
<div ng-controller="ProductController as pc">
#foreach ($products as $index => $product)
<div class="product">
<button ng-click="pc.addItem({{$product->name}})>
Add it to Cart
</button>
</div>
#endforeach
</div>
I use Laravel, so {{$product->name}} and #foreach are Laravel's expression.
In a nutshell,
There are one <span> and multiple <button>s, based on the result of #foreach (Again, I use Laravel, so this is basically the same thing as php's foreach)
When one of the <button> is pressed, the content of <span> is supposed to be updated.
The event is triggered in ProductController, which is supposed to update message of DiaplayingProductController, via MessageService.
The message is not going to be sent to the span tag.
This question may be silly. However, there are not many information resources out there which deal with this as statements, so I'd like to ask some advice here. Thank you in advance!
What's this #foreach?
There's a coma in your attributes. Shouldn't be there.
The expression in your ng-click has a missing parenthesis. Also, it should be an expression, therefore the {{}} have nothing to do here.
The data object are not shared between the controllers. You should:
use directives and pass the data using attributes ('=').
set the data in the $scope, which is not as good a solution
use a service as an intermediary (each controller can set/get the value
from that service)

ng-repeat does not update the html

I am new to Angular and need your help on an issue with the ng-repeat of my app.
Issue:
I have an html page (event.html) and in the corresponding controller of the file, I make a request to a firebase collection and update an array ($scope.events). The issue is that the data from firebase takes a few seconds to load and by the time data arrives to $scope.events, ng-repeat has already been executed and it displays an empty screen. The items are displayed correctly the moment I hit on a button in the HTML page (event.html).
Sequence of events:
I have a login page (login.html) where I enter a user name and phone number and I click on the register button. I've configured this click on the register button to go to the new state (event.html).
Here is the controller code for login.html:
$scope.register = function (user) {
$scope.user = user.name;
$scope.phonenumber = user.phonenumber;
var myuser = users.child($scope.user);
myuser.set({
phone : $scope.phonenumber,
Eventid : " ",
name : $scope.user
})
var userid = myuser.key();
console.log('id is ' +userid);
$state.go('event');
}
The controller of event.html (the state: event) has the following code:
var ref = new Firebase("https://glowing-torch-9862.firebaseio.com/Users/Anson/Eventid/");
var eventref = new Firebase("https://glowing-torch-9862.firebaseio.com/Events");
var myevent = " ";
$scope.events = [];
$scope.displayEvent = function (Eventid) {
UserData.eventDescription(Eventid)
//UserData.getDesc()
$state.go('myevents');
//console.log(Eventid);
};
function listEvent(myevents) {
$scope.events.push(myevents);
console.log("pushed to array");
console.log($scope.events);
};
function updateEvents(myevents) {
EventService.getEvent(myevents);
//console.log("success");
};
ref.once('value', function (snapshot) {
snapshot.forEach(function (childSnapshot) {
$scope.id = childSnapshot.val();
angular.forEach($scope.id, function(key) {
eventref.orderByChild("Eventid").equalTo(key).on("child_added", function(snapshot) {
myevents = snapshot.val();
console.log(myevents) // testing 26 Feb
listEvent(myevents);
updateEvents(myevents);
});
});
});
});
$scope.createEvent = function () {
$state.go('list');
}
event.html contains the following code:
<ion-view view-title="Events">
<ion-nav-buttons side="primary">
<button class="button" ng-click="createEvent()">Create Event</button>
<button class="button" ng-click="showEvent()">Show Event</button>
</ion-nav-buttons>
<ion-content class="has-header padding">
<div class="list">
<ion-item align="center" >
<button class= "button button-block button-light" ng-repeat="event in events" ng-click="displayEvent(event.Eventid)"/>
{{event.Description}}
</ion-item>
</div>
</ion-content>
</ion-view>
The button showEvent is a dummy button that I added to the HTML file to test ng-repeat. I can see in the console that the data takes about 2 secs to download from firebase and if I click on the 'Show Events' button after the data is loaded, ng-repeat works as expected. It appears to me that when ng-repeat operates on the array $scope.events, the data is not retrieved from firebase and hence its empty and therefore, it does not have any data to render to the HTML file. ng-repeat works as expected when I click the dummy button ('Show Event') because a digest cycle is triggerred on that click. My apologies for this lengthy post and would be really thankful if any of you could give me a direction to overcome this issue. I've been hunting in the internet and in stackoverflow and came across a number of blogs&threads which gives me an idea of what the issue is but I am not able to make my code work.
Once you update your events array call $scope.$apply(); or execute the code that changes the events array as a callback of the $scope.$apply function
$scope.$apply(function(){
$scope.events.push(<enter_your_new_events_name>);
})
If you are working outside of controller scope, like in services, directive, or any external JS. You will need to trigger digest cycle after change in data.
You can trigger digest cycle by
$scope.$digest(); or using $scope.$apply();
I hope it will be help you.
thanks
In your case you have to delay the binding time. Use $timeout function or ng-options with debounce property in your view.
you have to set a rough time taken to get the data from the rest API call. By using any one of the methods below will resolve your issue.
Method 1:
var myapp = angular.module("myapp", []);
myapp.controller("DIController", function($scope, $timeout){
$scope.callAtTimeout = function() {
console.log("$scope.callAtTimeout - Timeout occurred");
}
$timeout( function(){ $scope.callAtTimeout(); }, 3000);
});
Method 2:
// in your view
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ debounce: 1000 }" />

Use http cookie value in an Angular template

I have angular working in one of my ASP.NET MVC applications. I am using two html templates with Angular Routing. One is a list of current Favorites that comes from the database and is serialized into json from my Web API and used by angular to list those items from the database.
The second html template is a form that will be used to add new favorites. When the overall page that includes my angular code loads, it has a cookie named currentSearch which is holding the value of whatever the last search parameters executed by the user.
I would like to inject this value into my angular html template (newFavoriteView.html) for the value of a hidden input named and id'd searchString.
I have tried using jQuery, but had problems, plus I would much rather do this inside of angular and somehow pass the value along to my template or do the work inside the view(template). However, I know the latter would be bad form. Below is the code I think is important for one to see in order to understand what I am doing.
Index.cshtml (My ASP.NET VIEW)
#{
ViewBag.Title = "Render Search";
ViewBag.InitModule = "renderIndex";
}
<div class="medium-12 column">
<div data-ng-view=""></div>
</div>
#section ngScripts {
<script src="~/ng-modules/render-index.js"></script>
}
Setting the cookie in the MVC Controller
private void LastSearch()
{
string lastSearch = null;
if (Request.Url != null)
{
var currentSearch = Request.Url.LocalPath + "?" +
Request.QueryString;
if (Request.Cookies["currentSearch"] != null)
{
lastSearch = Request.Cookies["currentSearch"].Value;
ViewBag.LastSearch = lastSearch;
}
if (lastSearch != currentSearch)
{
var current = new HttpCookie("currentSearch", currentSearch){
Expires = DateTime.Now.AddDays(1) };
Response.Cookies.Set(current);
var previous = new HttpCookie("lastSearch", lastSearch) {
Expires = DateTime.Now.AddDays(1) };
Response.Cookies.Set(previous);
}
}
}
render-index.js
angular
.module("renderIndex", ["ngRoute"])
.config(config)
.controller("favoritesController", favoritesController)
.controller("newFavoriteController", newFavoriteController);
function config($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "/ng-templates/favoritesView.html",
controller: "favoritesController",
controllerAs: "vm"
})
.when("/newsearch", {
templateUrl: "/ng-templates/newFavoriteView.html",
controller: "newFavoriteController",
controllerAs: "vm"
})
.otherwise({ redirectTo: "/" });
};
function favoritesController($http) {
var vm = this;
vm.searches = [];
vm.isBusy = true;
$http.get("/api/favorites")
.success(function (result) {
vm.searches = result;
})
.error(function () {
alert('error/failed');
})
.then(function () {
vm.isBusy = false;
});
};
function newFavoriteController($http, $window) {
var vm = this;
vm.newFavorite = {};
vm.save = function () {
$http.post("/api/favorites", vm.newFavorite)
.success(function (result) {
var newFavorite = result.data;
//TODO: merge with existing topics
alert("Thanks for your post");
})
.error(function () {
alert("Your broken, go fix yourself!");
})
.then(function () {
$window.location = "#/";
});
};
};
favoritesView.html
<div class="container">
<h3>New Favorite</h3>
<form name="newFavoriteForm" ng-submit="vm.save()">
<fieldset>
<div class="row">
<div class="medium-12 column">
<input name="searchString" id="searchString" type="hidden"
ng-model="vm.newFavorite.searchString"/>
<label for="title">Name</label><br />
<input name="title" type="text"
ng-model="vm.newFavorite.name"/>
<label for="title">Description</label><br />
<textarea name="body" rows="5" cols="30"
ng-model="vm.newTopic.description"></textarea>
</div>
<div class="medium-12 column">
<input type="submit" class="tiny button radius" value="Save"/> |
Cancel
</div>
</div>
</fieldset>
</form>
</div>
My current attepts have been using jQuery at the end of the page after Angular has loaded and grab the cookie and stuff it in the hidden value. But I was not able to get that to work. I also thought about setting the value as a javascript variable (in my c# page) and then using that variable in angular some how. AM I going about this the right way?
Or should it be handled in the angular controller?...
I'm new to angular and the Angular Scope and a bit of ignorance are getting in the way. If any other info is needed I can make it available, thanks if you can help or guide me in the right direction.
You can do it by reading the cookie value using JavaScript, set it as a property of the $scope object and access it on the template.
//Inside your controllers
function favoritesController($http, $scope) {
//Get the cookie value using Js
var cookie = document.cookie; //the value is returned as a semi-colon separated key-value string, so split the string and get the important value
//Say the cookie string returned is 'currentSearch=AngularJS'
//Split the string and extract the cookie value
cookie = cookie.split("="); //I am assuming there's only one cookie set
//make the cookie available on $scope, can be accessed in templates now
$scope.searchString = cookie[1];
}
EXTRA NOTE
In AngularJS, the scope is the glue between your application's controllers and your view. The controller and the view share this scope object. The scope is like the model of your application. Since both the controller and the view share the same scope object, it can be used to communicate between the two. The scope can contain the data and the functions that will run in the view. Take note that every controller has its own scope. The $scope object must be injected into the controller if you want to access it.
For example:
//inject $http and $scope so you can use them in the controller
function favoritesController($http, $scope) {
Whatever is stored on the scope can be accessed on the view and the value of a scope property can also be set from the view. The scope object is important for Angular's two-way data binding.
Sorry if I'm misunderstanding or over-simplifying, but...assuming JavaScript can read this cookie-value, you could just have your controller read it and assign it to a $scope variable?
If JavaScript can't read the value, then you could have your ASP write the value to a JavaScript inline script tag. This feels yuckier though.
Update to show controller-as example.
Assuming your HTML looked something vaguely like this:
<div ng-controller="MyController as controller">
<!-- other HTML goes here -->
<input name="searchString" id="searchString" type="hidden" ng-model="controller.data.currentSearch"/>
Then your controller may look something like this:
app.controller('MyController', function ($scope, $cookies) {
$scope.data = {
currentSearch: $cookies.currentSearch
};
// Note that the model is nested in a 'data' object to ensure that
// any ngIf (or similar) directives in your HTML pass by reference
// instead of value (so 2-way binding works).
});

How to create own angular service with XHR properly?

I am very new about AngularJS things. Need to do file upload with other datas in form, I found some scripts and angular plugins but I am using my own service calls $xhr. I was able to send file but i got error, bug(not real error-bug, i just named like that) or i can not use AngularJS properly. Here it is:
.
JS
var app = angular.module('ngnNews', []);
app.factory('posts', [function () {...}]); // I reduced the codes
app.factory('$xhr', function () {
var $xhr = { reqit: function (components) { ... //My Xml HTTP Request codes here }}
return $xhr;
});
app.controller('MainCtrl', ['$http','$scope','$xhr','posts',
function ($http, $scope, $xhr, posts) {
$scope.posts = posts.posts;
$scope.files = [];
var newPost = { title: 'post one', upvotes: 20, downvotes: 5 };
$scope.posts.push(newPost);
$scope.addPost = function () {
$xhr.reqit({
form: document.getElementById('postForm'),
callbacks: {
success: function (result) {
if (result.success) {
console.log($scope.posts); //[FIRST OUT]
$scope.posts.push(result.post);
$scope.title = '';
console.log($scope.posts); //[SECOND OUT]
}
}
},
values: { upvotes: 0, downvotes: 0 },
files: $scope.files
});
...
}
}]);
.
HTML
<form action="/Home/FileUp" id="postForm" method="post" enctype="multipart/form-data">
<div class="form-group input-group">
<span class="input-group-addon">Post Title</span>
<input name="title" class="form-control" type="text" data-ng-model="title" />
</div>
<ul>
<li ng-repeat="file in files">{{file.name}}</li>
</ul>
<button class="btn btn-primary" type="button" data-ng-click="addPost()">Add New</button>
</form>
SCREEN
Sample post displayed in list
.
PROBLEMS
When I click first time Add New button everything works well until $scope.posts.push(result.post);. In console, [SECOND OUT] is here:
First object has $$hashKey but second object which sent from server(added by $scope.posts.push(result.post); function) doesn't have. I want to know why is this happening? But it's not only weird thing, when I second time click Add New button, everything completed successfully (No new logs in console, adding new post to list shown screen image above).
MAIN PROPLEM
I pushed returned value from the server but post list(in screen) is not affected when first click.
QUESTIONS
- What is happening? or
- What am I doing wrong? Thanks for any explanation.
You are doing nothing wrong with respect to $$hashkey if that is your concern. When you use ng-repeat with array of objects angular by default attaches a unique key to the items which is with the property $$hashkey. This property is then used as a key to associated DOM elements with the corresponding item in the array by identity. Moving the same object in array would move the DOM element in the same way in the DOM. You can avoid this (addition of additional property on the object by angular) by using track by with ng-repeat by providing a unique key on the object or a mere $index. So with that instead of creating a unique key and attaching it to $$haskey property angular will use the unique identifier you have provided to associate the DOM element with the respective array item.
ng-repeat="post in posts track by $index"
or (id you have a unique id for each of the object in the array, say id then)
ng-repeat="post in posts track by post.id"
And since you say you are using my xml http request code here, i am assuming it is not within the angular context so you would need to manually perform the digest cycle by using $scope.$apply() is on of those ways.
$scope.addPost = function () {
$xhr.reqit({
form: document.getElementById('postForm'),
callbacks: {
success: function (result) {
if (result.success) {
$scope.posts.push(result.post);
$scope.title = '';
$scope.$apply();//<-- here
}
}
},
But ideally you could wrap your xhr implementation with a $q and if you pass $q promise from your api, you wont need to perform a manual $scope.$apply() everywhere. Because $q promise chaining will take care of digest cycle invocation.

AngularJS: TypeError: undefined is not a function with chained selects

I have two select boxes, options for one of the boxes are loaded right away, and the second (child) select will get its options from a callback that queries an API. It is not an option to pre-load all possible options because there are 4200 records that would be loaded without the parent's selected value.
When the ng-change event of the parent box fires, a call is made:
function CertificateSearchCtrl($q, CPSIAService) {
var vm = this;
vm.products = [];
vm.categories = [];
vm.certficate = {};
vm.categoryselect = {};
vm.productselect = {};
I can call this via ng-init, or directly in the controller on first load
vm.loadCategories = function() {
CPSIAService.getCategories().then(function(results){
vm.categories = results;
});
};
OR I can call this in the same fashion (ng-init or direct via controller)
vm.findProducts = function(data) {
CPSIAService.getProductsByCategory(data.id).then(function(results){
vm.products = results;
});
};
...
But I can't call the two together at all, either through ng-change or direct through controller forcing a category ID into the findProducts() call.
This should, in turn, allow the child select to be populated with the "products" array. The controlling html (which is output via a directive) is this:
<div class="small-12 medium-6">
<select ng-model="vm.categoryselect" ng-change="vm.findProducts(vm.categoryselect)" ng-options="categories.category for categories in vm.categories track by categories.id" ng-cloak>
<option value="">(choose a category)</option>
</select>
</div>
<div class="small-12 medium-6">
<select ng-model="vm.productselect" ng-change="vm.loadCertificate(vm.productselect)" ng-show="vm.products.length>0" ng-options="products.description for products in vm.products track by products.sku" ng-cloak>
<option value="">(select a product)</option>
</select>
</div>
Even if I try to load the options for the child select initially (rather than through the ng-change event) - I get the same error. Here is the Chrome stack trace:
TypeError: undefined is not a function
at render (angular.js:25905)
at Scope.$get.Scope.$digest (angular.js:14280)
at Scope.scopePrototype.$digest (hint.js:1468)
at Scope.$get.Scope.$apply (angular.js:14493)
at Scope.scopePrototype.$apply (hint.js:1478)
at HTMLSelectElement.selectionChanged (angular.js:25657)
at HTMLSelectElement.eventHandler (angular.js:3011)angular.js:11598 (anonymous function)angular.js:8548 $getangular.js:14282 $get.Scope.$digesthint.js:1468 scopePrototype.$digestangular.js:14493 $get.Scope.$applyhint.js:1478 scopePrototype.$applyangular.js:25657 selectionChangedangular.js:3011 eventHandler
Here is a sample of the JSON data in question. I've linted/validated it and it is fine.
[{"sku":"2004","description":"ADHSVE PAPR BLK BDR8CT-12"},{"sku":"2005","description":"ADHSVE PAPR BLU BDR8CT-12"},{"sku":"2006","description":"ADHSVE PAPR RED BDR8CT-12"},{"sku":"0043630-5987","description":"BORD 50 CS ASST 60 CT-1"},{"sku":"51671","description":"SLFSTK BORDER BLK 2X12-12"},{"sku":"51672","description":"SLFSTK BORDER BLU 2X12-12"},{"sku":"51673","description":"SLFSTK BORDER RED 2X12-12"}]
Help!
I have noticed that I can, in fact load my child select options only if I don't attempt to make two calls to my service at one time. Maybe I'm misunderstanding promises? I thought they resolve with the .then() function, but it errors out when I try to make the second one complete, even though the API call is fine and data does come back as expected (see JSON above)
JQuery does not affect the error - same reproduction with or without jQuery included.
found the solution guys. In my service, I had this:
function CPSIAService($q, Restangular) {
var deferred = $q.defer();
var CPSIAService = {};
CPSIAService.getProductsByCategory = function(params) {
//original call
// var response = Restangular.one('compliance/products/by/category',params);
var response = Restangular.one('products.json'); //params from category would go here
response.getList().then(function(data) {
deferred.resolve(data);
});
return deferred.promise;
};
CPSIAService.getCategories = function() {
//original call
//var response = Restangular.all('compliance/categories/all');
var response = Restangular.all('categories.json');
response.getList().then(function(data) {
deferred.resolve(data);
});
return deferred.promise;
};
return CPSIAService;
}
Specifically, notice this at the top of the service:
var deferred = $q.defer();
If I were to make a call to the service after initial page load, the error would occur because I wasn't deferring the promise in the actual function I was calling. The solution was to go from this:
CPSIAService.getProductsByCategory = function(params) {
var response = Restangular.one('compliance/products/by/category',params);
response.getList().then(function(data) {
deferred.resolve(data);
});
return deferred.promise;
};
to this:
CPSIAService.getProductsByCategory = function(params) {
var deferred = $q.defer(); //defer here because we're calling it from the ng-change event fire
var response = Restangular.one('compliance/products/by/category',params);
response.getList().then(function(data) {
deferred.resolve(data);
});
return deferred.promise;
};
And now it works like a charm.
Had the same problem and solution was to update angular-mocks.js to the matching version as per this answer.

Categories

Resources