Assign value to dynamically created scope variables - javascript

I'm trying to create a means to toggle dynamically created rows of information. I've tried using ng-init, and then passing it to a function, but I'm screwing up somewhere and I can't seem to wrap my head around how or if this is possible. The gap, I believe, is in getting the concatenated scope variable to be referenced elsewhere. I'm using Bootstrap 3 and AngularJS 1.5.
The HTML:
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{ equipment.equipId }}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': field{{ $index }}, 'glyphicon-triangle-right': !field{{ $index }}}"
data-ng-init="equipment['field' + $index] = true"
data-ng-click="toggleTaskEquip('field{{ $index }}')">
field{{ $index }}: I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="field{{ $index }}">
...stuff here...
</div>
</div>
The JS:
$scope.toggleTaskEquip = function(toggleBool)
{
if (toggleBool === true)
$scope.isTaskEquipOpen = false;
else if (toggleBool === false)
$scope.isTaskEquipOpen = true;
};

If I understand the problem correctly, you want to be able to toggle the boolean created in the ng-init with a click.
I think you need this:
<div class="container-fluid">
<div ng-controller="MyCtrl">
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{equipment.equipId}}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': isVisible, 'glyphicon-triangle-right': !isVisible}"
data-ng-init="isVisible = true"
data-ng-click="isVisible = !isVisible">I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="isVisible">
...stuff here...
</div>
</div>
</div>
</div>
You don't even need the function toggleTaskEquip on the $scope.
JSFiddle here.
ng-repeat creates a new scope for each template instance, so you can just create a separate isVisible for each equipment with isVisible = true in the ng-init.

Related

Is it possible to assign the hash(#) automatically in angular?

Is there any way where I can assign the hash(#) automatically to the elements inside an ngfor?
<div *ngFor="let note of notes; index as i">
<h3 #[note][i]>
{{ note }}
</h3>
</div>
The result I would expect would be something like this:
<div>
<h3 #note11>
note1
</h3>
</div>
<div>
<h3 #note122>
note12
</h3>
</div>
<div>
<h3 #note153>
note15
</h3>
</div>
You can use the index variable to automatically assign a unique id to each element in the ngFor loop. Here's an example:
<div *ngFor="let note of notes; index as i">
<h3 id="note{{i}}">
{{ note }}
</h3>
</div>
This will give each element an id of "note0", "note1", "note2", etc.
Regarding using #, it is not possible to use it in such a way. The # symbol is used to create a template reference variable, you can use it like <h3 #myNote>{{ note }} and you can access the element using myNote in your component.
Try this:
<div *ngFor="let note of notes; index as i">
<h3 #{{note}}{{i}}>
{{ note }}
</h3>
</div>
Note:
This would do your work but # is used to create template reference.

Conditionally hide the nth element of a v-for loop without modifying the array. vue 3 composition api search function

I have a ref variable (foxArticles ), which holds a list that contains 100 items. In a v-for loop i loop over each value. As a result, i have 100 values rendered on the page.
<template>
<div class="news_container">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput)"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const foxArticles = ref([]);
</script>
I also have a search function, which returns the value, if it includes the passed in keyword. The function is used in the child of the v-for loop.
<div class="search_input_container">
<input
type="text"
class="search_input"
v-model="keywordInput"
/>
</div>
<script>
const keywordInput = ref("");
function containsKeyword(article, keywordInput) {
if (article.title.toLowerCase().includes(keywordInput.toLowerCase())) {
return article;
}
}
</script>
The problem is, i can't use .slice() on the foxArticles array in the v-for loop, because that screws up the search functionality, as it returns only the values from the sliced range.
How can i have the access the all of the values of the array, while not rendering all 100 of returned articles on the initial load?
Any suggestions?
I think your approach will make it incredibly complex to achieve. It would be simpler to always iterate over some set, this set is either filtered based on a search-term, or it will be the first 100 items.
I'm not very familiar yet with the Vue 3 composition api so I'll demonstrate with a regular (vue 2) component.
<template>
<div class="news_container">
<div
v-for="article in matchingArticles"
v-bind:key="article"
class="article_single_cell"
>
... news_box ...
</div>
</div>
</template>
<script>
export default {
...
computed: {
matchingArticles() {
var articles = this.foxArticles;
if (this.keywordInput) {
articles = articles.filter(article => {
return this.containsKeyword(article, this.keywordInput)
})
} else {
// we will limit the result to 100
articles = articles.slice(0, 100);
}
// you may want to always limit results to 100
// but i'll leave that up to you.
return articles;
}
},
....
}
</script>
Another benefit is that the template does not need to worry about filtering results.
ok, so i kind of came up with another solution, for which you don't have to change the script part...
instead of having one v-for loop , you can make two of them, where each one is wrapped in a v-if statement div
The first v-if statement says, If the client has not used the search (keywordInput == ''), display articles in the range of (index, index)
The second one says = If the user has written something (keywordInput != ''), return those articles.
<template>
<div class="news_container">
<!-- if no search has been done -->
<div v-if="keywordInput == ''">
<div
v-for="article in foxArticles.slice(0, 4)"
v-bind:key="article"
class="article_single_cell"
>
<div class="news_box shadow hover:bg-red-100 ">
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<!-- if searched something -->
<div v-else-if="keywordInput != ''">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput) && keywordInput != ''"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
im not sure how this impacts performance tho, but that's a problem for another day

Reuse html template in Angular project

I have this html template file, range-details-dialog.tpl.html
<div class="modal-header clearfix text-left">
<h5>Update Range</h5>
</div>
<div class="modal-body">
<form name="form" role="form" class="ng-pristine ng-valid" novalidate ng-submit="updateRange()">
<div class="form-group-attached">
<div class="row">
<div class="col-sm-12">
<div class="form-group form-group-default input-group p-l-10 p-r-10" ng-class="{ 'has-error' : form.$invalid }">
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-sm-8"></div>
<div class="col-sm-4 m-t-10 sm-m-t-10">
<button type="button" class="btn btn-primary btn-block m-t-5"
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
</div>
</div>
</div>
Then I want to have another file forced-range-details-dialog.tpl.html
These two files could be one file instead with dynamically populated placeholders.
These are the places were substitution would be needed:
<h5>Update Range</h5> would become <h5>Update Forced Range</h5>
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
would become:
<p ng-show="form.forcedRangeDaily.$error.min" class="help-block">Forced Daily range more than £5</p>
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
, ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateForcedRange()">Update</button>
Is there a way to avoid having two separate template files for the above? Could you please provide some examples, links, or pointers as to how that can be achieved?
Also, I see in the answers that a solution would be to add a boolean parameter inside the component and then call it twice. I am not sure how to call the component though. I have pasted my component below:
angular.module('app.investment.rangeDetails')
.component('pxForcedLimitAmount', {
templateUrl: '/assets/js/apps/range/range-details-dialog.tpl.html',
bindings: {
amount: '<',
isRequest: '<?',
requestedAt: '<?',
#Input() isForced: boolean //<<----I added this based on answers below
},
controller: [function () {
var ctrl = this;
ctrl.$onInit = function () {
ctrl.isRequest = ctrl.isRequest === true || false;
};
}],
});
Seems like only the placeholders need to change, so you can use a variable to decide what placeholder to display on the template. For example:
isForced: boolean;
ngOnInit() {
this.isForced = true; // decide how you want to toggle this
}
on the template:
<h5 *ngIf="!isForced">Update Range</h5>
<h5 *ngIf="isForced">Update Forced Range</h5>
and
<p *ngIf="!isForced" ng-show="form.rangeDaily.$error.min" class="help-block">
Daily range more than £5</p>
<p *ngIf="isForced" ng-show="form.forcedRangeDaily.$error.min" class="help-block">
Forced Daily range more than £5</p>
you can do the same for other tags as well.
From the comments, one way to "determine" the value for isForced is to introduce an input property to the component i.e.
#Input() isForced: boolean;
and invoke the component from elsewhere like:
<app-user [isForced]="true"></app-user>
You can use inputs.Write a component which takes input, and render it in html. then call this component in desired places with its selector
For events use output
See the doc https://angular.io/guide/inputs-outputs

use ngFor loop in Angular 6 to dynamically create array of input elements and add dynamical validation based on template reference variables

I would like to create dynamically 3 input tags in Angular 6 to not copy/paste html code because that input elements have similar html and functionality.
For this purpose I created an array "reusableItems" inside component and initialize it :
let numberOfInputElements = 3;
for (let i = 0; i < numberOfInputElements; i++) {
this.reusableItems.push({
answer: 'Answer ' + (i +1),
passwordRecoveryAnswer: this.user['passwordRecoveryAnswer' + (i + 1)]
});
}
Then I put code inside my html :
<div *ngFor="let item of dropDownDataManagerService.reusableItems" >
<li class="col-xs-12 pl-lg pr0 pv-sm bd1-bottom">
<div class="col-xs-4 ph0 pt"> {{item.answerTitle}}</div>
<div class="col-xs-8">
<input type="text" name={{item.answer}} ref-{{item.answer}}="ngModel" class="col-sm-12 k-textbox ph0"
[(ngModel)]=item.passwordRecoveryAnswer
[pattern]="[a-z]"
required autocomplete="off"/>
</div>
</li>
</div>
It seems works fine but then I need to add error messages when these fields will be empty and not match to pattern. Something like :
<div *ngIf="__{{item.answer}}__.errors?.required ">
{{'Please provide an answer' | translate}}
</div>
<div *ngIf="__{{item.answer}}__.errors?.pattern">
{{'Pattern is not match'}}
</div>
I don't know what should i put inside ngIf condition.
How can I do it if my template reference variables are comes from array?
Is anyone have ideas?
Thanks
Angular creates unique template reference variable for each embedded template so that you can use the same template reference variable name inside ngFor loop:
<div *ngFor="let item of reusableItems">
<li class="col-xs-12 pl-lg pr0 pv-sm bd1-bottom">
<div class="col-xs-4 ph0 pt"> {{item.answerTitle}}</div>
<div class="col-xs-8">
<input type="text" name={{item.answer}} ref-answer="ngModel" class="col-sm-12 k-textbox ph0" [(ngModel)]="item.passwordRecoveryAnswer"
[pattern]="'[a-z]'" required autocomplete="off" />
<div *ngIf="answer.errors?.required">
{{'Please provide an answer'}}
</div>
<div *ngIf="answer.errors?.pattern">
{{'Pattern is not match'}}
</div>
</div>
</li>
</div>
In the code above I use the same name for each input in array
ref-answer="ngModel" // or you can also use #answer="ngModel

ng-click to affect only the element it is inside

I am using ng-repeat to generate some elements...
<div class="form-block" ng-repeat="form in formblock | filter:dateFilter">
<div ng-click="showResults()" ng-if="repeat == true" class="drop">{{ form.form_name }} <span class="caret"></span></div>
<div ng-show="results" class="formURL">{{ form.url }}</div>
<div ng-show="results" class="formCount">{{ form.count }}</div>
<div ng-show="results" class="formSubmit">{{ form.submit }}</div>
</div>
As you can see, ng-click="showResults()" toggles the display of the other elements. The problem is, I only want the ng-click to toggle the elements inside the same container, not toggle all elements.
In short, I only want the click event to affect the elements in the same container that the function is called, how can I do this?
this is showResults in my controller...
$scope.showResults = function(){
return ($scope.results ? $scope.results=false : $scope.results=true)
}
ng-repeat provides you with a special variable (unless you already have an identfier): $index.
Using this, you can store (instead of a single boolean value) an object $index => toggleState in your angular code:
$scope.hiddenHeroes = {};
$scope.toggleHero = function (idx) {
$scope.hiddenHeroes[idx] = !$scope.hiddenHeroes[idx];
}
And in your HTML:
<div ng-repeat="hero in heroes">
<div class="hero" ng-hide="hiddenHeroes[$index]">
<h1>
{{hero}}
</h1>
All you want to know about {{hero}}!
<br />
</div>
<a ng-click="toggleHero($index)">Toggle {{hero}}</a>
</div>
See it live on jsfiddle.net!
You can use $index to index item/containers and show the corresponding results:
<div ng-click="showResults($index)" ng-if="repeat == true" class="drop">{{ form.form_name }} <span class="caret"></span></div>
<div ng-show="results[$index]" class="formURL">{{ form.url }}</div>
<div ng-show="results[$index]" class="formCount">{{ form.count }}</div>
<div ng-show="results[$index]" class="formSubmit">{{ form.submit }}</div>
And your function
$scope.showResults = function(index){
return ($scope.results[index] ? $scope.results[index]=false : $scope.results[index]=true)
}

Categories

Resources