Conditionally disable button in ngFor in Angular 7 - javascript

I am trying to disable buttons that the logged in user have voted on, however, when I use the disabled directive inside my ngFor (in the example below) all of the buttons are disabled, not just the items that include the logged in user. My goal is to check to see if an array contains the current user's uid, if so disable the button. How can I achieve this? Thanks in advance
compontent.ts:
this.itemsCollection = this.afs.collection('food', ref => ref.orderBy('voteCount', 'desc'));
this.items = this.itemsCollection.snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
this.afAuth.authState.subscribe(user => {
if(user) {
this.user = user.uid;
if (data.votedBy.includes(user.uid)){
console.log(id);
console.log('you already voted');
this.disabledButton = true;
return false;
}
else {
this.disabledButton = false;
}
}
});
return { id, ...data };
}))
);
html:
<div class="firestoreLoop" *ngFor="let item of items | async" >
<div class="container1">
<div class="card">
<div class="card-body">
<p>{{item.voteCount}}</p>
<p>{{item.id}}</p>
<p>{{item.votedBy}}</p>
<p>{{user}}</p>
</div>
<button type="button" id="item.id" class="btn"(click)="upvote(item.id)" [disabled]="disabledButton">Upvote</button>
</div>
</div>
</div>
Edit: I got the desired result by adding *ngIf= "user != item.votedBy" to the button. Thanks for the help gentleman.

best way for you is to add disabledButton as a property of item in items array,
and then the [disabled] on the button should be [disabled]="item.disabledButton".
And then you can control which item should be disabled by simply identifying the user and setting its corresponding item's disabledButton property to true otherwise false

Related

Preselect checkbox if id is present in array angular

I have this checkbox
<div *ngFor="let user of userList">
{{ userList.Name }}
<input type="checkbox" (change)="selectUser($event,user)">
</div>
selectUser(event,user)
{
if (event.target.checked) {
const userDetails = {
userID: user.UserID,
Name:Name ,
};
this.tempLists.push(userDetails);
}
else
{
this.tempLists= this.tempLists.filter(function(obj) {
return obj.userID !== user.UserID;
});
}
}
in this method I'm adding the selected user to the array, but i want that if id is present in the array then when reloading the content where checkbox is present, the checkbox should be selected if that particular id is present in the array.
You need to merge the userList and the tempLists in order to put a property checked on userList and then just add [checked]="user.checked"
The merge part:
userList.map(u =>{
const potentialTempLists = tempLists.find(t => t.UserID === u.UserID)
return potentialTempLists ? {...u, checked: true} : u
});
You can create a function that will check if user is there in the temp list and based on that you can mark checkbox as checked so in you ts file
isChecked(user){
return this.tempLists.some(u=> u.userID==user.UserID);
}
Then on UI user this function to see if user is checked or not
<input type="checkbox" [checked]="isChecked(user)" (change)="selectUser($event,user)">
Working demo

How to populate data from first array on randomly selected card?

how to populate data from first array on randomly selected card?
I want to make a random card in Angular, where every time I select the first card to click it will take the first value from the array and the other cards will be filled automatically with other array values. and click can only be done once. thank you.
here my code sandbox
here my angular component:
export class AppComponent {
arrayData: any = \["one", "two", "three"\];
value: any = "";
clicker() {
this.value = this.arrayData\[0\];
}
}
and this for html:
<div class="card" \*ngFor="let v of arrayData" (click)="clicker()"\>
<label\>{{value}}\</label\>
<p>
How to get arrayData[0] on card selected and filled in other card with
arrayData[1] and arrayData[2] then disable click
</p>
i hope any one can help me to solve this problem!!! thanks you.
You are looping the array in HTML, but in the loop, you are outputting one value from this.value three times.
Just loop the array, and use its elements to display, but only after a card was clicked. (The json pipe helps if the elements are objects...)
<div class="card" *ngFor="let v of arrayData" (click)="clicker()">
<label *ngIf="clicked">{{v | json}}</label>
</div>
<p>
How to get arrayData[0] on card selected and filled in other card with
arrayData[1] and arrayData[2] then disable click
</p>
and in TS:
clicked = false;
clicker() {
this.clicked = true;
}
You show as many cards as the length of array - ok. On each card you show value of the variable value - the same variable.
<div class="card" *ngFor="let v of arrayData" (click)="clicker()">
<label>{{value}}</label>
</div>
You have no association of card with values.
It would be helpful to add:
(You can also do better - programmatically create as many array objects as arrayData length)
cards: any = [
{ id: 1, value: null },
{ id: 2, value: null },
{ id: 3, value: null }
];
Now for each card you have the option to assign a value.
In template you can display:
<div
class="card"
*ngFor="let card of cards; let i = index"
(click)="onCardClick(i)"
>
<label>{{ card.value }}</label>
</div>
{{ card.value }} - value of unique card.
Pay attention to the *ngFor="let card of cards; let i = index" - we have an (incremented) index here - information about which card was clicked.
In onCardClick method we check whether the user has already performed such an operation.
onCardClick(index: number) {
if (!this.shuffled) {
this.clicker(index);
}
}
In else you can show the user an alert or whatever to let them know they've already done it.
If not, call the method clicker.
clicker(index: number) {
let arrayDataIndex = 1;
this.cards.forEach((card: any, idx: number) => {
if (index === idx) {
card.value = this.arrayData[0];
} else {
card.value = this.arrayData[arrayDataIndex];
arrayDataIndex++;
}
});
this.shuffled = true;
}
In foreach loop through the next elements of cards. If element has the same index as clicked card, assign first value of arrayData. Otherwise assign arrayData[1], arrayData[2]...
At least, this.shuffled = true, that the operation cannot be performed again.
I hope I understood what you wanted to accomplish :)
See working code
https://codesandbox.io/s/loving-wind-gku7vl

Pre-filled-out select in Svelte

I am currently working on a form where you can enter diverse data (text, img's and also choose pdf's).
With the latter I am having some troubles. When I want to edit a form, the Selectfield (where the pdf is safed) is empty.
First of, when I open the Modal to edit the entry, I get my data like this:
onMount(async () => {
const data = await api.get('/documents');
console.log({ data });
documents = data.map((doc) => ({ value: doc.id, label: doc.name }));
if (isEdit) {
selectedDocument = data.find((doc) => doc.value === formInput.projectId);
}
});
I want to display the chosen PDF in a SelectInput field:
<div class="sm:col-span-3">
<SelectInput
id="files"
items={documents}
name="files"
label="Dateien"
bind:selectedItem={selectedDocument}
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
/>
</div>
The component SelectInput is built like this:
<script>
import Select from 'svelte-select';
export let items = [];
export let selectedItem = undefined;
export let id;
export let name;
export let label;
function handleSelect(event) {
selectedItem = event.detail;
console.log('selectedItem', selectedItem);
}
function handleClear() {
selectedItem = undefined;
}
</script>
<div class={`themed ${$$props.class}`}>
<label for={id} class="block text-base font-medium text-gray-700">{label}</label>
<Select {id} {items} {name} on:select={handleSelect} on:clear={handleClear} />
</div>
TLDR: The Select-value is not preselected like the other text-areas. Basically I want to edit a dB entry. So when I click on "edit" I want to see all the changable values (title, uppertext, lowertext, selectedItem). So far I am only able to see the textareas but not the selectable. When I open the edit, it is empty and I dont know why.
Because I'm not familiar with the svelte-select package, I can't be sure as to why it is not reacting to the changes. But here's what I would try to debug this:
Check if there are any errors printed in the console on the server or browser?
Use bind:items for <SelectInput>
Use bind:items for <Select>
Finally, if none of those work, you can use a key block. This will force it to re-render when the key changes.
{#key items}
<SelectInput
/>
{/key}

How to Keep active state of an item inside of component that iterates over some dataset

I have array with objects (posts - strings), and I need to have an opportunity to edit every of them, but In my case when I press on button, it starts to edit all of them:
const [editMode, setEditMode] = useState<any>(false);
let activeStatusChange = () => {
setEditMode(true);
};
<div>
{posts?.map((el) => (
<div key={Number(el.id)}>
{
editMode === true
? <p>{<input />}</p>
: <div>{el.body}
<button onClick={activeStatusChange}>edit</button>
</div>
}
</div>
))}
you are lopping on every posts element of your component and applying your handler activeStatusChange on every one of them and thats runes every thing! :)
you need to keep track of active post to activate/deactivate it!
you might have an id for your data (posts) or if you don't you can use the id of your loop to keep track of your active element, also for doing that you need to use id instead of true/false value:
const [editableItemId, setEditableItemId] = useState<number | null>(null);
let activeStatusChange = (targetId: number) : void => {
setEditMode(Number(targetId));
};
<div>
{posts?.map((el) => (
<div key={el.id}>
{
editableItemId === el.id
? <p><input /></p>
: <div>{el.body}
<button onClick={() => activeStatusChange(el.id)}>edit</button>
</div>
}
</div>
))}

How to iterate through an array of objects all residing on index 0

I am making a simple quiz app for my assignment using firebase. When I retrieve my questions from firebase I get an array which contain 1 object which contains 2 objects of its own.
Like this:
I want to render them on virtual dom separately like a quiz. Is there a way to iterate through them like using Questions.Q1.Question but when user presses the next button it will switch to Questions.Q2.Question dynamically for all questions.
I am getting questions like this:
getQuestions() {
const firebaseRef = firebase.database().ref("Quizes").child("JavaScript").child("Quiz 2").child("Questions");
firebaseRef.on("value", snap => {
this.setState(prevState => ({
Questions: [...prevState.Questions, snap.val()]
}))
})
}
Then rendering them:
renderQuiz() {
const { Questions, currentQuestion } = this.state;
let QTile, choice_1, choice_2, choice_3, choice_4 = "";
Questions.map(value => {
QTile = value.Q1.Question;
choice_1 = value.Q1.Choice_1;
choice_2 = value.Q1.Choice_2;
choice_3 = value.Q1.Choice_3;
choice_4 = value.Q1.Choice_4;
})
return (
<div className="panel-group questions">
<div className="panel panel-primary">
<div className="panel-heading">{QTile}</div>
<div className="panel-body">
<input type="radio" value={choice_1} /> {choice_1}
</div>
<div className="panel-body">
<input type="radio" value={choice_2} /> {choice_2}
</div>
<div className="panel-body">
<input type="radio" value={choice_3} /> {choice_3}
</div>
<div className="panel-body">
<input type="radio" value={choice_4} /> {choice_4}
</div>
</div>
<button className="btn btn-info" onClick={this.nextQuestion} style={{ float: "right", marginTop: "15px" }}>Next</button>
</div>
)
}
Next question is empty currently:
nextQuestion() {
console.log("Next Question");
}
First solution:
You could use Object.keys when retrieving your question object. This yields an array with the keys of an object. You store these in a field or your state and an index referring to the current position in this array.
So (sorry, never done Firebase, I'll be checking if I can adapt this to your code), your final state would be:
{
questions: { /* Object of questions, with keys Q1, Q2 and so */ },
currentQuestionIndex: /* some integer that you would increment */
}
Then, to access your questions you would do (in your render method):
const { questions, currentQuestionIndex } = this.state;
const currentQuestionKey = Object.keys(questions)[currentQuestionIndex];
const currentQuestion = questions[currentQuestionKey];
And to update the question (in your nextQuestion):
this.setState({ ...this.state, currentQuestionIndex: currentQuestionIndex + 1});
Better solution:
Another option, would be to turn Questions into an array directly using the Object.values function.

Categories