Why is angular $scope not removing old value? - javascript

I have the following controller
angular.module('publicApp')
.controller('URLSummaryCtrl', function ($scope, $location, Article, $rootScope, $timeout) {
$scope._url = "";
$scope._title = "";
$scope._article = "";
$scope._authors = "";
$scope._highlights = [];
$scope._docType = "";
$scope.summarizeURL = function(){
Article.getArticleInfo($scope.url, "").then(
function(data){
$scope._url = data.url;
$scope._title = data.title;
$scope._authors = data.authors.join(', ');
$scope._highlights = data.highlights;
$scope._docType = data.documentType;
if($scope._docType == 'html'){
$scope._article = data.article[0].article;
}
else{
$scope._article = data.article;
}
var _highlights = [];
$scope._highlights.forEach(function (obj) {
_highlights.push(obj.sentence);
});
// wait for article text to render, then highlight
$timeout(function () {
$('#article').highlight(_highlights, { element: 'em', className: 'highlighted' });
}, 200);
}
);
}
and the following view
<form role="form" ng-submit="summarizeURL()">
<div class="form-group">
<input id="url" ng-model="url" class="form-control" placeholder="Enter URL" required>
</div>
<button class="btn btn-success" type="submit">Summarize</button>
</form>
<div class="col-lg-8">
<h2>{{ _title }}</h2>
<p> <b>Source: </b> {{_url}}</p>
<p> <b>Author: </b> {{_authors}} </p>
<p> <b>Article: </b><p id="article">{{_article}}</p></p>
</div>
When I give a url in the text field initially and click Summarize it works as expected. But when I change the value in the text field and click the button again every thing is updated properly, with the new values, but the $scope._article gets the new value and doesn't remove the old value. It displays both the new and the old value that was there before.
Why is this happening?
EDIT #1: I added more code that I had. I found that when I remove the $timeout(function(){...}) part it works as expected. So now the question is, why is $scope._article keeping the old value and pre-pending the new value?
EDIT #2: I found that $timeout(...) is not the problem. If I change
$timeout(function () {
$('#article').highlight(_highlights, { element: 'em', className: 'highlighted' });
}, 200);
to
$('#article').highlight(_highlights, { element: 'em', className: 'highlighted' });
it still behaves the same way. So now I'm assuming it's because I'm changing the $scope._article to be something else? What's happening is that I'm displaying the $scope._article value and then modifying what's displayed to contain highlights <em class='highlighed'> ... </em> on what ever I want to highlight.
EDIT #3: I tried to remove the added html before making the request to get new data but that doesn't work either. Here's the code I tried.
angular.module('publicApp')
.controller('URLSummaryCtrl', function ($scope, $location, Article, $rootScope, $timeout) {
$scope._url = "";
$scope._title = "";
$scope._article = "";
$scope._authors = "";
$scope._highlights = [];
$scope._docType = "";
$scope.summarizeURL = function(){
//Remove added html before making call to get new data
$('.highlighted').contents().unwrap();
Article.getArticleInfo($scope.url, "").then(
function(data){ ... }
);

Jquery in angular controllers = headache.
The problem is probably here for you
$timeout(function () {
$('#article').highlight(_highlights, { element: 'em', className: }, 200);
#article.html() here, is going to give weird output, because angular has it's own sync system and the jquery library you're using has it's own way of working with the DOM. Throw in the fact that asynchronous javascript is already a pain if you're working with multiple things.
What you want instead is to set the html to the angular scope variable before you work with it in jquery so you know what the jquery is working with, i.e.:
$timeout(function () {
$('#article').html($scope._article);
$('#article').highlight(_highlights, { element: 'em', className: }, 200);

Related

prevent reflecting ng-model value across all select tags

I am pretty new to AngularJS. I am working on a project wherein I need to append certain html select tags based on a button click. Each select tag is bound to a ng-model attribute (which is hardcoded). Now the problem I am facing is, once I append more than 2 such html templates and make changes in a select tag then value selected is reflected across all the tags bound to the corresponding ng-model attribute (which is pretty obvious). I would like to know if there is a way around it without naming each ng-model differently.
JS code:
EsConnector.directive("placeholderid", function($compile, $rootScope, queryService, chartOptions){
return {
restrict : 'A',
scope : true,
link : function($scope, element, attrs){
$scope.current_mount1 = "iscsi";
$scope.current_dedupe1 = "on";
$scope.y_axis_param1 = "Total iops";
var totalIops =[];
var totalBandwidth =[];
element.bind("click", function(){
$scope.count++;
$scope.placeholdervalue = "placeholder12"+$scope.count;
var compiledHTML = $compile('<span class="static" id='+$scope.placeholdervalue+'>choose mount type<select ng-bind="current_mount1" ng-options="o as o for o in mount_type"></select>choose dedupe<select ng-model="current_dedupe1" ng-options="o as o for o in dedupe"></select>choose y axis param<select ng-model="y_axis_param1" ng-options="o as o for o in y_axis_param_options"></select></span><div id='+$scope.count+' style=width:1400px;height:300px></div>')($scope);
$("#space-for-buttons").append(compiledHTML);
$scope.$apply();
$(".static").children().each(function() {
$(this).on("change", function(){
var id = $(this).closest("span").attr("id");
var chartId = id.slice(-1);
queryService.testing($scope.current_mount1, $scope.current_dedupe1, function(response){
var watever = response.hits.hits;
dataToBePlot = chartOptions.calcParams(watever, totalIops, totalBandwidth, $scope.y_axis_param1);
chartOptions.creatingGraph(dataToBePlot, $scope.y_axis_param1, chartId);
});
});
});
});
}
}
});
Code explanation:
This is just the directive which I am posting.I am appending my compiledHTML and doing $scope.apply to set the select tags to their default values. Whenever any of the select tags are changed I am doing a set of operations (function calls to services) on the values selected.
As you can see the ng-model attribute being attached is the same. So when one select tag is changed the value is reflected on all the appended HTML even though the data displayed does not match to it.
Hope this PLunker is useful for you. You need to have one way binding over such attributes
<p>Hello {{name}}!</p>
<input ng-model="name"/>
<br>Single way binding: {{::name}}
Let me know if I misunderstood your question
It is a bit hard to understand your whole requirement from your description and your code, correct me if I'm wrong: you are trying to dynamically add a dropdown on a button click and then trying to keep track on each of them.
If you are giving the same ng-model for each generated items, then they are bound to the same object, and their behavior is synchronized, that is how angular works.
What you can do is, change your structure to an array, and then assigning ng-model to the elements, so you can conveniently keep track on each of them. I understand you came from jquery base on your code, so let me show you the angular way of doing things.
angular.module('test', []).controller('Test', Test);
function Test($scope) {
$scope.itemArray = [
{ id: 1, selected: "op1" },
{ id: 2, selected: "op2" }
];
$scope.optionList = [
{ name: "Option 1", value: "op1" },
{ name: "Option 2", value: "op2" },
{ name: "Option 3", value: "op3" }
]
$scope.addItem = function() {
var newItem = { id: $scope.itemArray.length + 1, selected: "" };
$scope.itemArray.push(newItem);
}
$scope.changeItem = function(item) {
alert("changed item " + item.id + " to " + item.selected);
}
}
select {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app='test' ng-controller='Test'>
<button type='button' ng-click='addItem()'>Add</button>
<select ng-repeat='item in itemArray'
ng-options='option.value as option.name for option in optionList'
ng-model='item.selected'
ng-change='changeItem(item)'></select>
</div>

How to pass focus to a new field as soon as it appears (angular 1.4)?

In the following example a new field is added (by adding a blank row to $scope) when the last field loses focus if it is not empty. The problem is that the new field is not added to the DOM in time to receive focus.
Is there a way to detect when angular has finished appending new field to the DOM and then pass focus to it?
Please, no "timer" solutions; the time it takes to change DOM is unknown and I need this focus switch to happen as fast as possible. We can do better!
JSFiddle
HTML
<div ng-app='a' ng-controller='b'>
<input type="text" ng-repeat="row in rows" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}">
</div>
JS
angular.module('a', []).controller('b', function ($scope) {
$scope.rows = [{'word': ''}];
$scope.$watch('rows', function (n, o) {
var last = $scope.rows[$scope.rows.length - 1];
last.word && $scope.rows.push({'word': ''});
}, true);
});
This is a View-concern and so should be dealt with by using directives.
One way to do so, is to create a directive that grabs the focus when it's linked:
.directive("focus", function(){
return {
link: function(scope, element){
element[0].focus();
}
}
});
and use it like so:
<input type="text"
ng-repeat="row in rows"
ng-model="row.word"
focus>
Demo
Use $timeout without specifying a number of milliseconds. It will, by default, run after the DOM loads, as mentioned in the answer to this question.
angular.module('a', []).controller('b', function($scope, $timeout) {
$scope.rows = [{
'word': ''
}];
$scope.addRow = function() {
$scope.rows.push({
'word': ''
});
$timeout(function() {
//DOM has finished rendering
var inputs = document.querySelectorAll('input[type="text"]');
inputs[inputs.length - 1].focus();
});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='a' ng-controller='b'>
<div ng-repeat="row in rows">
<input type="text" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}"><br>
</div>
<input type="button" ng-click="addRow()" value="Add Row">
</div>

Losing Controller Scope in ng-repeat

Well . . . a cursory look at my code should save me from having to explain that I'm brand new to Angular, I'm sure.
I'm building an app that allows search text from a user, queries a database when the value of the text input changes, then produces a list of matches. The back end is simple and is working. On the front, I've got the search field and the results container:
<div id="search" ng-controller="FormController">
<input type="text" class="form-control input-lg" placeholder="Start typing . . ." ng-keypress="search()" ng-model="searchField" id="search-field">
<div id="results" class="alert alert-success">
<a href="#" ng-href="#" ng-repeat="item in items" ng-click="add(item)">
<p class="lead text-left">
<span>{{item.DisplayName}} -</span>
<span> {{item.Description}} -</span>
<span> {{item.Count}} ct. -</span>
<span> ${{item.Price}}</span>
<span class="glyphicon glyphicon-check pull-right"></span>
</p>
</a>
<h4>{{noResults}}</h4>
</div>
</div>
The two methods being called in my controller:
$scope.search = function()
{
$scope.$watch('searchField', function (newValue)
{
if (newValue)
{
$http.post('/search',
{
val: newValue
})
.success(function (response)
{
if (response.length > 0)
{
$scope.items = response;
$scope.noResults = '';
}
else
{
$scope.noResults = 'No Matches Found';
$scope.items = '';
}
})
.error(function (response)
{
console.log('Oooops, error: ' + response);
});
}
});
};
$scope.add = function(item)
{
console.log('added');
};
$scope.search(), while probably a little messy, is working. But the add() method is not called on click. I'm guessing I'm simply not in the scope of the controller at that point, but after a LOT of searching around, I turn to you, stumped. I'm at the "banging-your-head-against-the-keyboard-and-hoping-for-it-to-magically-work" stage.
Is this an inheritance issue?
** Update **
Here is the entire controller (with the $watch removed as suggested in the comments):
var app = angular.module('AppModule', ['toastr']);
app.controller('FormController', ['$scope', '$http', 'toastr', function($scope, $http, toastr)
{
$scope.search = function()
{
var searchText = $scope.searchField;
$http.post('/search',
{
val: $scope.searchField
})
.success(function (response)
{
if (response.length > 0)
{
$scope.items = response;
$scope.noResults = '';
}
else
{
$scope.noResults = 'No Matches Found';
$scope.items = '';
}
})
.error(function (response)
{
console.log('Oooops, error: ' + response);
});
};
$scope.add = function(item)
{
console.log('added');
};
}]);
Update 2
Here is a plunker showing that everything is working up til the add() method (I may have renamed that method in this version). Of course, in place of my $http post, I've hard-coded a fake of the response that comes back from the server.
CSS issue. Comment out line, ugh, 8382 of your CSS (setting #results display to none). It'll work then. How you eventually resolve this in your CSS is a different issue.
Went through your plunker and i must say that the issue found is kinda silly.
At first, i put the code for removing the class has-value from resultsContainer in a timeout. That made the call for addItem working.
setTimeout(function() {
resultsContainer.removeClass('has-value');
}, 1000);
This may act as a solution, but setTimeout ?? Not happening.
Digging a little deep revealed that you are using display:none for #results. So i removed the display css and used opacity instead.
#results {
position: absolute;
/*display: none; */
opacity : 0;
top: 100%;
left: 0;
width: 100%;
}
#results.has-value {
display: block;
opacity : 1;
}
This got it working without the timeout. **Now i have myself faced issues where display:none screws the functionalities. So you either tweak your css or use a timeout instead. **
Also, consider moving that code in a directive.
Updated plunker here

watchcollection swallowing an attribute being watched

There are two attributes selCountry and searchText. There is a watch that monitors these two variables. The 1st one is bound to a select element, other is a input text field.
The behavior I expect is: If I change the dropdown value, textbox should clear out, and vice versa. However, due to the way I have written the watch, the first ever key press (post interacting with select element) swallows the keypress.
There must be some angular way of telling angular not to process the variable changes happening to those variables; yet still allow their changes to propagate to the view...?
$scope.$watchCollection('[selCountry, searchText]', function(newValues, oldValues, scope){
console.log(newValues, oldValues, scope.selCountry, scope.searchText);
var newVal;
if(newValues[0] !== oldValues[0]) {
console.log('1');
newVal = newValues[0];
scope.searchText = '';
}
else if(newValues[1] !== oldValues[1]) {
console.log('2');
newVal = newValues[1];
scope.selCountry = '';
}
$scope.search = newVal;
var count = 0;
if(records)
records.forEach(function(o){
if(o.Country.toLowerCase().indexOf(newVal.toLowerCase())) count++;
});
$scope.matches = count;
});
Plunk
I think the problem you are encountering is that you capture a watch event correctly, but when you change the value of the second variable, it is also captured by the watchCollection handler and clears out that value as well. For instance:
selCountry = 'Mexico'
You then change
selText = 'City'
The code captures the selText change as you'd expect. It continues to clear out selCountry. But since you change the value of selCountry on the scope object, doing that also invokes watchCollection which then says "okay I need to now clear out searchText".
You should be able to fix this by capturing changes using onChange event handlers using ng-change directive. Try the following
// Comment out/remove current watchCollection handler.
// Add the following in JS file
$scope.searchTextChange = function(){
$scope.selCountry = '';
$scope.search = $scope.searchText;
search($scope.search);
};
$scope.selectCountryChange = function(){
$scope.searchText = '';
$scope.search = $scope.selCountry;
search($scope.search);
};
function search(value){
var count = 0;
if(records)
records.forEach(function(o){
if(o.Country.toLowerCase().indexOf(value.toLowerCase())) count++;
});
$scope.matches = count;
}
And in your HTML file
<!-- Add ng-change to each element as I have below -->
<select ng-options="country for country in countries" ng-model="selCountry" ng-change="selectCountryChange()">
<option value="">--select--</option>
</select>
<input type="text" ng-model="searchText" ng-change="searchTextChange()"/>
New plunker: http://plnkr.co/edit/xCWxSM3RxsfZiQBY76L6?p=preview
I think you are pushing it too hard, so to speak. You'd do just fine with less complexity and watches.
I'd suggest you utilize some 3rd party library such as lodash the make array/object manipulation easier. Try this plunker http://plnkr.co/edit/YcYh8M, I think it does what you are looking for.
It'll clear the search text every time country item is selected but also filters the options automatically to match the search text when something is typed in.
HTML template
<div ng-controller="MainCtrl">
<select ng-options="country for country in countries"
ng-model="selected"
ng-change="search = null; searched();">
<option value="">--select--</option>
</select>
<input type="text"
placeholder="search here"
ng-model="search"
ng-change="selected = null; searched();">
<br>
<p>
searched: {{ search || 'null' }},
matches : {{ search ? countries.length : 'null' }}
</p>
</div>
JavaScript
angular.module('myapp',[])
.controller('MainCtrl', function($scope, $http) {
$http.get('http://www.w3schools.com/angular/customers.php').then(function(response){
$scope.allCountries = _.uniq(_.pluck(_.sortBy(response.data.records, 'Country'), 'Country'));
$scope.countries = $scope.allCountries;
});
$scope.searched = function() {
$scope.countries = $scope.allCountries;
if ($scope.search) {
var result = _.filter($scope.countries, function(country) {
return country.toLowerCase().indexOf($scope.search.toLowerCase()) != -1;
});
$scope.countries = result;
}
};
});

Empty Kendo-Editor from angular controller

I am using kendo ui richtextbox on one of my pages:
<textarea kendo-editor rows="3" cols="4" ng-model="fooData.Answer" id="answerInput" placeholder="blablablabla" class="form-control" k-encoded="false"></textarea>
In my controller I have:
function ($scope, $sce, Foo) {
$scope.foos = [];
$scope.fooData = kendo.observable();
$scope.addFoo = function (fooData) {
Foo.create(fooData)
.success(function (data) {
$scope.fooData = kendo.observable();
$scope.foos.push(data);
});
}
Which adds an item foo to a collection using an api, which all works like a charm. The thing that does not work is that the kendo-editor does not get emptied. The input remains. In the documentation they stated that you should use kendo.observables, which I do, but to no avail.
Any ideas?
Apparetnly I just needed to do
$scope.fooData.Answer = "";
$scope.fooData.Question = "";
in my addFoo function.

Categories

Resources