Angular one-time binding on object keys? One-time add like button - angularjs

Goal: Like button only adds once using Angular.
Theory: Angular's one-time binding will do the trick.
It works here on Angular Expression Doc and on the Plunker example
Problem: Doesn't work in my trials.
Here's the Controller info (from Codecademy's Book App exercise)
$scope.products = [
{
name: 'The Book of Trees',
price: 19,
pubdate: new Date('2014', '03', '08'),
cover: 'img/the-book-of-trees.jpg',
likes: 0,
dislikes: 0
},
{
name: 'Program or be Programmed',
price: 8,
pubdate: new Date('2013', '08', '01'),
cover: 'img/program-or-be-programmed.jpg',
likes: 0,
dislikes: 0
}
View Trial 1
<div ng-repeat="product in products" class="col-md-6">
<div class="thumbnail">
<img ng-src="{{ product.cover }}">
<p class="title">{{ product.name }}</p>
<p class="price">{{ product.price | currency }}</p>
<p class="date">{{ product.pubdate | date }}</p>
<div class="rating">
<p class="likes" ng-click="plusOne($index)">+ {{ ::product.likes}} </p> <!-- here's the damn pickle of a problem -->
<p class="dislikes" ng-click="minusOne($index)">+ {{ product.dislikes }} </p>
</div>
</div>
</div>
RESULT - The button doesn't add
View Trial 2
<div ng-repeat="product in products" class="col-md-6">
<div class="thumbnail">
<img ng-src="{{ product.cover }}">
<p class="title">{{ product.name }}</p>
<p class="price">{{ product.price | currency }}</p>
<p class="date">{{ product.pubdate | date }}</p>
<div class="rating">
<p class="likes" ng-click="plusOne($index)">+ {{ ::product.::likes}} </p> <!-- here's the damn pickle of a problem -->
<p class="dislikes" ng-click="minusOne($index)">+ {{ product.dislikes }} </p>
</div>
</div>
</div>
RESULT - No content shows, only Handlebars / Mustaches / Handlebar-Mustaches {{ }}

I tried implementing your code. The following works for me:
<div ng-repeat = 'product in products'>
<p ng-click="plusOne($index)"> + {{ product.likes}}</p>
<p ng-click="minusOne($index)"> + {{ product.dislikes }}</p>
</div>
Since products is an array, we need to iterate through each of its elements.
$scope.products = [
{
name: 'The Book of Trees',
price: 19,
pubdate: new Date('2014', '03', '08'),
cover: 'img/the-book-of-trees.jpg',
likes: 0,
dislikes: 0
},
{
name: 'Program or be Programmed',
price: 8,
pubdate: new Date('2013', '08', '01'),
cover: 'img/program-or-be-programmed.jpg',
likes: 0,
dislikes: 0
}];
$scope.plusOne = function(index){
if($scope.products[index].likes == 0){
$scope.products[index].likes++;
}
};
I am incrementing the value of likes only if it is 0. Hence it will get incremented only once.

Here's the quick error I see on your code. 1. remove the double colon (::) and change your + sign to -. See screenshot below:
https://i.gyazo.com/ae9d8bb8be57a7b9e9cf840d3f1d705b.png
Also, don't forget to attach your plus and minus properties on your scope(in your controller).
$scope.plusOne = function(index) {
$scope.products[index].likes += 1;
};
$scope.minusOne = function(index) {
$scope.products[index].dislikes += 1;
};

If I understand you correctly, you want the user to only be able to "like" or "dislike" a product once.
One-time binding is used to have the view only watch a value until it is defined, and then stop watching it. The main purpose it to not add extra watches when you are binding data that won't be changing to your view.
In the case of ng-click, the expression is not watched using a $watch, but rather, re-evaluated every time the button is clicked. Even if it worked with one-time and the expression was only evaluated once, that expression would still be used every time the button clicked.
You need to be tracking whether the user has already "liked" a product or not. You can then check that in your plusOne and minusOne functions, and simply short-circuit them if they've already been fired.
Alternately, you could simply have plusOne and minusOne simply replace themselves with empty functions after they fire, like so:
$scope.plusOne = function(index) {
// Do stuff...
// This function disables itself after it fires once
$scope.plusOne = function() {};
};

Related

Angular filter results when user clicks on repeat

I am trying to create an Angular filter that allows users to click on a record and have the filter then show only that one record within the same repeater.
For example, when a person searches for last name "knight," they see some results. Then, when the user clicks on the specific record they want, they should see only that one record displaying in the repeater.
My html is this:
<md-list-item class="md-3-line" ng-repeat="value in ctrl.results" ng-click="ctrl.showRecord(value)">
<div class="md-list-item-text" layout="column">
<h4>Employee Id: <b>{{ value.employeeId }}</b></h4>
<h4>NetId: <b>{{ value.netId }}</b></h4>
<p>Name: {{ value.preferredFirstName }} {{ value.preferredLastName }}</p>
</div>
</md-list-item>
and it passes the selected record to my controller in this function:
vm.showRecord = function (selectedRecord, ev) {
//need my filter here
};
I have looked over a number of the examples on filters, but I don't quite see how to make the filter update the very same repeat that the user clicked on.
** Edit to show answer based on Tom Chen's work **
For anyone doing this with controller as syntax, here is the answer.
html:
<md-list-item class="md-3-line" ng-repeat="value in ctrl.results | filter:ctrl.selectedId" ng-click="ctrl.showRecord(value.employeeId)">
<div class="md-list-item-text" layout="column">
<h4>Employee Id: <b>{{ value.employeeId }}</b></h4>
<h4>NetId: <b>{{ value.netId }}</b></h4>
<p>Name: {{ value.preferredFirstName }} {{ value.preferredLastName }}</p>
</div>
</md-list-item>
controller:
vm.showRecord = function (selectedRecord) {
vm.selectedId = selectedRecord;
};
You can achieve this simply by using angular filter expression like this:
<md-list-item class="md-3-line" ng-repeat="value in ctrl.results | filter:vm.selectedId" ng-click="ctrl.showRecord(value.employeeId)">
<div class="md-list-item-text" layout="column">
<h4>Employee Id: <b>{{ value.employeeId }}</b></h4>
<h4>NetId: <b>{{ value.netId }}</b></h4>
<p>Name: {{ value.preferredFirstName }} {{ value.preferredLastName }}</p>
</div>
</md-list-item>
and in your controller :
vm.showRecord = function (id) {
vm.selectedId = id;
};
UPDATED ANSWER WITH WORKING EXAMPLE
Here is an example in Plunker
You could do it with an ng-if
<md-list-item ng-if="!ctrl.selectedRecord || ctrl.selectedRecord === value" ng-repeat="value in ctrl.results" ng-click="ctrl.showRecord(value)">
...
</md-list-item>
JS
vm.showRecord = function (selectedRecord, ev) {
//if the record is already selected toggel it off
if(vm.selectedRecord === selectedRecord) {
vm.selectedRecord = undefined;
} else {
vm.selectedRecord = selectedRecord
}
};

AngularJS filtering in multiple arrays

I'm trying to apply filter on a list of objects but I can't manage to make it work. I've read that AngularJS does not provide "out of box" multiples objects filtering, may be that's why it's not working?
Here is my code:
<div class="list list-inset">
<label class="item item-input" id="events-search">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Rechercher parmis les evenements" ng-model="nsearch">
</label>
</div>
<div class="list">
<a class="item item-thumbnail-left" href="#" ng-repeat="info in infos | filter:nsearch">
<img src="xxx">
<h2>{{ info.name }}</h2>
<p>{{ info.date}} à {{ info.hour }}</p>
</a>
</div>
For example, "infos" value would be something like:
Q5PAvIQ2x8TLNkZmhTr59s984ALI5s10 {
name: "This is an event",
...
},
jj8oB6WemYVsGZ1FSm31DFBtSlM0pfZK {
name: "This is a second event",
...
}
I'm trying to filter by name.
Does anyone have an idea?... thanks!
If your data is in a hash table rather than an actual array then you need to use the key, value notation for accessing the data in a ng-repeat:
<a class="item item-thumbnail-left" href="#" ng-repeat="(key, value) in searchedInfos()">
<h2>{{ value.name }}</h2>
</a>
In such arrangement angular's filter can not be simply applied to a non array set, so you have a create a custom filtering function on the your scope which would take it's value from a an input:
<input type="text" ng-model="view.searchStr"/>
and on the scope:
$scope.infos = {
Q5PAvIQ2x8TLNkZmhTr59s984ALI5s10: {
name: "This is an event"
},
jj8oB6WemYVsGZ1FSm31DFBtSlM0pfZK: {
name: "This is a second event"
}
};
$scope.view = {
searchStr : ""
}
$scope.searchedInfos = function(){
var searched = {};
if ($scope.view.searchStr) {
angular.forEach($scope.infos, function(value, key){
if (value.name.toLowerCase().indexOf($scope.view.searchStr.toLowerCase()) != -1) {
searched[key] = value;
}
});
}
else {
searched = $scope.infos;
}
return searched;
}
Here is a working fiddle
if infos will be an array of objects and you want to filter by the name not all the object variables in the system so all you need to do is to convert the search variable to be an object and but in it the same variable name that you want to search with
in your code you only need to convert the ng-model of the input to be like this
<input type="text" placeholder="Rechercher parmis les evenements" ng-model="nsearch.name">
and complete the rest of the code as it is
As the name is variable and unusable in a repeat you could reference by index
<h2>{{ info[0].name }}</h2>
<p>{{ info[0].date}} à {{ info[0].hour }}</p>
In your 'something like this' json array you probably need a comma
[Q5PAvIQ2x8TLNkZmhTr59s984ALI5s10 , {
name: "This is an event",
...},
jj8oB6WemYVsGZ1FSm31DFBtSlM0pfZK , {
name: "This is a second event",
...},
...]
, each info element will only has 1 element and can be referenced with [0]

ng-model into ui-gmap-marker

I need help to use ng-model directive with ui-gmap-marker. My example app.js is:
// DevicesController
$scope.devices = {
id: 1,
center: { latitude: X, longitude Y },
options: {
show: true,
name: 'device 1',
radius: 100
}
(...)
}
My index.html is:
<ul ng-controller="DevicesController">
<li ng-repeat="d in devices">
<input type="checkbox" ng-model="d.options.show">
<span>{{ d.options.name }}</span>
</li>
</ul>
(...)
<div id="map_canvas" ng-controller="DevicesController">
<ui-gmap-marker
ng-repeat="d in devicesMarkers track by d.id"
idkey="d.id"
coords="d.center"
ng-model="d.options.show">
</ui-gmap-marker>
(...)
How can I use ng-model? Doesn't work because I'm using the same controller e two different places? I want that the user be able to click in input checkbox and the marker appear/disappear.
I'd suggest simply wrap both the div in same controller rather than providing a separate controller to them.
Markup
<div ng-controller="DevicesController">
<ul>
<li ng-repeat="d in devices">
<input type="checkbox" ng-model="d.options.show">
<span>{{ d.options.name }}</span>
</li>
</ul>
(...)
<div id="map_canvas">
<ui-gmap-marker
ng-repeat="d in devicesMarkers track by d.id"
idkey="d.id"
coords="d.center"
ng-model="d.options.show">
</ui-gmap-marker>
</div>
(...)
</div>
Else maintain the data in share able service that will provide the data to both controller and will make sure, data should be updated in both places.

Ng-repeat in Angular bind to HTML template in object {{ key.value }}

I've got an object in a service I'm trying to represent in the page markup. Notably a description key in the object that stores the local address of an HTML file with the relevant description.
Here is the object I'm referencing:
service.content = [
{
id: 'bootstrap-grid',
title: 'The Complete Basics of the Bootstrap 3 Grid',
type: 'article',
date: 'February 28, 2015',
description: 'articles/partial/BootstrapGrid/description.html',
pathToHTML: 'articles/partial/BootstrapGrid/BootstrapGrid.html',
ratingArr: [],
updated: null,
},
{
id: 'HTMLConverter',
title: 'Blog-friendly HTML Converter',
type: 'resource',
date: 'March 6, 2015',
description: 'components/HTMLconverter/description.html',
pathToHTML: 'components/HTMLconverter/friendlyHTML.html',
ratingArr: [],
updated: null,
}
];
Here is the markup I'm trying to work with. See the comment to pinpoint problem.
<div class="global-container" ng-controller="HomeCtrl">
<h1>New Content</h1>
<span ng-repeat="post in posts">
<h1>
{{ post.title }}
</h1>
<div>
{{ post.date }}
</div>
<div class="post-title">
<a ui-sref="post({ id: post.id })">
{{ post.title }}
</a>
</div>
<!-- How can I get this to display the contents of the local html file address at this location? -->
{{ post.description }}
<a ui-sref="post({ id: post.id })">
Read Article
</a>
</span>
</div>
I tried ng-include but to no avail.
<ng-include src={{ post.description }}></ng-include>
I also tried creating a directive, but template URL doesn't include the current the lexical scope. What are my options here?
Try
<ng-include src="post.description"></ng-include>

How to iterate over inner object's properties in an AngularJS template?

I'm in the process of learning AngularJS. I would like to print out a list of objects and iterate over one of the object's inner object's properties. This looked like a standard procedure of using nested loops, however, it doesn't appear to be so simple.
My Controller is setup below. Essentially, it is a list of random vehicles.
var vehicleApp = angular.module("vehicleApp", []);
vehicleApp.controller('VehicleController', function ($scope) {
$scope.vehicles = [{
id: 0,
name: "car",
parts: {
wheels: 4,
doors: 4
}
}, {
id: 1,
name: "plane",
parts: {
wings: 2,
doors: 2
}
}, {
id: 2,
name: "boat",
parts: {
doors: 1
}
}];
});
I'd like to output the vehicles as such:
car
- wheels (4)
- doors (2)
plane
- wings (2)
- doors (2)
boat
- doors (1)
My template that I used was setup as such:
<div ng-app="vehicleApp" ng-controller="VehicleController">
<p ng-repeat="vehicle in vehicles">
{{ vehicle.name }}
</p>
<ul>
<li ng-repeat="(attribute, value) in vehicle.parts">
{{attribute}} ({{value}})
</li>
</ul>
</div>
This produces a list of the vehicles, but not the sub lists of the parts inner object.
Interestingly, enough, when I use {{ vehicle.parts }} it returns a JSON string of the parts inner object. Does AngularJS treat it as a string and hence, it is unable to print out the properties of the parts object?
You didn't enclose the second ngRepeat in the first one:
<div ng-app="vehicleApp" ng-controller="VehicleController">
<p ng-repeat="vehicle in vehicles">
{{ vehicle.name }}
<ul>
<li ng-repeat="(attribute, value) in vehicle.parts">
{{attribute}} ({{value}})
</li>
</ul>
</p>
</div>

Resources