Changes in scope variable not updating DOM - javascript

I came across this angularJs issue where a change in an array is not updating the DOM, even though there is a binding in place.
I have tried everything from using $scope.$apply, $scope.$evalAsync and also tried using vm instead of $scope.
AngularJs code:
app.controller("controller", function ($scope){
$scope.managers = [];
$scope.RefreshManagerList = async function(){
var response = await App.getStoreManagers();
$scope.managers = response;
};
$scope.Setup = async function(){
await App.start();
await $scope.RefreshManagerList();
};
$scope.Setup();
});
HTML Code:
<header>
<h1>Administrator Panel</h1>
</header>
<div class="jumbotron">
<h3>Store Manager List</h3>
<ul class="list-group">
<li class="list-group-item" ng-repeat="manager in managers">
{{manager}}
</li>
</ul>
</div>
What is expected to happen is that when $scope.managers is set to response, the list should be rendered in the HTML page.
EDIT:
Tried doing this also doesn't work. Console.log still gives the expected object to be displayed in the list. If I interact with the page in the slightest way, the list automatically loads:
app.controller("administratorController", function ($scope){
$scope.managers = {content: null};
$scope.RefreshManagerList = function(){
App.getStoreManagers().then(function(response){
$scope.managers.content = response;
console.log($scope.managers.content);
});
};
$scope.Setup = function(){
App.start().then(function(){
$scope.RefreshManagerList();
});
};
$scope.Setup();
});

Related

How to implement Infinite Scrolling using Node.js, Angular.js and Firebase?

UPDATE 8:
CODE:
<% include ../partials/header %>
<script src="https://www.gstatic.com/firebasejs/3.5.2/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>
<script>
var config = {
info
};
firebase.initializeApp(config);
var fb = firebase.database().ref("posts/fun");
var app = angular.module('app', ['firebase']);
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
$scope.getDataset = function() {
fb.orderByChild('id').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
console.log("THE VALUE:"+$scope.data);
});
_start = _start + _n;
_end = _end + _n;
};
$scope.getDataset()
});
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);
</script>
<div class ="containerMarginsIndex">
<div ng-controller="ctrl">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
</div>
</div>
<% include ../partials/footer %>
SITUATION:
Ok, I have reworked my Firebase database architecture and changed the Firebase rules.
I am now certain the Firebase function returns a value (it is logged in the console).
But I still get the following error:
This HTML:
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
gets REPLACED by this once RENDERED:
<!-- ngRepeat: d in data --> == $0
What have I done wrong ?
It's not displaying in your view because you have nothing on the $scope and you're not using {{}} to interpolate your data. See the following changes:
Assign data to a $scope variable to be used in the view:
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
var getDataset = function() {
fb.orderByChild('time').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
});
_start = _start + _n;
_end = _end + _n;
And your view, use ngRepeat and {{}} to interpolate:
<div class ="containerMarginsIndex">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{post.id}}" target="_blank">
<img class= "imgIndex" src="/images/uploads/{{post.image}}" >
</a>
</div>
<div class="postScore">({{d.upvotes - d.downvotes}}) HP</div>
</div>
</div>
Add your scroll listener within your controller. The function more does not exist indeed, however you do have a $scope.more method.
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
// ORDERED BY TIME:
var ref = firebase.database().ref("posts/fun");
var scrollRef = new Firebase.util.Scroll(ref, "time");
$scope.posts = $firebaseArray(scrollRef);
scrollRef.scroll.next(5);
// AS DATA APPEARS IN DATABASE ORDERED BY TIME:
ref.once('value', function(snap) {
$scope.rawdata = snap.val();
$scope.$apply();
});
$scope.more = function() {
scrollRef.scroll.next(5);
};
// Add scroll listener
window.addEventListener('scroll', function() {
if (window.scrollY === document.body.scrollHeight - window.innerHeight) {
$scope.$apply($scope.more);
}
});
});
Note that I am calling $scope.more within $scope.$apply so that the scope is digested at the end of the call. Indeed a JS listener on a window scroll event is out of the Angular lifecycle so we need to manually $digest the scope for Angular to update all its watchers and update the HTML. Search online about $scope.$apply if you want to learn more about it.
About your first problem
Your angular application is not started because angular is never initialized. For that you need either to load it synchronously and use the ng-app directive, or if you don't want to change anything with your code you can simply add these lines after your module and controller definition:
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);
You need to include $scope.$apply() because the the scroll event executes outside Angular's context.
Also the event listener should be inside your controller so that the scoped more function is accessible.
Here's an updated fiddle:
https://jsfiddle.net/xue8odfc/2/
I'd say the problem with Angular not resolving the {{post.image}} etc. is due to incompatibilities among the libraries you are referencing. I suggest testing using the versions from the working jsfiddle:
<script src="https://cdn.firebase.com/js/client/2.0.3/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.min.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>

Can not update view from controller after promise is returned

I have a page that will show a list of items. The list of items can be be constantly changing, so whenever the use goes to this page or refreshes the page, he would see the new list of items.
I am currently use Parse to store my items and I will use a promise to interact with the Parse API and get my items. A very simplified example is below.
The basic flow is that when index.html is shown, HomeCtrl.js will load and call ItemService.getItems() to get the items, attach them to $scope.items and then display any changes to the view. ItemService.getItems() is a promise provided from the Parse API.
app.js
var myApp = angular.module('myApp',[]);
// Parse API keys
Parse.initialize('MCNXFhdenmpSRN1DU8EJrG3YROXaX4bg0Q5IYwKp', 'XZfWd7J9xGSZQOizu0BoAtIUYtECdci4o6yR76YN');
index.html
<html ng-app = 'myApp'>
<script src="//www.parsecdn.com/js/parse-1.6.2.min.js"></script>
<head>
<title> My Simple Example that doesn't work! </title>
</head>
<body layout="row">
<div ng-controller = "HomeCtrl as ctrl">
<div ng-repeat="item in ctrl.items">
{{item.id}}
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.min.js"></script>
<script src = "app.js"> </script>
<script src = "HomeCtrl.js"> </script>
<script src = "ItemService.js"> </script>
</body>
</html>
ItemService.js
var myApp = angular.module('myApp');
myApp.service('ItemService', function(){
// gets all Items
this.getItems = function() {
var Item = Parse.Object.extend("Item");
var query = new Parse.Query(Item);
return query.find().then(function(items){
return items;
});
return this;
}
});
HomeCtrl.js
var myApp = angular.module('myApp');
myApp.controller('HomeCtrl',[ 'ItemService', '$scope',function(ItemService, $scope){
$scope.items = [];
ItemService.getItems()
.then(function(results){
$scope.$apply(function() {
$scope.items = results;
console.log($scope.items);
});
});
console.log($scope.items);
}]);
$scope.items does change in the $scope.$apply function (I print it out and can see some items) , however it is not changed for the view. When I print $scope.items after ItemService.getItems(), I print out an empty array.
Am I incorrectly updating $scope.items after calling the promise or is there some concept that I am not understanding?
Any help is appreciated.
EDIT
from 456's answer, I see that I made a mistake in my ng-repeat and his answer works. However I would like to keep using controllerAs syntax. I have tried to update this.items in a $scope.$apply function but it does not work - this.items is modified, but the change is not represented in the view. My modifications are below
index.html
<div ng-controller = "HomeCtrl as ctrl">
<div ng-repeat="item in ctrl.items">
{{item.id}}
</div>
</div>
HomeCtrl.js
var myApp = angular.module('myApp');
myApp.controller('HomeCtrl',[ 'ItemService', '$scope',function(ItemService, $scope){
this.items = [];
ItemService.getItems()
.then(function(results){
$scope.$apply(function() {
this.items = results;
console.log(this.items);
});
});
}]);
The error you are getting is due to the fact that 'this' will point to the window object when the control comes to your then function.
ItemService.getItems()
.then(function(results){
//Here 'this' will point to window
//window.items is undefined
});
Hence the error.
You can solve this in many ways,one of which is using another object to point to this.
var myApp = angular.module('myApp');
myApp.controller('HomeCtrl',[ 'ItemService', '$scope',function(ItemService, $scope){
var that = this;
that.items = [];
ItemService.getItems()
.then(function(results){
that.items = results;
});
}]);
Try this if it works for you.
U should call it in html like this-
<div ng-repeat="item in items">

AngularJS two controllers... overcoming $scope.$on when page loads

Still wrapping my head around AngularJS. I have a pretty basic page which has two controllers. One has text input to take tags (for searching). The other calls http.get(), using the tags to get the images... it does a bit of ordering and then they're displayed. Following some info on the page below, I came up w/ something.
Can one controller call another?
Caveat: It requires onClick() to execute. Generally I want it to load up on page load with zero tags... and after a click with the tags. I've seen a few suggestions in other threads here, but I'm not quite sure how to pull them off in this case. imagesController runs on load, but of course doesn't get past the 'handleBroadcast' bit.
var myModule = angular.module('myModule', []);
myModule.run(function($rootScope) {
$rootScope.$on('handleEmit', function(event, args) {
$rootScope.$broadcast('handleBroadcast', args);
});
});
function tagsController($scope) {
$scope.handleClick = function(msg) {
$scope.tags = msg;
$scope.$emit( 'handleEmit', { tags: msg });
};
}
function imagesController($scope,$http) {
$scope.$on('handleBroadcast', function(event, args) {
$scope.tags = args.tags;
var site = "http://www.example.com";
var page = "/images.php";
if ( args.tags ) {
page += "?tags=" + $scope.tags;
}
$http.get(site+page).success( function(response) {
// SNIP
$scope.data = response;
});
});
}
The HTML is quite trivial. Slightly simplified, but it should suffice.
<form name="myForm" ng-controller="tagsController">
<div class="left_column">
<input class="textbox_styled" name="input" data-ng-model="tags"><br>
<button ng-click="handleClick(tags);">Search</button><br>
</div>
</form>
<div class="centered" data-ng-controller="imagesController">
<span class="" data-ng-repeat="x in data">
{{x}}
</span>
</div>
Not sure I fully understood, but if you want to show it with no tags on load, simply emit the event when the controller loads:
function tagsController($scope) {
$scope.handleClick = function(msg) {
$scope.tags = msg;
$scope.$emit( 'handleEmit', { tags: msg });
};
$scope.$emit( 'handleEmit', { tags: "" }); // or $scope.handleClick("");
}

Ember - Asynchronous data in nested view

I have a list of messages. When a particular message is clicked it loads the details. This part works fine.
I want to load some other related data asynchronously when the clicked message is loaded. For that I'm nesting a view inside my messageView. However I am unable to load and access the data.
Here is my template
<script type="text/x-handlebars" id="message">
{{#view "messageThread" contentBinding="this"}}
{{#each message in view.getConversation}}
<div>Message body</div>
{{message.body}}
{{/each}}
</div>
{{/view}}
</script>
Here is the messageThreadView used in the template above
App.MessageThreadView = Ember.View.extend({
getConversation: function(){
var msg = this.get('context').model;
var sender_id = msg.sender.id;
var recipient_id = msg.recipient.id;
downloadConversation(recipient_id, sender_id);
return this.get('current_conversation');
}.property('current_conversation'),
});
Here is the asynchronous data load function called in the view above
function downloadConversation(recipient_id, sender_id){
$.getJSON(<a url>)
.then(function(data){
App.set('current_conversation', data['objects']);
});
}
How do I get view.getConversation to work as expected i.e load the data when it becomes available?
Here's the simplest pattern for asynchronous properties, especially when they are a collection. You essentially return a collection reference (in this case convo), then you asynchronously populate that collection from the reference.
App.MessageThreadView = Ember.View.extend({
getConversation: function(){
var msg = this.get('context').model,
sender_id = msg.sender.id,
recipient_id = msg.recipient.id,
convo = [];
$.getJSON(<a url>).then(function(data){
data.forEach(function(item){
convo.pushObject(item);
});
});
return convo;
}.property(), // this should be watching sender and receipient
});

Automatic two way binding with Knockout

I'm just getting started with Knockout.js and i have a view(html) which is supposed to be populated by data from a rest api via jquery's $.getJSON method.
When i run the app, nothing shows but using firebug i can see that the 'GET' query returns a status code of 200 and the right data.
I'm at a fix as to why nothing shows in the view since the bindings in Knockout.js are supposed to be automatic.
Below is my code.
Thanks
<div id ='main'>
<!-- ko foreach: posts -->
<p>Hello</p><span data-bind="text: title"></span></p><p data-bind="text: content"></p>
<p data-bind="text: author"></p><p data-bind="text: date"></p>
<!-- /ko -->
</div>
</body>
<script type="text/javascript">
function Post(data){
this.title = ko.observable(data.title);
this.content = ko.observable(data.content);
this.author = ko.observable(data.author);
this.date = ko.observable(data.date)
}
function PostListViewModel(){
var self = this;
self.posts = ko.observableArray([]);
$.getJSON("/posts", function(getPost){
var mappedPost = $.map(getPost, function(item){
return new Post(item)
});
self.posts(mappedPost);
});
}
var postlistviewmodel = new PostListViewModel();
ko.applyBindings(postlistviewmodel);
</script>
This should be:
$.getJSON("/posts", function(getPost){
var mappedPosts = $.map(getPost, function(item){
return new Post(item)
});
self.posts(mappedPosts);
});
wouldn't do self.posts.push(mappedPosts[i]) at all. You should just pass mappedPosts through the ko binding in order to update the listeners.
If your just getting the latest posts and want to update your current list simply do:
var allPosts = self.posts().concat(mappedPosts);
self.posts(allPosts);
You don't need the model to have ko.observable if you're just displaying them. If you want to edit model as well, then leave as.
Also, I tend to do this for single or multiple view models:
ko.applyBindings({viewModel : new viewModel() };
This allows for having multiple named view models. Access scope using: $root.viewModel
This is what I did earlier: http://jsfiddle.net/jFb3X/
Check your code against this fiddle then.
Script tags also need to be above the closing body tags
<html>
<head>
</head>
<body>
<!-- all your html content -->
<script type="text/javascript">
var viewModel = function () {
}
ko.applyBindings({viewModel : new viewModel()});
</script>
</body>
</html>
Is it something as simple as waiting for the DOM to be ready?
Are you able to try the following:
$(function () {
ko.applyBindings(postlistviewmodel);
});
Source: I've done this a few times and been stumped for a bit trying to see what I did wrong. :-)
(As a style thing, I'd also move the /body to after the /script - probably not related to your issue though).
I suspect you get multiple posts from /posts. You only push a single item (array).
...
$.getJSON("/posts", function(getPost){
var mappedPosts = $.map(getPost, function(item){
return new Post(item)
});
for(var i = 0; i < mappedPosts.length; i++) {
self.posts.push(mappedPosts[i]);
}
});
...

Categories

Resources