angularjs: dynamic html dependent on json data - html

I want to show the content of my json model in a dynamic way, depending on the provided json. I use ng-repeat to loop through my data and want to display a html template to fill with data dependent on the encountered data type.
JSON
{
"elements": [
{
"type": "input-text",
"desc": "Full Name"
},
{
"type": "input-checkbox",
"desc": "Accept Terms"
}
]
}
This should result in different html code, appropriate filled with the json content.
E.g.
<div><label>Full Name</label> <input type="text"></div>
<div><input type="checkbox"> <label>Accept Terms</label></div>
Right now what I do is to use an angularjs directive to create an element and add the json values to the right spot. e.g. element.html('<div><input type="checkbox"> <label>' + scope.item.desc + '</label></div>') That seems like the jquery way (or worse) to do it although I want to do it the 'right' angularjs way.
How can I use a different HTML template filled with content, dependent on the encountered JSON data?
PS: The above example is a simple one, the encountered data is far more complex than switching the position of the label and input field.

All you need to do is set the data on the scope, then use the ng-repeat directive in your HTML to output it:
Controller:
.controller('MyData', function ($scope) {
$scope.myModel = {
elements: [ { desc: .. }, .. ]
};
})
You would be using the $http service or some other appropriate method in this controller to populate myModel with data, but in the end the data needs to end up on the $scope object somehow. Then it's the template's job to display that data:
<div ng-controller="MyData">
<ul>
<li ng-repeat="element in myModel.elements">
{{ element.desc }}
</li>
</ul>
</div>

A simple solution seems to use ngSwitch with different HTML paths, e.g.:
<div ng-switch="item.type">
<div ng-switch-when="input-text">
<div><label>{{item.desc}}</label> <input type="text"></div>
</div>
<div ng-switch-when="input-checkbox">
<div><input type="checkbox"> <label>{{item.desc}}</label></div>
</div>
<div ng-switch-default>Unknown item.type: {{item.type}}</div>
</div>
Seems the approach using an angularjs directive (which I took first) may be a good solution for complex scenarios as "Huy Hoang Pham" points out in his blog post: http://onehungrymind.com/angularjs-dynamic-templates/ (thanks!)

Related

How to instantiate a variable in Nuxt

I've tried a couple of different ways to instantiate a variable in Nuxt but neither way seems to work. I have read around the subject and suspect that perhaps what I'm trying to do is not compatible with Webpack but I'm not sure how.
Here is a jsFiddle of the code: jsfiddle.net/tutmoses/z2365g49/4
First in the script section I export dataSize:
<script>
export default {
data(){
return {
page_name: "Run Model",
dataSize: 1296
}
}
</script>
Then in the HTML above I'm trying to import it but nothing renders:
<div class="setting">
<span class="setting-label">Training Size:</span>
<input id="trainingSize" :value="dataSize"></input>
</div>
I've also tried this:
<div class="setting">
<span class="setting-label">Training Size:</span>
<input id="trainingSize" :value= {{ dataSize }}></input>
</div>
...but the value instantiates as
{{
I've tried both of the above options without binding the value but that didn't work either.
Another way I've tried is this in a separate file:
export const nnSettings = {
dataSize: 1296
}
And then importing it with this:
import nnSettings from '~/components/testindex.js'
Again, zip.
The reason why I'm importing the value is because other values will be calculated from it. What would be the standard, best way to do it?
Nuxt (Vue) uses v-model to bind to form input. Have a look here for more info on form bindings
<div class="setting">
<span class="setting-label">Training Size:</span>
<input id="trainingSize" v-model="dataSize"></input>
</div>

How can I generate different DOM elements based on JSON properties in Angular?

As part of my application one of the features involves displaying release notes to the user when a new version of the app has been deployed. These release notes are stored in the backend in JSON format, with the structure as below.
{
"version": "1.0.0",
"workItems": [
{
"id": 18391,
"title": "Release Note Item Title",
"elements": [
{
"elementType": 1,
"elementData": [
"Release note item paragraph"
]
},
{
"elementType": 2,
"elementData": [
"Release note list item 1",
"Release note list item 2",
"Release note list item 3"
]
}
]
}
]
}
Within the JSON file there is the version number, as well as an array of workItem object, each object contains an Id, Title, and then an inner array elements which stores the textual elements that need to be displayed to the user. Each element in the elements array is a separate structure, with elementType representing exactly what type of HTML element needs to be displayed.
1 = Paragraph
2 = Unordered list
So elements[0] represents <p>Release note item paragraph</p>
While elements[1] represents
<ul>
<li>Release note list item 1</li>
<li>Release note list item 2</li>
<li>Release note list item 2</li>
</ul>
What I need to do is figure out a way that I can render the appropriate HTML tags according to the value of elementType for each object. Since Angular
dis-encourages the manual manipulation of the DOM I'm not sure how to proceed.
I have considered printing all of the elements and then using *ngIf to somehow determine which HTML elements are displayed to the user, but as far as I am aware *ngIf is solely for determining whether or not the element is displayed i.e. manipulating the CSS of already existing HTML elements, and can't be used to conditionally render certain HTML ahead of time.
<div *ngFor="let element of data.elements">
<p *ngIf="element.elementType === '1'">{{element.elementData}}</p>
</div>
I've tried the above, but this obviously doesn't work.
I feel like this isn't something that can be achieved in the template and instead will be need to done in the component first and then pushed to the template. Would it work if I instead built the HTML strings within the component using the JSON data ahead of time and then had the template display them incrementally?
Try like this:
<div *ngFor="let data of dataSet.workItems">
<div *ngFor="let element of data.elements">
<p *ngIf="element.elementType == 1">{{element.elementData[0]}}</p>
<ul *ngIf="element.elementType == 2">
<li *ngFor="let list of element.elementData">
{{list}}
</li>
</ul>
</div>
</div>
Working Demo
'elementType' in your JSON data is a number but you compare it to a string in the template (using quotes around the number).
Try this instead:
<div *ngFor="let element of data.elements">
<p *ngIf="element.elementType === 1">{{element.elementData[0]}}</p>
</div>

Working with custom components for input generates "No value accessor for form control with path X->0->Y"

I have a working form taking the following HTML markup. No errors or warnings.
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
I transformed it into a custom component, which also works, as shown below.
<app-input-text [info]="'Title'"
formControlName="targetField"
ngDefaultControl></app-input-text>
In my next view, I need to use FormArray as follows - still working code.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
</div>
</div>
Now, I expected that combining both (i.e. being able to use custom input component and being able to form array for components) would post no problem. However, the sample below doesn't work.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<app-input-text [info]="'Title'"
formControlName="targetField"
class="col-sm-6"></app-input-text>
</div>
</div>
It generates the following error.
No value accessor for form control with path: 'stuff -> 0 -> targetField'
The custom component is design like this (although given that it works in the explicit markup example, I'm not sure if it's relevant information). The only (wild) guess I have might be that value isn't jacked into the form array field somehow.
export class InputTextComponent implements OnInit {
constructor() { this.value = new EventEmitter<string>(); }
#Input() info: string;
#Output() value: EventEmitter<string>;
onEdit(value: any): void { this.value.emit(value); }
}
The group and array creating in the current view is done like this (not sure if this is of any relevance neither, as it works for the explicit HTML markup case).
this.form = builder.group({
id: "",
stuff: builder.array([
builder.group({ targetField: "aaa" }),
builder.group({ targetField: "bbbb" }),
builder.group({ targetField: "cc" })
])
});
Is there a limitation in Angular in this regard that I'm not aware of? I'm rather sure there's not and that I'm just doing something fairly clever simply missing a tiny detail.
I do understand the error but I can't see how it relates to the code. The form can't find the 0th element in the array or that element has no field of that name. Since I do get to see a few rows, I know there must be a 0th element. Since I specified the name of the field, I know there is indeed such. What else am I missing?

Submit $modelValue instead of $viewValue in a traditional form post in AngularJS

I'm using an input mask directive to force an input field to adjust to a fixed decimal format (dependent on the locale) in AngularJS 1.4.9.
This is my code:
<div ng-app="MyApp">
<div ng-controller="MyCtrl">
<form action="http://httpbin.org/post" method="post" name='myForm'>
<input type="text" name="amount" ui-number-mask="2" ng-model="debit.amount">
<input type='submit'>
</form>
</div>
</div>
Everything works as expected. The model holds the non formatted decimal value and the input field displays the formatted value.
The problem arises when I hit submit and the form is submitted in a traditional POST fashion.
The server receives the following:
{
"form": {
"amount": "1,299.99",
},
}
instead of
{
"form": {
"amount": "1299.99",
},
}
I believe it's because it submits $viewValue instead of $modelValue.
Workarounds I've tried
Use a hidden field and set the VALUE attribute to the model value:
<input type='hidden' name="amount_hidden" value="{{debit.amount}}">
It works, but it's a bit of a hassle having to duplicate fields. See JsFiddle.
Submit using AngularJS' $http.post() to mimic a traditional form post. This also works, but it's even more cumbersome than the previous. See JsFiddle.
Is there an easier way to force the submission of $modelValue?

HTML "First Time User" form best practice (Jade / Angular)

So I'm trying to implement the following form in my app.
This is a form which should appear the first time a user tries to create a task in our app. Now my question is, what is the best way to deal with something like this? I'm not a very good frontend-guy and this might be a trivial question, I'm sorry if it is - nevertheless, I don't know the answer to it.
I'm not that curious about components etc, those are ok but rather of the flow. How should the things be organized in the html/js. Do I create a separate button each time, should the elements be dynamically inserted somehow.. etc
Any help would be awesome, thanks!
You could use angular directives for this, dynamically showing them based on other values. This should get you in the right direction:
<label for="taskName">Task name:</label>
<input type="text" name="taskName"
ng-model="task.name" />
<div ng-show="currentStep > 1">
<label for="assigned">Assigned:</label>
<select>
<!-- options etc. -->
</select>
</div>
<div>
<button class="btn btn-default"
ng-click="nextStep()">{{ currentStep.nextText }}</button>
</div>
controller:
.controller("MyCtrl",
["$scope", function($scope) {
$scope.steps = [
{ number: 1, nextText: "Let's go!" },
{ number: 2, nextText: "Next, please" }
];
$scope.task = {};
$scope.currentIndex = 0;
$scope.currentStep = $scope.steps[$scope.currentIndex];
$scope.nextStep = function (){
$scope.currentIndex += 1;
$scope.currentStep = $scope.steps[$scope.currentIndex];
}
}]);
Angular has a built in directive for this kind of process, ngSwitch. Using it, you can define a series of steps, and change the display based on the value of the step you are on in the process.
<form ng-switch="wizardStep">
<div ng-switch-when="Step1">This is Step 1</div>
<div ng-switch-when="Step2">This is Step 2</div>
</form>