Angular - Iterate and numerate elements without jQuery - javascript

Object has 3 properties:
public obj;
...
this.obj = {
height: 10,
width: 20,
weight: 30
};
Then I bind buttons with those properties, but I want to numerate buttons and show them only if the property value exist:
<button *ngIf="obj.height"> Button <span class="buttonNr">1</span> </button>
<button *ngIf="obj.width"> Button <span class="buttonNr">2</span> </button>
<button *ngIf="obj.weight"> Button <span class="buttonNr">3</span> </button>
However, if lets say width is missing, I would get "Button 1" and "Button 3"
and I want them to be numerated normally, no matter which properties exist or not, I want to have Button 1 then 2 then 3...
In case width doesn't exist, I want to have Button 1 for height, and Button 2 for weight.
Now I know how to do it with jQuery, to iterate and populate spans with class buttonNr counting from 1 till the end.
But I'd like to do it Angular way, without jQuery.
EDIT:
Sorry, when I saw the first answer, I realized I oversimplified my example: there are various objects.
So imagine there's also
public data;
public book;
...
this.data = {
color: 'blue',
price: 100
}
this.book = {
title: 'a',
author: 'b'
}
and then I just keep on adding buttons, but also their display depends.
<button *ngIf="data.color"> Button <span class="buttonNr">4</span> </button>
<button *ngIf="data.price"> Button <span class="buttonNr">5</span> </button>
<button *ngIf="book.title"> Button <span class="buttonNr">6</span> </button>
<button *ngIf="book.author"> Button <span class="buttonNr">7</span> </button>

If you want to be explicit about the order of the buttons, you can define a getButtons() method on the component:
getButtons() {
return [
this.data?.color,
this.data?.price,
this.book?.title,
this.book?.author
].filter(val => val !== undefined);
}
The method takes the ideal order of all the buttons, then filters out the values that don't exist.
Then you just iterate over the result in the template:
<button *ngFor="let value of getButtons(); let i = index">
Button <span class="buttonNr">{{i + 1}}</span>
</button>

Your solution could look as follows
<button *ngFor="let key of obj | keyvalue; let i = index">
Button <span class="buttonNr">1</span>
</button>
Let me explain what happens here
*ngFor is a directive that renders a template for each item in a collection
keyvalue is a special Pipe that allows iterating through object properties
let i = index - exporting iteration index into i variable
Update
According to the answer updated, I've updated my solution to the multiple objects including cumulative counter
Here you can find a simple example: https://stackblitz.com/edit/angular-ivy-qn8x38?file=src%2Fapp%2Fapp.component.html

Related

Why nested array dynamic update is not working in my case

Hi i have a problem with nested array dynamic update as shown in below image
Here is a steps to re-produce the problem
click on Add Likes button it will add likes for eg New Apple
my default like is Mango
click on Add about button it will add next row (Mango is not appeared) click on Add Likes to see the problem.
For reducing code readability i have put helper function into a mixin file called mixin.js
Expectation: i can add any number of Add about and Add Likes(with default value Mango)
Here is my code: https://codesandbox.io/s/musing-hawking-di4d3?file=/src/App.vue
First, you don't need a nested array for the tags property.
Use:
getLikesTemplate() {
let year = this.year;
let template = {
id: this.getUniqueId(),
like: `I'm ${year} Old and like things like`,
tags: [this.getTagsTemplate("Mango")] //nesting removed
};
this.year++;
return template;
}
Secondly, in JS objects are passed by reference, so you can do this:
method:
addLikes(like) { //removed the extra code
like.tags.push(this.getTagsTemplate("New Apple"));
},
template:
...
<div style="text-align: left; display: flex">
<div> //nesting removed
<div class="tags" v-for="tag in like.tags" :key="tag.id">
{{ tag.name }}
</div>
</div> //passing the reference to the object
<button style="margin-left: 20px" #click="addLikes(like)">
Add Likes
</button>
</div>
Result img

angular ng-if json array contains boolean

Angular 1 app.
Basically, there is an array of JSON object like this:
ctrl.messages =
[
{id: 1, text: 'hello1',createdBy:{name: 'Jack', hasBeenRead: false} }
,{id: 2, text: 'hello2',createdBy:{name: 'Steven', hasBeenRead: true} }
];
Now, in the view I print the messages like this:
<div ng-repeat="message in messages">
Message: {{message.text}}
</div>
Somewhere else I have this html:
<div ng-if="messages.length > 0">
<button ng-if="?">Mark all read</button>
<button ng-if="?">Mark all unread</button>
</div>
The buttons above will never be showing together. But only one of them (and only if there are messages at all).
I wonder if it possibile to add in the ng-if above (in the button) a code for understanding if the button has to be showing.
The button Mark all read will be showing only if there is at least one message marked with hasBeenRead: false.
The button Mark all unread will be showing only if all the messages have been read.
I could do this in the controller. But I thought it would be neater if I could add this directly in the view.
The difficulty for me is to access the hasBeenRead in the JSON from the view without iterating. Just asking "is there at least one unread message?".
Is there a way to do it in this way?
Create filter as below
app.filter('hasBeenRead', function () {
return function (input) {
return input.some(val => !val.createdBy.hasBeenRead);
}
});
<div ng-if="messages.length > 0">
<button ng-if="!messages|hasBeenRead">Mark all read</button>
<button ng-if="messages|hasBeenRead">Mark all unread</button>
</div>

Dynamic attribute value element locator in Protractor

When I add a new button with some value it gets dynamically added into DOM. Non-Angular HTML element for this button is:
<li class="ui-state-default droppable ui-sortable-handle" id="element_98" data-value="2519">
25.19 EUR
<button type="button" class="btn btn-default removeParent">
<span class="glyphicon glyphicon-remove" aria-hidden="true">
</span>
</button>
</li>
Once I remove this button I want to check it is not present anymore. Element that I'm searching for is data-value="2519"and this could be anything I set, like for example 2000, 1000, 500, 1050,...
In page object file I have tried to use the following:
this.newValueButtonIsNotPresent = function(item) {
newValueButton = browser.element(by.id("containerQUICK_ADD_POINTS")).all(by.css('[data-value="' + item + '"]'));
return newValueButton.not.isPresent();
};
And in spec file I call this function as follows:
var twentyEurosButtonAttributeValue = '2000';
describe("....
it ("...
expect(predefined.newValueButtonIsNotPresent(twentyEurosButtonAttributeValue)).toBeTruthy();
I know this is not correct, but how I can achieve something like that or is there another way?
Stupid me, I found a simple solution. Instead dynamically locating an element I located the first on the list, which is always the one, which was newly added and then checked if it's text does not match:
Page object file:
this.newValueButtonIsNotPresent = function() {
newValueButton = browser.element(by.id("containerQUICK_ADD_POINTS")).all(by.tagName('li')).first();
return newValueButton.getText();
};
Spec file:
// verify element 20.00 EUR is not present
predefined.newValueButtonIsNotPresent().then(function(value) {
expect(value).not.toEqual(twentyEurosText);
});

How to push a new item into an array of objects using AngularJS?

On my website are a list of user created via points. The user starts with one, and then if they wish to add another they can click “add” and it should add a new object inside of the array with an empty value and the user can input a new value. The user can continue adding until they have 5. How would I do this? Below is my code.
I want:
User can create via points which add a new item with val="" and then the user can change the value with the input.
After 5 via points no more are allowed.
Thank you for any help!
//HTML
<table>
<tr ng-repeat="item in viaPoints">
<td>
<p class="title-icon form-label">VIA LOCATON {{$index + 1}}</p>
<button style="bottom: -3px;" class="transparent position pull-right" ng-click="removeViaPoint($index)">
<img src="images/icons/close-14.png">
</button>
<input class="form" id="drop-off" type="text" value="{{x}}" ng-model="item.val">
</td>
</tr>
</table>
<button class="add" ng-click="addViaPoint()">+ ADD MORE LOCATIONS</button>
<button class="okay okay-full">OKAY</button>
//Angular
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
//Via Point Objects
$scope.viaPoints = [
{val:"Hanoi"}
]
//Push Via Points
$scope.addViaPoint = function () {
$scope.viaPoints.push("val:''");
}
//Remove Via Point
$scope.removeViaPoint = function (x) {
$scope.viaPoints.splice(x, 1);
}
});
By making
$scope.addViaPoint = function () {
$scope.viaPoints.push("val:''");
}
only makes you add a string to the array. If you want to add an object, just write instead
$scope.addViaPoint = function () {
$scope.viaPoints.push({val:''});
}
You're now adding a new object with a property val set to ''
As for not allowing more than 5 points, you could just make a logic gate on your function, like:
if ($scope.viaPoints.length === 5) return;
Or you can opt for a more user-friendly way of deactivating the button depending on that length, like:
<button class="add"
ng-click="addViaPoint()"
ng-disabled="$scope.viaPoints.length === 5">
+ ADD MORE LOCATIONS
</button>
(although you should use a more flexible approach with a function like reachedMaxViaPoints())

AngularJS nested ng-repeat click & show/hide

I've done a ton of reading and research on this topic the past few days and have found some good answers, but for some of the answers I question performance and necessity.
My question pertains to nested ng-repeat scopes. I'm wondering what the best way to achieve an "add item" scenario for adding an item to the nested foreach.
My Code
My HTML is simply 2 ng-repeats and my goal is to be able to add an item to the second (nested) ng-repeat
<div ng-app="myApp">
<div class="nav" ng-controller="FoodsController as vm">
<div class="level1" ng-repeat="foods in vm.foodGroups">{{foods.Name}}
<button type="button" ng-click="vm.addNewFood()">add new food</button>
<div ng-show="vm.newFoodBeingAdded">
<input type="text">
</div>
<div class="level2" ng-repeat="food in foods.FoodsInGroup">{{food.Name}}</div>
</div>
</div>
</div>
My Angular controller looks like this:
app.controller('FoodsController', function () {
var vm = this;
vm.foodGroups = [{
"Name": "Grains",
"FoodsInGroup": [{
"Name": "Wheat"
}, {
"Name": "Oats"
}]
}, {
"Name": "Fruits",
"FoodsInGroup": [{
"Name": "Apple"
}, {
"Name": "Orange"
}]
}];
vm.newFoodBeingAdded = false;
vm.addNewFood = function () {
vm.newFoodBeingAdded = true;
};
});
What should happen
The general work flow would be a user clicks an Add New button and it shows a text box with a "save" button. The text box & button would be within the parent foreach. Once a user saves the item it would then be added to the nested foreach (that logic isn't shown).
The issue
The issue is that when I click "Add New Food" (which should just show 1 of the text boxes & save buttons), all of the text boxes show. How do I ensure I am "scoping" this correctly and that only the text box/button within that parent are shown?
Possible solution
One answer I found was to create a child controller for each nested item. For example I'd have a FoodGroupsController which would manage all the logic for the nested foreach (because there will be a lot more going on than just adding a new item in a real app, so it could be justified).
jsFiddle
Here's a jsFiddle with the code that currently does not function correctly.
There is the forked Fiddle
I made just few changes. The fact is that you were binding the ng-show with a single var in your controller. It was a show me all or show me nothing possibility.
So the fix it, you have to bind this, in your food item, not in the controller himself.
Html :
<button type="button" ng-click="vm.addNewFood(foods)">add new food</button>
<div ng-show="foods.newFoodBeingAdded" class="add-new-food">
<input type="text" placeholer="add a new food">
<button type="button">save new food</button>
</div>
Controller :
vm.addNewFood = function (foods) {
foods.newFoodBeingAdded = true;
};
With this code, you pass the food in param of your function, so you can change the boolean of your food only. And then, your ng-show is just binding on this boolean.

Categories