Dynamically added <input> not being rendered - javascript

I have a string which holds a form that I want to dynamically render.
When the user clicks "Add Step", my template string is added to a list and rendered with an ngFor. The problem is that the input doesn't render, only the <h3> tag.
Before button click:
After - no input is rendered on screen or in the html:
ts:
scenarioTemplate = `
<div id="content">
<h3>Name</h3>
<input name="{{i}}" [ngModel]="i">
</div>`;
steps = [];
addScenarioStep() {
this.steps.push(this.scenarioTemplate);
}
html:
<form #myForm="ngForm">
<fieldset ngModelGroup="inputs" #inputs="ngModelGroup">
<ng-container *ngFor="let step of steps; let i = index">
<div [innerHTML]=step attr.stepId="{{i}}"></div>
</ng-container>
</fieldset>
</form>
<button id="addStepBtn" type="button" class="btn btn-light" (click)="addScenarioStep()">Add Scenario Step +</button>

Put that whole thing
<div id="content">
<h3>Name</h3>
<input name="{{i}}" [ngModel]="i">
</div>
in the template, not the TS.
Don't mutate the array. Instead of
this.steps.push(newElem);
do
this.steps = [...this.steps, newElem];

Related

How to remove and add dropdown values based on the condition selected by another dropdown in angular?

My requirement:
I have to create a form row that has two select dropdowns and add and delete button at last
1. the first dropdown will have a list of values for ed.. ['one', 'two', 'three','four']
2. the second dropdown will have the condition ['and','or']
let say the first row of the form value selected like ['one'] ['and'] then clicking on the add button then the second row will create. here first dropdown should not show the 'one' value because the condition is 'and'. if user select or then it should all the values. similary for all the rows i have to create logic in angular.
HTML Code:
<div class="state-filter-conditions-grid subs-model-sec filter-grid" *ngFor="let filterCondition of filters; index as i">
<!-- OUTPUT PROPERTY -->
<ng-container *ngTemplateOutlet="filterView == 'subscription'? attrs: map; context: { index: i }"></ng-container>
<!-- CONNECTOR CONDITION -->
<div class="dt-attr valueCondition ctrl-condition minimal">
<div class="abs-cheveron select-box-cheveron">
<select class="state-select" [(ngModel)]="filters[i].op">
<option *ngFor="let val of op" [value]="val">{{val}}</option>
</select>
</div>
</div>
<!-- INPUT -->
<ng-container *ngTemplateOutlet="filterView != 'subscription'? attrs: map; context: { index: i }"></ng-container>
<!-- SELECT -->
<div class="dt-attr icons center-align">
<select class="ctrl-condition" *ngIf="!(i==filters.length-1)" [(ngModel)]="filters[i].logop" (change)="operatorChange(filters[i])">
<option *ngFor="let val of logop" [value]="val">{{val}}</option>
</select>
</div>
<!-- ICONS -->
<div class="dt-attr icons center-align">
<button *ngIf="i==filters.length-1" class="add-btn" (click)="addStateConditionRow()"><i
class="fa fa-sm fa-plus add-icon" aria-hidden="true"></i></button>
<button *ngIf="i>0" class="delete-btn" (click)="deleteStateConditionRow(i)"><i
class="fa fa-sm fa-trash delete-icon"></i></button>
</div>
</div>
<ng-template #attrs let-i="index">
<div class="dt-attr ">
<ng-container *ngIf="!dataProps">
<input class="ctrl-attr" type="text" [(ngModel)]="filters[i].value">
</ng-container>
<ng-container *ngIf="dataProps">
<select class="value-select" [(ngModel)]="filters[i].value">
<option *ngFor="let data of dataProps" [value]="data.attrId">{{data.attrName}}</option>
</select>
</ng-container>
</div>
</ng-template>
<ng-template #map let-i="index">
<div class="dt-attr ctrl-condition minimal">
<input class="ctrl-attr" type="text" clickOutside (clickOutside)="closeAccordion($event)" (click)="openAccordion($event)" [(ngModel)]="filters[i].attribute">
<div *ngIf="showAccordion" class="state-filter-accordion" style="position: absolute;
top: 105%;
width: 100%;z-index:1">
<app-common-accordion-mapping [filterInputRef]="filterInputRef" [inputAttributes]='inputAttributes' ></app-common-accordion-mapping>
</div>
</div>
</ng-template>
can you guys help me to achieve this.
Thanks in advance.
I think what you are looking for is to dynamically populate dropdown based on a particular logic. This stackoverflow answer covers that for AngularJS and a stackoverflow question + blog for Angular 8 . However I cannot help you with the business logic itself as it is not clear to me.

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

How to edit passed data from parent to child component in Angular 4

I have parent component with all teams and child component for displaying each of team in box using *ngFor and #Input. All child components are displayed fine but when I want to change each of them opening modal I always get the data from first object in array of teams.
team.component.html
<app-team-box *ngFor="let team of teams" [team]="team"></app-team-box>
team-box.component.html
<div class="ibox">
<div class="ibox-title">
<h5>{{this.team.name}}</h5>
<a class="btn btn-xs" (click)="openEditTeamModal(this.team)">Edit</a>
</div>
</div>
<div class="modal fade" id="edit_team_modal">
<div class="modal-dialog">
<div class="modal-content">
<form role="form" (ngSubmit)="editTeam()" novalidate>
<div class="modal-body">
<div class="row" *ngIf="this.team">
<label>Team name:</label>
<input type="text" id="name" name="name" [(ngModel)]="this.team.name" #name="ngModel">
</div>
</div>
<div class="modal-footer">
<button type="submit">Save</button>
</div>
</form>
</div>
</div>
team-box.component.ts
export class TeamBoxComponent implements OnInit {
#Input() team;
constructor(private teamService: TeamService) {}
openEditTeamModal(selectedTeam): void {
this.team = selectedTeam;
$("#edit_team_modal").modal();
$(".modal").appendTo("html");
}
editTeam(): void {
this.teamService.update(this.team).subscribe((team: Response) => {
$("#edit_team_modal").modal("hide");
});
}
}
The problem is when I change first one everything is fine, modal is populated with name and after changing it's get saved. But when I click on edit button for example second team, in modal I get the data for first team. Why this.team is always referencing to first object in array of teams?

Dynamically add element to DOM in angular

This is how my page looks like on initial load
<body>
<div class="col-md-12" id="dataPanes">
<div class="row dataPane"> Chunk of html elements </div>
</div>
<div class"col-md-12 text-right">
<input type="button" class="btn btn-primary" value="Add dynamic row" ng-click="addElementChunk()" />
</body>
I am in need to add rows to div#dataPanes on button click
If I was using jQuery,addElementChunk() function would have looked as below
var addElementChunk = function()
{
var html = "<div class='row dataPane'> Chunk of html elements </div>";
$("#dataPanes").append(html);
}
but how do I implement the same in angular??
You need to use $compile
Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
and $sce
Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
addElementChunk = function(){
var html = '<div class="row dataPane"> Chunk of html elements </div>';
var trustedHtml = $sce.trustAsHtml(html);
var compiledHtml = $compile(trustedHtml)($scope);
angular.element(document.getElementById('dataPanes')).append(compiledHtml);
}
you can append new div using angular ng-repeat directive
lets say you have an array that contain one element and every time you click the button you add another element to the array, while you are repeating it in your "dataPane" div
so you code could be:
HTML
<div ng-app="myApp" ng-controller="myCtr">
<div class="col-md-12" id="dataPanes">
<div class="row dataPane" ng-repeat="element in added_elements"> Chunk of html elements ( {{element}} ) </div>
</div>
<div class="col-md-12 text-right">
<input type="button" class="btn btn-primary" value="Add dynamic row" ng-click="addMoreElements()" />
</div>
</div>
JS
angular
.module('myApp', [])
.controller('myCtr', ['$scope', function($scope) {
$scope.added_elements = ["elem 1"];
$scope.addMoreElements = function(){
$scope.added_elements.push("elem "+ ($scope.added_elements.length+1));
}
}])
so you can add whatever data you want about your repeated row and bind it in html in simple way without having to repeat the whole html code
Working Demo
You can also append a new html element in this way. I think its very easy to write and also understand. hope it will help you.
angular.element used to access the html element.
Here is the html code:
angular.module('myApp',[]).controller('myCtrl', function($scope){
$scope.addElementChunk = function()
{
var htmlStr = '<div class="row dataPane"> Chunk of html elements </div>';
debugger;
angular.element(document.getElementById('dataPanes')).append(htmlStr);
}
});
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<div class="col-md-12" id="dataPanes">
<div class="row dataPane"> Chunk of html elements </div>
</div>
<div class="col-md-12 text-right">
<input type="button" class="btn btn-primary" value="Add dynamic row" ng-click="addElementChunk()" />
</div>
</div>
Here is the fiddle link

Is JQuery breaking my functionality?

I am making an app, the user can either select an item or use their camera to get the qr code which translates into an item's ID.
The problem is that I think some JQuery is messing with my scope from working properly.
I have to get the QR code by listening to an innerHtml change, once it changes (DOMSubtreeModified) the following occurs.
var index = 0;
$('#result').one('DOMSubtreeModified', function(e) {
var code = "";
if (e.target.innerHTML.length > 0) {
code = e.target.innerHTML;
$scope.ToRun(code);
}
});
$scope.ToRun = function(code) {
for (i = 0; i < $scope.plantList.length; i++) {
if ($scope.plantList[i].plantcode == code) {
index = i;
break;
}
}
$scope.currentPlant = $scope.plantList[index];
$scope.plantDetails = false;
$scope.searchDetails = true;
}
For some reason the following does not have any effect on my ng-classes. As when an item is selected I hide the input dialogs, and show the result one.
$scope.plantDetails = false;
$scope.searchDetails = true;
But when a user selects the item manually it works just perfectly. (the items have an ng-click on it)
$scope.viewPlant = function(plant) {
$scope.currentPlant = plant
$scope.plantDetails = false;
$scope.searchDetails = true;
};
And the above works fine, with the ng-click. So why won't my new function that listens for an innerHtml change work?
HTML snippet
<div ng-class="{ 'hidden': searchDetails }">
<!-- CHOOSE INPUT TYPE -->
<div class="form-group" style="margin-bottom:0px">
<div class="btn-group btn-group-justified">
<div class="btn-group">
<button type="button" class="btn btn-default" ng-click="digits = false; qr = true">Plant Code</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-default" ng-click="digits = true; qr = false">QR Code</button>
</div>
</div>
</div>
<br />
<!-- QR CODE INPUT -->
<div ng-class="{ 'hidden': qr }">
<img id="blah" src="./images/placeholder.png" />
<span class="btn btn-default btn-file">
<i class="glyphicon glyphicon-camera"></i>
<input type="file" onchange="readURL(this);handleFiles(this.files)">
</span>
<div id="result">xxxxxx</div>
<canvas id="qr-canvas" width="800" height="600"></canvas>
</div>
<!-- MANUAL SELECTION INPUT -->
<div ng-class="{ 'hidden': digits }">
<input ng-model="search.$" style="width:100%; font-size:30px; text-align:center" placeholder="Plant Code" />
<div style="overflow: auto; max-height:250px">
<table class="table table-striped" style="background-color:lightblue">
<tr ng-repeat="plant in plantList | filter:search" ng-click="viewPlant(plant)" style="cursor:pointer">
<th>{{plant.name}}</th>
<th>{{plant.plantcode}}</th>
</tr>
</table>
</div>
</div>
<div>
</div>
</div>
<div ng-class="{ 'hidden': plantDetails }">
// results - this should appear when one of the above is entered.
// works for manual selection, but not qr code
</div>
Just confused on why my QR input will not fire off the change-class options to hide the searchDetails div and show the plantDetails div
EDIT: Doing a small test, it seems that my class variables are indeed not updating at all. I just put the values at the bottom of my page and they do not update when hitting the block of:
$scope.plantDetails = false;
$scope.searchDetails = true;
You need to let Angular know about the change. Add this at the end of your method and let me know if it works.
$scope.$apply();

Categories

Resources