AngularJS ngRepeat Not Updating Display Properly - angularjs

I have have a page where I am using plupload to upload files and having a weird issue with a ng-repeat not updating properly. Here is the relevant code:
<div ng:app>
<div name="myForm" ng-controller="Ctrl">
<input ng-model="test" type="text" />{{test}}<div id="container" class="controls">
<div id="filelist">
<div ng-repeat="file in filesToUpload">{{file.name}} ({{file.size}}) <b>{{file.percent}}</b></div>
</div>
<br />
<a id="pickfiles" href="#">[Select files]</a>
</div>
</div>
</div>​
function Ctrl($scope) {
$scope.test = '';$scope.filesToUpload = [{id: 1, name: 'test', size: '123kb'}];
$scope.addItem = function(object) {
$scope.filesToUpload.push(object);
}
$scope.uploader = new plupload.Uploader({
runtimes : 'html5,flash,browserplus,gears',
browse_button : 'pickfiles',
container : 'container',
max_file_size : '10mb',
url : 'upload.php',
flash_swf_url : '/plupload/js/plupload.flash.swf'
});
$scope.uploader.init();
$scope.uploader.bind('FilesAdded', function(up, files) {
$scope.filesToUpload = [];
$.each(files, function(i, file) {
$scope.addItem({
id: file.id,
name: file.name,
size: plupload.formatSize(file.size)
});
});
console.log($scope.filesToUpload);
up.refresh(); // Reposition Flash/Silverlight
});
}​
Here is a trimmed down jsfiddle showing the issue happening:
http://jsfiddle.net/9HuUC/
To reproduce this issue do the following:
Click on [select files] and selects a few files (notice how you don't see the files displayed anywhere on the output)
Type any character into the input box (magically the files that you select know appear)
What would cause this type of behavior? I mean I know that the data is properly being set in $scope.filesToUpload because I have the console.log() there and even checked it in Batarang and it loods good there but for some reason something else needs to be updated for the display to be updated.
Interestingly enough, I have another ng-repeat that is working fine on the same page. I am wondering if it has anything to do with where the code is (being inside the FilesAdded event on the uploader).

The issue is due to the fact that the FilesAdded callback is executed outside the scope of AngularJS (it's called by the uploader), therefore the scope updates won't be triggered.
To solve this, just add the $scope.$apply call in the callback, encapsulating your existing code:
$scope.uploader.bind('FilesAdded', function(up, files) {
$scope.$apply( function() {
$scope.filesToUpload = [];
$.each(files, function(i, file) {
$scope.addItem({
id: file.id,
name: file.name,
size: plupload.formatSize(file.size)
});
});
console.log($scope.filesToUpload);
up.refresh(); // Reposition Flash/Silverlight
});
});
With this update, it's working in the fiddle. For reference see the AngularJS official documentation, $apply method of the scope object:
http://docs.angularjs.org/api/ng.$rootScope.Scope

Related

Phantom Js with Node and Angular - ng-repeat not rendering

I have an angular App, using angular-ui-router.
Page I have built looks fine.
I am trying to take a snapshot of the html and create a pdf.
To do this, I am using Phantom.
I can trigger and create a PDF fine, but it doesn't render my ng-repeat section of the HTML.
My HTML:
<div>{{vm.hello}}</div>
<div class="row">
<div class="col-xs-2">Heading</div>
</div>
<div class="row" ng-repeat="record in vm.records">
<div class="col-xs-2">Space</div>
<div class="col-xs-2" ng-bind="record.id"></div>
</div>
My controller triggers on load to populate vm.records.
vm.hello is hardcoded and renders fine.
Nothing in my ng-repeat loads.
My Node js code:
var phantom = require('phantom');
function pdf(req, res) {
console.log('pdf function entered');
var url = "http://myapp.com/page";
phantom.create().then(function(ph) {
ph.createPage().then(function(page) {
page.viewportSize = {
width: 1400,
height: 1024
};
page.property('onLoadFinished', function(status) {
console.log("Load Finished with status " + status);
});
page.open(url).then(function(status) {
page.render('test.pdf', {
format: 'pdf',
quality: '100'
}).then(function() {
console.log("File Created");
page.close();
ph.exit();
});
});
});
});
res.status(200).send();
}
Can anyone shed some light as to why the ng-repeat won't work? I've tried adding delays in and such based on other questions/answers, but just can't seem to get it working.
In your controller you have to call
window.callPhantom();
when the render is completed
Next is you have to call it inside $timeout like this
$timeout(function(){
window.callPhantom();
return;
})
to avoid call it before binding

Blaze dynamic data binding

Following is the angular example for simple input field model binding.
<div ng-app>
<div>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<h1>Hello {{yourName}}!</h1>
</div>
</div>
How can I replicate the same functionality using meteor-blaze with Jade, i.e. input field change event will update the corresponding helper.
Update
Below is my code in Blaze and Jade:
.form-group
lable
| Click buttons to increase their values:
input#user(type="text")
span
|{{usernameinput}}
Below is javascript code:
Template.unitconversion.helpers({
'nm': function(){ return "100" }
});
Template.unitconversion.events({
'keyup #user': function(evt, tmpl){
var tor = tmpl.find('#user').value;
console.log(tor);
//console.log(evt);
//console.log(tmpl);
Template.unitconversion.helpers({
'usernameinput': function()
{
return tor
}
});
}
});
I was expecting the helper function to update the 'usernameinput' whenever user input is changed but nothing happens. I do get the expected console output whenever input field changes.
Any help would be appreciated to solve it.
Let me answer your question in "spacebars". I think you should be able to translate it to "jade" easily. First make sure you add reactive-dict package:
meteor add reactive-dict
It's little more complicated than you might have expected, but the following code should do the the job pretty well:
<template name="hello">
<h1>Wellcome {{myName}}!</h1>
<p>
Tell your name <input type="text" name="myName" value="{{myName}}"/>
</p>
</template>
and the corresponding javascript:
Template.hello.onCreated(function () {
this.state = new ReactiveDict();
});
Template.hello.helpers({
myName: function () {
return Template.instance().state.get('myName');
},
});
Template.hello.events({
'keyup input[name]': function(e, t) {
t.state.set($(e.target).attr('name'), $(e.target).val());
}
});
The reason this is so "complicated" is because Meteor does not offer a built-in two-way-data binding support. It does not mean it can't be done as the above example shows.
You can read more about two-way-data-binding in meteor cookbook here:
https://github.com/awatson1978/meteor-cookbook/blob/master/cookbook/data-binding.md#two-way-data-binding-1
As per Michel Floyd guidance following is the working code:
Jade Template:
.form-group
lable
| Click buttons to increase their values:
input#user(type="text")
span
| {{usernameinput}}
Javascript:
var usernameinput = new ReactiveVar(0);
Template.unitconversion.helpers({
usernameinput: function(){ return usernameinput.get();
}
});
Template.unitconversion.events({
'keyup #user': function(evt, tmpl){
var tor = tmpl.find('#user').value;
console.log(tor);
usernameinput.set(tor);
}
});

Angular binding model in scope issue?

Hi I am new to the angular js and trying to write the chat application using the socket io and angular js with ionic for android platform. But in my chat page there is one issue.
I am trying to bind the textbox to the $scope.message variable using ng-model but it is not getting bind as the test when i show the same variable value in page itself it works as it is but in controller i gets value as undefined or empty
index.html
<body ng-app="farmApp" ng-controller="farmAppController" >
<ion-content>
<ul id="Messeges">
<li class="right">Welcome to Chat</li>
<li ng-repeat="chatMessage in messages">
{{chatMessage}}
</li>
</ul>
<form ng-submit="sendMessage()">
<input placeholder="Your message" ng-model="message">
<input type="submit" name="send" id="send" value="Send" class="btn btn-success">
<br/>
{{message}} //This updates the value as i type in textbox
</form>
</ion-content>
</body>
but when i see print that model on console it shows undefined when i define at the start in controller then it shows empty value
Controller.js
var farmAppControllers = angular.module('farmAppControllers',[]);
farmAppControllers.controller('farmAppController',['$scope','socket',function($scope,socket){
$scope.messages = [];
$scope.message = ''; //When i don't declare then console shows undefined on sendMessage function if declared then empty
socket.on("update", function (data) {
console.log(data);
$scope.messages.push(data);
});
$scope.sendMessage = function (){
console.log($scope);
console.log($scope.message); // This shows undefined or empty on console
socket.emit("msg",$scope.message);
$scope.messages.push($scope.message);
$scope.message ='';
};
}]);
My app.js
'use strict';
var farmApp = angular.module('farmApp', ['farmAppControllers','farmAppServices','ionic']);
And services.js for socket wrapper
var farmAppServices = angular.module('farmAppServices',[]);
farmAppServices.factory("socket",function($rootScope){
var socket = io.connect();
return {
on:function (eventName,callBack){
socket.on(eventName,function(){
var args = arguments;
$rootScope.$apply(function(){
callBack.apply(socket,args);
});
});
},
emit:function(eventName,data,callBack){
socket.emit(eventName,data,function(){
var args = arguments;
$rootScope.$apply(function(){
if(callBack){
callBack.apply(socket,args);
}
});
});
}
};
});
i stuck here... i try to google it but not able to solve it. In case of confusion feel free to comment. Any help would be great.
UPDATE
When i used the first answer of this question the problem got solved but still not clear why ng-submit is not working ? Any Idea Why ? Because it seems i am still unable to update the view scope from the controller ?
I think the problem is <ion-content> which is a directive and seems to create its own scope. From the docs:
Be aware that this directive gets its own child scope. If you do not understand why this is important, you can read https://docs.angularjs.org/guide/scope.
Therefore your message property isn't in the scope of your controller.
Objects are passed by reference and sendMessage is a function respectively an object, thats why it's still called correctly from the child scope.
What you should do is create an object with a name that makes sense to "package" the properties you want to share.
$scope.package = {}
$scope.package.messages = [];
$scope.package.message = 'You default message...';
And then in your function:
$scope.package.messages.push($scope.package.message);
And in your template:
<input placeholder="Your message" ng-model="package.message">
Here is a plunker with a working solution. It throws some random errors, I actually don't know ionic. But the example works and everything else doesn't matter.

Perform task after model's DOM is displayed in view

I have a code snippet in my content which is a model fetched from http. I am using syntax highlighter to prettify the code. So I need to call a javascript function as soon as the DOM is updated for that particular model.
Here is a sample code to make it clear. I am using alert to demonstrate it. In my project I would use a third party plugin which will find matching dom elements and remodel them.
Here,
I want the alert to occur after the list is displayed
jsfiddle :
http://jsfiddle.net/7xZde/2/
My controller has something like this.
$scope.items = Model.notes();
alert('test');
alert comes even before the items list is shown, I want it after the list is displayed.
Any hint to help me achieve this.
We need to use $timeout ,
$scope.items = Model.notes();
$timeout(function () {
alert('test');
})
Yeah it was silly , $timeout seemed to be a misnomer to me. I am 2 days old to angularjs . Sorry for wasting your time.
Lucky for you, I wanted to do the exact same thing. Mutation observers are the path forward, but if you need backwards compatibility with older browsers, you'll need a bit more code than this.
Working plunker for Firefox, Chrome, and Safari.
Javascript:
var app = angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.name = 'World';
})
.directive('watchChanges', function ($parse, $timeout) {
return function (scope, element, attrs) {
var setter = $parse(attrs.watchChanges).assign;
// create an observer instance
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$timeout(function () {
var text = angular.element('<div></div>').text(element.html()).text();
setter(scope, text);
});
});
});
// configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
observer.observe(element[0], config);
};
});
HTML:
<body ng-controller="MainCtrl">
<div watch-changes="text">
<p>Hello {{ name }}</p>
<label>Name</label>
<input type="text" ng-model="name" />
</div>
<pre>{{text}}</pre>
</body>

AngularJS - Using a Service as a Model, ng-repeat not updating

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/

Resources