How to duplicate div in angular - javascript

I am using innerHtml to show html content from variable in angular but it is not supporting external style and ngFor also.in the following screen whenever user click on the add service button then one row should increment.
this is the data having in service list with.
<div class="col-sm-6 col-md-4">
<label class="mobileNumberLabel " for="mobilrNumber">Select Service</label>
<div class="nice-wrap">
<select class="js-example-basic-single nice-textbox" style="width: 105%">
<optgroup *ngFor="let item of serviceObject" label='{{item.categoryName}}'>
<option *ngFor="let service of item.services">
{{service.serviceName}} - <span >{{item.categoryName}}</span>
</option>
</optgroup>
</select></div>
</div>
I tried with innerHtml
<div [innerHTML]="divItem"></div>
but it is showing like this

Angular doesn't work the way you envision it to work, based on your question. When you write a template in Angular, it's actually not HTML -- it's a custom Angular syntax known to the Angular compiler (ngc). Your templates are transformed to TypeScript code which manipulates the DOM based on the model (which is then transformed to JavaScript by tsc so it can be executed by the browser).
This transformation is happening while you're compiling your code. When it reaches user's browsers, there's nothing of Angular templates left in it: no *ngFor, no {{ interpolation.
Therefore, setting innerHTML to Angular's syntax (such as *ngFor or {{ }}) won't work, because you're trying to do in run-time of the application. You'll have to tweak your logic and the way you're building templates to a more structural way.
Instead of setting a string with {{ a }} which you [innerHTML] into an element, switch your logic so that you have an object, for example, { a: 'value' }, and pre-define your template such as {{ obj.a }} in that element.

I think you want to add a reactive form dynamically into the HTML.
I came across this situation some time back as I had to add new paragraphs dynamically.
In this code, I have a text area as input for the paragraph. I can add a paragraph by clicking on the Add New Paragraph button and delete a paragraph by clicking on the Remove button.
.html file should have -
<div formArrayName="contentDescriptions">
<div *ngFor="let contentItem of contentDescriptions.controls; let i=index">
<textarea [formControlName]="i" name="" id="" cols="30" rows="6" placeholder="Enter Description"></textarea>
<button (click)="deleteContentDescription(i)"> Remove</button>
</div>
<div>
<button (click)="addNewParagraph()">
<span class="icon icon-plus"></span> Add New Paragraph
</button>
</div>
</div>
.ts file should have -
form = this.fb.group({
contentDescriptions: this.fb.array([
this.fb.control('')
])
});
get contentDescriptions() {
return this.form.get('contentDescriptions') as FormArray;
};
constructor(private fb: FormBuilder) { }
ngOnInit(){
this.form.patchValue({
contentDescriptions: this.contentSelected.descriptionArray
});
}
addNewParagraph(){
this.contentDescriptions.push(this.fb.control(''));
}
deleteContentDescription(i){
this.contentDescriptions.removeAt(i);
}

Related

Angular Reactive Form Array with freeform object

I've been working with Angular for a bit and I know my way decently around Reactive forms and template-driven forms but I am having trouble with this problem, especially Angular's Reactive Form Array.
API response that I wish to POST/PUT to is formed like this, where the user is able to add a row and a dropdown will let them select the property, in this case, it's "p", "h1", "h2" and so on and the value they wished to have typed
"description": {
"body": [
{
"p": "test paragraph"
},
{
"h1": "test header"
}
],
I am in the process of converting our old JQuery and js nonsense to a Framework and it has been hard for me to wrap my mind on how to convert this process.
The user will click on Add item:
Then a row will be created via formArray I am assuming since I have tried this beforehand and it has worked but the dropdown to edit the property is giving me an issue.
And this is how it would look like when a few additional rows are created
I understand how formArrays work via this example:
Dynamically Add Rows Based on Condition Using Reactive Forms in Angular
so I will need a getter, as well as the function that creates the dynamic form and HTML but I am currently stuck at the moment.
I have eventually figured out most of the problem,
we will be using Angular's FormBuilder, FormArrays, and template interpolation.
Start by defining the form with form builder:
text-form.ts
this.textForm = this.fb.group({
lang: null,
description: this.fb.array([]),
});
You will be using Angular's getter in this case you want to grab description from the defined above form builder
get descriptionItems() {
return this.artifactTextForm.get('description') as FormArray;
}
Then you will create two functions one is used in a button to add rows and the other to delete the current row at the index in the Array
This will be a bit different from other examples as we will be using some ES6 tricks
addDescriptionItem() {
this.descriptionItems.push(this.fb.group({[this.selectedHeading]: ''}));
}
I defined this.selectedHeading as:
at the top of my component class
selectedHeading = 'h1';
Now create the button that will be created in the forloop on the template for each row
// deleteDescriptionItem removes the item at the index given in the formArray
deleteDescriptionItem(index: number) {
this.descriptionItems.removeAt(index);
}
Now comes one of the most important parts in the template to make sure everything looks nice. I am using Angular Material Design but it should work and perform the same natively.
<form [formGroup]="artifactTextForm">
<mat-dialog-content>
<mat-form-field appearance="fill">
<mat-label>Select Header Type</mat-label>
<mat-select [(value)]="selectedHeading">
<mat-option value="h1">Header 1</mat-option>
<mat-option value="h2">Header 2</mat-option>
<mat-option value="h3">Header 3</mat-option>
<mat-option value="h4">Header 4</mat-option>
<mat-option value="p">Paragraph</mat-option>
</mat-select>
</mat-form-field>
<!-- Description form array -->
<div formArrayName="description">
<div *ngFor="let item of descriptionItems.controls; let i = index" [formGroupName]="i">
<input
[formControlName]="this.selectedHeading"
placeholder="Enter Description Data"
[maxLength]="formMaxLength">
<button
mat-raised-button
color="primary"
type="button"
(click)="deleteDescriptionItem(i)">Delete Item</button>
</div>
<button mat-raised-button color="primary" type="button" (click)="addDescriptionItem()">Add Description</button>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button type="submit" (click)="onSubmit()">Save</button>
<button mat-raised-button type="reset" (click)="dialogRef.close()">Cancel</button>
</mat-dialog-actions>
</form>
Make sure the add button is outside of the div with the forloop and it should something like so.
side note:
Flame shot on ubuntu is amazing.

Angular 2 - trackBy function, what does it really do?

I was under the impression that trackBy function is used only when trying to optimize the performance of *ngFor, so that if something changes, the DOM doesn't have to be rebuild.
However, recently, I came across a situation when trackBy actually fixed a wrong behavior.
Take this for example: https://plnkr.co/edit/nRgdwoiKAMpsbmWaoMoj?p=preview
Focus on Hobbies section, especially HTML:
<div>
<h2>Hobbies</h2>
<div *ngFor="let h of user.hobbies; trackBy:customTrackBy; let i = index">
#{{i}} - {{h | json}}<br />
<input [(ngModel)]="h.name" name="hobby_name_{{i}}" /> <br /><br />
<select [(ngModel)]="h.type_id" name="type_{{i}}">
<option *ngFor="let t of types" [value]="t.id">{{t.type}}</option>
</select>
<br />
<br />
<button class="btn btn-warn" (click)="remove(i)">Remove</button>
<br /><br />
</div>
</div>
I had to explicitly define this part: trackBy:customTrackBy in the first *ngFor. If trackBy is removed, and the following steps are performed:
remove the first item
add a new item
In this case, the inputs of the first item get replaced with the content of the second item (both fields have the same content), however, the values in the model are correct.
trackBy solves this issue, but why?
I would really appreciate any kind of explanation. If this is not the right place to ask this kind of questions please redirect me to the correct one. Thanks.
update
Here's an example of the wrong behavior: https://plnkr.co/edit/u8YajKfHcPiVqY0WcJt7?p=preview remove the first item (cycling) and add a new item (add button) and see how both values get the same default value (BF will get replaced by "default value" even though the model stays correct).
*ngFor by default tracks items by object identity.
If you have primitive values like an array of strings, and use them in
<div *ngFor="let item of items; let i=index">
<input [(ngModel)]="item" name="item{{i}}">
</div>
and you edit one item, then *ngFor gets in trouble, because the identity of the edited item has changed.
With ngForTrackBy you can tell *ngFor to track the item by index, then above code will work fine when you edit fields.
Another use case is when you want *ngFor to track items by some custom object id property instead of the object identity.

Form validity using Angular ng-repeat

I have an Angular form that is parsing some JSON data.
I'm rendering using ng-repeat. However, I'm having an issue in that the form never becomes valid when a radio button in each group is selected.
I suspect the issue lies with the ng-model in each input, but I can't seem to figure out the correct way to dynamically create an ng-model inside an ng-repeat.
Form block code:
<form name="posttestForm">
<alert ng-repeat="alert in alerts"
type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert>
<div class="well question-well" ng-repeat="question in questions">
<p>
<strong>{{question.question}}</strong>
</p>
<ul>
<li ng-repeat="answers in question.answers">
<input ng-model="Q[question.id]"
type="radio" name="Q{{question.id}}" value="{{answers.id}}"
required="" data-key="{{answers.isCorrect}}" />{{answers.answer}}
</li>
</ul>
</div>
<button type="submit" class="btn btn-primary" ng-click="EvaluatePosttest(3)"
ng-show="!TestPassed">
Submit Questions</button>
</form>
Here's a Plunkr that shows the dynamic code and demonstrates the form never turning valid when a radio button in each group is selected.
http://plnkr.co/edit/HQGPIOCdn3TGlE96CpK5?p=preview
And here's a Plunkr using static content displaying it working.
http://plnkr.co/edit/ZFt2VnBfaQjuu73kaNQJ?p=preview
Just add this in your javascript controller
$scope.Q = [undefined,undefined,undefined,undefined,undefined,undefined];
Explanation : you set ng-model as Q[question.id] but Q is undefined so the ng-model won't ever work. You always must initialize variable in the controller. The only case it works not initialize is when you do :
ng-model="test"
if you do
ng-model="test.test"
ng-model="test[0]"
It won't ever work if it's not initialized properly.
You can do a custom form validation inside your controller. In your case:
$scope.Q = [];
$scope.$watch('Q', function (Q) {
$scope.posttestForm.$setValidity('count', Q.length >= $scope.questions.length);
}, true);
Inside that, you can do whatever validation you want.
Here's the working plunkr: http://plnkr.co/edit/7Ww4fjJzkDjifPaZ2QtG?p=preview

Submit a form made of multiple components

I've started Angular2 lately and I am facing a problem. I want to create a form to generate some multiple choices questions and this is my problem :
I have a FormComponent which display the layout of my form.
The AnswerComponent can be added to the FormComponent to provide multiple choices about the question.
I have been using the DynamicComponentLoader to programatically add thoses AnswerComponent inside of my FormComponent.
The thing is the submit button must belong to the FormComponent and I do not know how to alert my AnswerComponent to send their data to the FormComponent so it cans gather all the data and create my question.
If anyone has an idea that would be great !
Thank you !
Let's take a sample. I have a form that manages company details:
<form [ngFormModel]="companyForm">
<field label="Name" [state]="companyForm.controls.name">
<input [ngFormControl]="companyForm.controls.name" [(ngModel)]="company.name"/> {{name.valid}}
</field>
<field label="Tags">
<tags [(labels)]="company.labels"></tags>
</field>
<button type="submit" [disabled]="!companyForm.valid">Submit</button>
</form>
As you can see, I use two sub components:
The field one that aims to build the layout for a field block using Bootstrap3. It accepts a variable area to provide the form element (input, select, textarea). This component also leverages the associated control to display validation errors if any.
The tags one that manages the tags attribute that is a list of string. It allows to display, add and remove tags.
You can see that every form element leverages two way binding. This means that each form element is associated to a property of an object of the component. Here it's the company one that is a property of the component.
This means that when you want to submit the form, you can use this company object to build the payload of the corresponding HTTP request for example.
Let's deal a bit more with the associated with the company object. For inputs, it's obvious using the ngModel directive with this syntax: [(ngModel)]. With the tags sub component, it's perhaps not so obvious.
In fact you need define inputs and outputs to manage the labels with two ways bindings:
#Input labels:string[]
#Output labelsChanged: EventEmitter
When labels are updated, you need to call the emit method of labelsChanged.
Here is the complete code for the TagsComponent component:
#Component({
selector: 'tags',
template: `
<div *ngIf="labels">
<span *ngFor="#label of labels" style="font-size:14px"
class="label label-default" (click)="removeLabel(label)">
{{label}} <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</span>
<span> | </span>
<span style="display:inline-block;">
<input [(ngModel)]="labelToAdd" style="width: 50px; font-size: 14px;" class="custom"/>
<em class="glyphicon glyphicon-ok" aria-hidden="true" (click)="addLabel(labelToAdd)"></em>
</span>
</div>
`
})
export class TagsComponent implements OnInit {
#Input()
labels:string[];
#Output()
labelsChange: EventEmitter;
constructor(private elementRef:ElementRef) {
this.labelsChange = new EventEmitter();
}
removeLabel(label:string) {
var index = this.labels.indexOf(label, 0);
if (index != undefined) {
this.labels.splice(index, 1);
this.labelsChange.emit(this.labels);
}
}
addLabel(label:string) {
this.labels.push(this.labelToAdd);
this.labelsChange.emit(this.labels);
this.labelToAdd = '';
}
}
Hope it helps you,
Thierry

How to let Angular JS ng-init only under certain condition?

Let say I have the following short input form:
<form ng-controller="parentController" action="testing.php" method="post">
<input name="parentInputField" ng-model="x">
<div ng-controller="childController">
<div ng-repeat="item in itemList">
<input name="childInputField[]" ng-model="x">
</div>
</div>
</form>
As you may already know, the model x in childController will follow the value of of model x in parentController until the user type in something in the childController input field, so the user can simply change the value in parent for bulk input and then fine tune in child.
Now, after the user have submitted the form, I want to call the same form for editing function. To keep the bulk input function on new items, is there a way I can ng-init model x in childController only when there is a previous value?
Haven't tried but I believe you can achieve with:
<div ng-init="ctrl.value && (ctrl.value=1)">
BUT if you want an advice, avoid both ng-init and nesting controllers like this: it would be a pain to maintain this program. Prefer to use controllerAs syntax (https://docs.angularjs.org/api/ng/directive/ngController) and put init logic on controller/service.

Categories

Resources