Scope not updating changes in the model - javascript

I have an expandable form that generates an object with two attributes, a title and description. This object successfully submits to my database as a json object. I'm currently using an Angular (1.3.2) front end that interacts with Tastypie as the interface layer with my Django (1.7) backend. The problem is that I never observe updates to my home page after adding a new object to the db. I need to refresh the page for the object to appear which is not ideal.
home.html
<div class="protocol-list-container">
<div ng-app="protocolApp"
id="protocol-list">
<div class="new-protocol-container" ng-controller="protoCtrl">
<h4>Add New Protocol</h4>
<button type="button"
ng-click="toggle()"
id="id_new">
<span class="glyphicon glyphicon-plus"></span>
</button>
<div ng-hide="visible" class="protocol-new">
<form name="newProtocolForm" novalidate>
<input type="text"
id="id_new_title"
placeholder="Title"
ng-model="protocol.title"
required /><br>
<input type="text"
id="id_new_desc"
placeholder="Description"
ng-model="protocol.description"
required /><br><br>
<input type="submit"
id="id_submit_new_protocol"
value="New Protocol"
ng-click="submit(protocol)"
ng-disabled="newProtocolForm.$invalid">
</form>
{% verbatim %}
<pre>form = {{ protocol | json}}</pre>
{% endverbatim %}
</div>
<div class="protocol">
<h4>My Protocols</h4>
<li ng-repeat="protocol in protocols">
{% verbatim %}
<div><span ng-bind="protocol.title"></span></div>
{% endverbatim %}
<div> - <span ng-bind="protocol.description"></span>
</li>
<br>
</div>
</div>
</div>
app.js
angular.module('protocolApp', [])
.factory('protocolFactory', ['$http', function($http) {
var urlBase = '/api/v1/protocol/';
var protocolFactory = {};
protocolFactory.getProtocols = function() {
console.log('getProtocols called');
return $http.get(urlBase);
};
protocolFactory.addProtocol = function(protocol) {
console.log('addProtocol called');
return $http.post(urlBase, protocol);
};
return protocolFactory;
}])
.controller('protoCtrl', ['$scope', 'protocolFactory',
function ($scope, protocolFactory) {
$scope.visible = true;
var self = this;
getProtocols();
function getProtocols() {
protocolFactory.getProtocols()
.success(function(data) {
$scope.protocols = data;
})
.error(function(error) {
console.log('error retrieving protocols');
});
}
$scope.toggle = function() {
$scope.visible = !$scope.visible;
var self = this;
var protocol = {};
self.submit = function() {
var protocol = {title: self.title, description: self.description};
console.log('clicked submit with ', self.protocol);
protocolFactory.addProtocol(self.protocol)
.success(function(response) {
console.log('protocol added');
$scope.protocol = null;
})
.error(function(error) {
console.log('post to api failed');
});
// gives the behavior I want, but ultimately crashes chrome
// $scope.$watch('protocols', function(newVal, oldVal) {
// protocolFactory.getProtocols()
// .success(function(data) {
// $scope.protocols = data;
// console.log('watcher data', data);
// });
// }, true);
};
};
}]);
I've done some testing with a $scope.$watch function (commented out), but this either shows the new object and never stops (true removed) or does not update (but tells me that there is an extra object in the data based on the console statement) (true present).
Any help would be appreciated.

When the database gets updated, how does the front end know that it should get the latest data unless we tell it to ? You don't have some kind of sockets between the server and front end, looking for events and making the front end to get the latest data...
So, When you post the data to backend and database got updated, make a call to getProtocols(), in the success callback of submit.
In your case of using $watch(), you are repeatedly getting the protocols from backend, which updated the scope variable, which again fired the callback repeatedly and browser crashed.

Related

AngularJs - Bind to function

I've had a simple setup displaying a variable using ng-bind-html. But now requisites have changed and I part to display comes from a collection and needs to display a pager.
So I thought about using a functionI , thinking that it would be easy, but failing miserably at it.
The expected workflow:
Via AJAX I load the content of a select field (working)
When the user selects one of the options, the function should get called to paint the content from the collection that is assigned to the selected option, including a pager to navigate within the collection (not working).
What I have so far:
{% extends 'BDAMainBundle::layout.html.twig' %}
{% block title %}Kurs-Auswahl{% endblock %}
{% block body %}
<div data-ng-controller="SingleCourseDownloadCtrl">
<div class="row">
<div class="col-xs-8">
{{ textSnippet('snippet.solo_download_selection')|raw }}
<div data-ng-show="loading">
<span class="inline-ajax-indicator"></span>
</div>
<div data-ng-hide="loading || courses.length == 0" class="input-group">
<select
class="form-control"
data-ng-change="tcAccepted = false"
data-ng-model="course"
data-ng-options="c|courseSelectionCourseLabel for c in courses"
><option value="">Bitte wählen:</option></select>
....
<div data-ng-show="course">
<div>
{% verbatim %}
{{ paged_weekly_exercises }}
{% endverbatim %}
</div>
</div>
</div>
....
{% endblock %}
and:
app.controller('SingleCourseDownloadCtrl', ['$scope', '$window', 'CourseManager', 'RemoteRouteConfig', function ($scope, $window, CourseManager, RemoteRouteConfig) {
$scope.courses = [];
$scope.loading = true;
$scope.paged_weekly_exercise;
$scope.course = [];
// load
CourseManager.getAvailableForSoloDownload().then(function (response) {
$scope.courses = response;
$scope.loading = false;
$scope.$watch('course', function(){
if(!course.length){
return false;
}
$scope.paged_weekly_exercise = $scope.course.weekly_exercises[0].content;
});
});
// starts the given course for single download
$scope.startCourse = function startCourse(course) {
$window.location = RemoteRouteConfig.course.startSoloDownload({
course: course.id
});
};
}]);
But I feel I went a totally wrong path. How should I go to accomplish my goal?
I have minimal experience with Angular, but am quite decent at Javascipt and JQuery.
Instead of adding a watcher in your controller, just use the ng-change:
<select
class="form-control"
data-ng-change="courseChanged()"
data-ng-model="course"
data-ng-options="c|courseSelectionCourseLabel for c in courses"
In your controller:
$scope.courseChanged = function() {
$scope.tcAccepted = false;
var course = $scope.course; // the used ng-model
if(!course.length){
return;
}
$scope.paged_weekly_exercise = course.weekly_exercises[0].content;
}

How to display a returned json in angular view?

I am implementing a search in the github repository.
I need to display the information that i get from here: https://api.github.com/search/repositories?q=bootstrap . for instance into a view or HTML
<div ng-app="newsearchApp">
<div ng-controller="MainCtrl">
<form action="#/about" method="get">
<input ng-model="searchText" />
<button ng-click="search()">Search</button>
</form>
</div>
</div>
the code for searching the Github repository;
angular.module('newsearchApp')
.controller("MainCtrl", ["$scope", function($scope) {
$scope.searchText = "";
$scope.search = function() {
console.log($scope.searchText);
var item = $scope.searchText;
// console.log(item)
var GithubSearcher = require('github-search-api');
var github = new GithubSearcher({username: 'test#something.com', password: 'passwordHere'});
var params = {
'term': $scope.searchText
};
//i am not certain about the 'userData'
github.searchRepos(params, function(data) {
console.log(data);
$scope.userData = data; //i am not certain about the 'repoData'
});
} }]);
the problem is here, when populating the json object to HTML
<div ng-repeat="repo in userData | filter:searchText | orderBy:predicate:reverse" class="list-group-item ">
<div class="row">
<div class="col-md-8">
<h4>
<small>
<span ng-if="repo.fork" class="octicon octicon-repo-forked"></span>
<span ng-if="!repo.fork" class="octicon octicon-repo"></span>
<small>{{repo.forks_count}}</small>
</small>
<a href="{{repo.html_url}}" target="_blank" >
{{repo.name}}
</a>
<small>{{repo.description}}</small>
<small>{{repo.stargazers_count}}</small>
<a href="{{repo.open_issues_count}}" target="_blank" >
Open Issues
</a>
<small>{{}}</small>
</h4>
</div>
</div>
</div>
the results are null on the HTML but are not null on the console.
thanks in advance
the results are null
The problem is, that Angular doesn't notice that the GitHub server has answered and doesn't update the view. You have to tell Angular manually to re-render the view. Try calling $scope.$apply():
github.searchRepos(params, function(data) {
console.log(data);
$scope.userData = data;
$scope.$apply();
});
If you'd make your request to the GitHub API with Angulars $http service, then this would not be needed - you'll only need $scope.$apply() if something asynchronous happens which doesnt live in the "Angular world" - for example things like setTimeout, jQuery ajax calls, and so on. That's why there are Angular wrappers like $timeout and $http.
More details: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
The GitHub API can be accessed using the AngularJS $http service:
app.controller("myVm", function($scope,$http) {
var vm = $scope;
var url = "https://api.github.com/search/repositories?q=bootstrap"
$http.get(url).then(function onSuccess(response) {
vm.data = response.data;
console.log(vm.data);
})
})
HTML
<div ng-app="myApp" ng-controller="myVm">
<div ng-repeat="item in data.items">
{{item.full_name}}
</div>
</div>
The DEMO on JSFiddle
Since you're not using the Angular $http service, angular is not aware of the changes. You need to manually tell Angular to re-render and evaluate by using
$scope.$apply();

Updating multi-model form from Angular to Sinatra

I'm currently having an issue with updating a form in Angular and pushing the update through to Sinatra.
It is supposed to:
When clicked, the form to edit the current item is shown (current data for each field is displayed from the item scope).
When submitted, it is attempting to update to a different scope (updateinfo). I am not sure but do I need a way of using multiscope or one scope to allow it to update?
At present the script sends the correct downloadID parameter, but the JSON from the scope submitted is as I believe, incorrect.
Also, I'm not sure whether the Sinatra app.rb syntax is correct, for someone new to these frameworks, it has been hard to find useful documentation online.
If anybody could help it would be very much appreciated.
downloads.html
<div ng-show="showEdit">
<form ng-submit="updateinfo(item.downloadID); showDetails = ! showDetails;">
<div class="input-group"><label name="title">Title</label><input type="text"
ng-model="item.title"
value="{{item.title}}"/></div>
<div class="input-group"><label name="caption">Download caption</label><input type="text"
ng-model="item.caption"
value="{{item.caption}}"/>
</div>
<div class="input-group"><label name="dlLink">Download link</label><input type="url"
ng-model="item.dlLink"
value="{{item.dlLink}}"/>
</div>
<div class="input-group"><label name="imgSrc">Image source</label><input type="url"
ng-model="item.imgSrc"
value="{{item.imgSrc}}"/>
</div>
<!-- download live input types need to be parsed as integers to avoid 500 internal server error -->
<div class="input-group"><label name="imgSrc">
<label name="dlLive">Download live</label><input type="radio" ng-model="download.dl_live"
value="1"/>
<label name="dlLive">Not live</label><input type="radio" ng-model="download.dl_live"
value="0"/></div>
<div class="input-group"><label name="imgSrc"><input type="submit"/></div>
</form>
controllers.js
$scope.loadData = function () {
$http.get('/view1/downloadData').success(function (data) {
$scope.items = data;
});
};
$scope.loadData();
$scope.updateinfo = function(downloadID) {
id = downloadID
var result = $scope.items.filter(function( items ) {
return items.downloadID == id;
});
console.log(result);
updatedata = $scope.items
$http({
method : 'PUT',
url : '/view1/downloadedit/:downloadID',
data : result
});
};
app.rb
#edit download
put '/view1/downloadedit' do
puts 'angular connection working'
ng_params = JSON.parse(request.body.read)
puts ng_params
#download = Download.update(ng_params)
end
The wrong scope was attempting to be used. Once the scope was corrected to items, the correct JSON was being routed:
$scope.updateinfo = function(downloadID) {
id = downloadID
var result = $scope.items.filter(function( items ) {
return items.downloadID == id;
});
console.log(result);
updatedata = $scope.items
$http({
method : 'PUT',
url : '/view1/downloadedit/:downloadID',
data : result
});

Data doesn't bind when submitting form

When I refresh the page the correct values come up. I want the list of 'gists' to automatically update when the form is submitted.
The global #todone is set that way because I receive an undefined error when I set it as 'todone'. It may be unrelated.
app.factory "To_done", ["$resource", ($resource) ->
$resource("/to_dones", {}, {update: {method: "PUT"}})
]
#MainCtrl = ["$scope", "To_done", ($scope, To_done) ->
$scope.to_dones = To_done.query()
$scope.addTodone = ->
#todone = To_done.save($scope.newTodone)
$scope.to_dones.push(#todone)
$scope.newTodone = {}
]
<div ng-controller="MainCtrl">
<form ng-submit="addTodone()">
<input type="text" ng-model="newTodone.gist">
<input type="submit" value="Add">
</form>
<ul>
<li ng-repeat="todone in to_dones">
{{todone.gist}}
</li>
</ul>
</div>
To_done.save($scope.newTodone) is asynchronous. You need to register a callback function to get the value returned by your POST.
var todone = To_done.save($scope.newTodone, function() {
//success callback - optional
$scope.to_dones.push(todone);
}, function() {
//error callback - optional
});
$scope.newTodone = {};
You can add arguments to the callback methods if you need more informations about the answer received from your service. More details here : http://docs.angularjs.org/api/ngResource.$resource

Sending POST with hidden <input> value does't work in AngularJs

In my web app, There are many form on a page. I want to submit it with AngularJS for specific form.
In each of form, it need unique ID with Hidden Value to submit. But value="UNIQUE_ID" seen doesn't work in hidden input box in AngularJS.
My HTML
<div ng-app>
<div ng-controller="SearchCtrl">
<form class="well form-search">
<input type="text" ng-model="keywords" name="qaq_id" value="UNIQUE_ID">
<pre ng-model="result">
{{result}}
</pre>
<form>
</div>
</div>
This is js script
function SearchCtrl($scope, $http) {
$scope.url = 'qa/vote_up'; // The url of our search
// The function that will be executed on button click (ng-click="search()")
$scope.search = function() {
// Create the http post request
// the data holds the keywords
// The request is a JSON request.
$http.post($scope.url, { "data" : $scope.keywords}).
success(function(data, status) {
$scope.status = status;
$scope.data = data;
$scope.result = data; // Show result from server in our <pre></pre> element
})
.
error(function(data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
}
It may be that the only reason your code is not working is that $scope.keywords is a simple variable (with a text value) instead of an Object, which is required - see http://docs.angularjs.org/api/ng.$http#Usage
As angularJS works with variables within its own scope - its models, a form becomes just a way to interact with those models, wich can be sent via whatever method you want.
You can have a hidden field, yes, but in angularJS it isn't even necessary. You only need that information to be defined in the controller itself - randomly generated for each instance, or received from some other source.. Or you can define it yourself, upon the loading of the controller, for instance.
So (and only for sake of clarity) if you define a formData variable within your formCtrl:
Your HTML:
<div ng-app>
<div ng-controller="SearchCtrl">
<form class="well form-search">
<input type="text" ng-model="formData.title">
<input type="textarea" ng-model="formData.body">
<button ng-click="sendData()">Send!</button>
</form>
<pre ng-model="result">
{{result}}
</pre>
</div>
</div>
And your controller:
function SearchCtrl($scope, $http) {
$scope.url = 'qa/vote_up'; // The url of our search
// there is a formData object for each instance of
// SearchCtrl, with an id defined randomly
// Code from http://stackoverflow.com/a/1349426/1794563
function makeid()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
$scope.formData = {
title: "",
text: ""
};
$scope.formData.id = makeid();
// The function that will be executed on button click (ng-click="sendData()")
$scope.sendData = function() {
// Create the http post request
// the data holds the keywords
// The request is a JSON request.
$http.post($scope.url, { "data" : $scope.formData}).
success(function(data, status) {
$scope.status = status;
$scope.data = data;
$scope.result = data; // Show result from server in our <pre></pre> element
})
.
error(function(data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
}
Also: If you wanted to set the unique id on the html itself, you could add an input type="hidden" and set it's ng-model attribute to formData.id, and whichever value you set it to, the model would have it binded. using a hidden input won't work, as the value attribute doesn't update the angularJS Model assigned via ng-model. Use ng-init instead, to set up the value:
HTML with 2 forms:
<div ng-controller="SearchCtrl" ng-init="formData.id='FORM1'">
<form class="well form-search">
<input type="text" ng-model="formData.title">
<input type="textarea" ng-model="formData.body">
<button ng-click="sendData()">Send!</button>
</form>
</div>
<div ng-controller="SearchCtrl" ng-init="formData.id='FORM2'">
<form class="well form-search">
<input type="text" ng-model="formData.title">
<input type="textarea" ng-model="formData.body">
<button ng-click="sendData()">Send!</button>
</form>
</div>
You can add a hidden field, but it accomplishes nothing - the ng-init attribute does everything you need.

Categories

Resources