Anything like template references in AngularJS? - angularjs

I'm trying to create a form whose layout is entirely data driven.
Example data source:
{
title : "Form Test",
fields : [{
name : "FieldA",
type : "string",
value : "initial value"
}, {
name : "FieldB",
type : "selection",
options : ["1", "2", "3"],
value : "2"
}, {
name : "FieldC",
type : "struct",
value :
[{
name : "FieldC1",
type : "string",
value : "initial value"
}, {
name : "FieldC2",
type : "string",
value : "initial value"
}
]
}
]
}
I think can use ng-repeat and ng-switch to choose the form element depending on the 'type', however I get stuck when it comes to doing this recursively when I get to 'FieldC'.
<span ng-switch on="field.type">
<div ng-switch-when="string">STRING: {{field.value}}</div>
<div ng-switch-when="selection">SELECTION: {{field.value}}</div>
<div ng-switch-when="struct">STRUCT: ????</div>
<div ng-switch-default>DEFAULT:{{field.value}}</div>
</span>
Essentially I want a way that when I encounter a "struct" it recursively applies the ng-switch to the struct fields? Is there any way to "reference" the template so it can be used in multiple places on the same page? The support for template "partials" seems to need to be coordinated server-side via routes which seems like overkill here. Is this something where I need to start digging into creating my own directives?
EDIT I just stumbled across this that looks like it has a decent chance of doing what I want (I have yet to properly test it), is that in the right direction?

You'll want to build a directive that takes this kind of data and builds the form from it.
The way to treat the recursion is to treat every level, including the top level, as another struct. I built a version here: http://jsfiddle.net/U5Kyp/9/
Make sure you read the directive guide in the docs so you understand what's happening: http://docs.angularjs.org/guide/directive

Here is an update of accepted answer for angular.js 1.0.1 There were a few non-compatible changes in stable version:
ng-app is now required directive
scope syntax and semantics were changed
http://jsfiddle.net/9qAfM/1/

In my opinion this is a bad case of the inner platform effect. Quoting wikipedia: "tendency of software architects to create a system so customizable as to become a replica, and often a poor replica, of the software development platform they are using".
AngularJS already has a powerful mechanism for traversing a tree of objects and building a stack of scopes and controllers out of it. You could argue that it is exactly what AngularJS IS.
If you are forced to build forms out of such abominable JSON, I think the easiest way is to turn them into HTML (by means of a simple template language of any kind, server side or client side) and then using the $compile service to turn them into an angularjs application.

Related

How can my template include an element whose type is determined by an expression in angularjs?

It's 2022 and sadly I'm learning AngularJS (already past end of life!)
I need need to use what might be called a dynamic element/component. Pseudocode example:
In controller:
this.theElementName = 'b';
In the template:
<{{$ctrl.theElementName}}>this is some text</{{$ctrl.theElementName}}>
I would want this to create <b>this is some text</b>.
The reason is that I want to generate an array of different directives to render, and I don't want code like:
<b ng-if="$ctrl.theElementName === 'b'">this is some text</b>
<div ng-if="$ctrl.theElementName === 'div'">this is some text</div>
<directive-abc ng-if="$ctrl.theElementName === 'directive-abc'">this is some text</directive-abc>
...
In Svelte, it's
<svelte:element this={theElementName} />
In Vue it's
<div :is="theElementName" />
EDIT: in response to the reluctant 'that person', clarifying the use-case
Consider a user-configurable UI. The result of the configuration might be an array list of components desired. I would then need to loop and output those different components in my template. Of course the components would need a standard interface for properties passesd in, events emitted etc. but that can all be designed for.
My code could do a big switch statement, but that requires prior knowledge of every possible component that might be used now or in the future. By doing it the way I intend to, however, a future person could add a component without needing to touch this code.
You can write directive my-directive to use:
<div my-directive="$ctrl.theElementName">...
to generate:
<div><component-a>...
<div><component-b>...
<div><component-c>...
All directive should do is to generate html string and compile it:
element.append($compile('<' + scope.myDirective + '>...')(scope))
(also remember to update content in onChanges if you want to support it)
Directive may also copy certain/all attributes from original element etc.
P.S. you should be cautious e.g. if component name comes from database that may allow injections.
Not a brilliant solution, but documenting what is more of a workaround.
ng-include can be used to source another template file. That file can contain the component you need to include.
<ng-include src="'/path/to/' + theElementName + '.html'"></ng-include>

Does AngularJS translate provides any cross-reference mechanism?

I defined the name of some fields in my translation file and now I want to add some validation messages. This would be my translation file i.e:
{
"field-name": "Name",
"field-email": "Email",
"required": "The field {{field}} is mandatory"
}
Is there any way to tell angular translate to cross-reference and pass as parameter the key of another translation? Something like:
<span translate translate-values="{field: 'field-name'}">
required
</span>
or
<span translate translate-values="{field: 'field-email'}">
required
</span>
I searched the docs and googled it but got no results.
If this is not possible, what would be the less bloated way to implement it? Take into account this is for a SPA (Single Page App) and the user can change the language without reloading the page.
I managed to deal with it using $translateProvider.postProcess().
It even works with translate-values, and nested translate-values parameters (with some care not to have two parameters with the same name)
Check it here: https://codepen.io/Onnizuka/pen/ePwKMK

Angular 2 interpolation vs property binding

I know from multiple blogs and some questions here in stackoverflow that in Angular 1 ng-bind has better performance than {{ }} interpolation because of the way the $digest process works.
Angular 2 has changed the way it does data-binding and I want to know if there is any test on the subject. Specifically if this
<h1 [innerText]="obj.name"></h1>
is still better than this
<h1> {{ obj.name }} </h1>
Using getTitle() method as example. checkBinding is debug mode check, can be ignored.
Attribute binding calls sanitize and el.setAttribute:
var currVal_0 = self.context.getTitle();
if (jit_checkBinding34(throwOnChange,self._expr_0,currVal_0)) {
self.renderer.setElementAttribute(self._el_0,'innerHTML',((self.viewUtils.sanitizer.sanitize(jit_SecurityContext36.HTML,currVal_0) == null)? null: self.viewUtils.sanitizer.sanitize(jit_SecurityContext36.HTML,currVal_0).toString()));
self._expr_0 = currVal_0;
}
Interpolation, calls el.textContent = value;:
var currVal_0 = jit_interpolate36(1,'',self.context.getTitle(),'');
if (jit_checkBinding34(throwOnChange,self._expr_0,currVal_0)) {
self.renderer.setText(self._text_1,currVal_0);
self._expr_0 = currVal_0;
}
The only difference is using sanitize, you can check html_sanitizer.ts source code to see that is does some complicated stuff.
Interpolation: It represents as {{}}. it may be concatenate two string ,calculate value and display value.
Property Binding: It represents as [].It is mainly used for non-concatenate string like variable.
Interpolation is a convenient alternative for property binding in many cases.
In fact, Angular 2 translates those interpolations into the corresponding property bindings before rendering the view.
In Angular 2, I think there is no technical reason to prefer one form to the other. You should be choosing the form that feels most natural for the task.

AngularJS: how put correct model to repeated radio buttons

I think I have some sort of special code here as all I could google was "too simple" for my problem and it also didn't helped to come to a solution by myself, sadly.
I got a radio button group of 2 radios. I am iterating over "type" data from the backend to create the radio buttons.
My problem is the data binding: When I want to edit an object its "type" is set correctly, but not registered by the view so it doesn't select the desired option.
Follwing my situation:
Backend providing me this as "typeList":
[
{"text":"cool option","enumm":"COOL"},
{"text":"option maximus","enumm":"MAX"}
]
HTML Code:
<span ng-repeat="type in typeList track by type.enumm">
<input
type="radio"
name="type" required
ng-model="myCtrl.object.type"
ng-value="type">
{{type.text}}
</span>
Some Explanation
I don't want to use "naked" texts, I want to use some sort of identifier - in this case it is an enum. The chosen value shall be the entire "type", not only "type.text" as the backend expects type, and not a simple String.
So all I do with this is always a package thingy, the type.text is for like formatted/internationlized text etc.
A Pre-Selection works by setting this in the controller: this.object.type = typeList[0];
The first radio button is already selected, wonderful.
But why isn't it selected when editing the object. I made a "log" within the HTML with {{myCtrl.object.type}} and the result is {"text":"cool option","enumm":"COOL"}. The very same like when pre selecting. I already work with the same "technique" using select inputs, and it works fine. I also found some google results saying "use $parent because of parent/child scope". But 1) I didn't get that straight and 2) think it is not the problem here, as I use a controllers scope and not the $scope, or is this thinking wrong?
It might be explained badly, sorry if so, but I hope someone 1) get's what I want and 2) knows a solution for it.
Thank you!
If you're trying to bind to elements from an array, I believe you need to assign the actual elements from the array to your model property.
So this creates a new obj and sets it to $scope.selectedType (not what you want):
$scope.selectedType = {"text":"cool option","enumm":"COOL"};
whereas this assigns the first element of the array (which is what you want)
$scope.selectedType = $scope.typeList[0];
So to change the model, you can lookup the entry from the array and assign it to your model with something like this
$scope.selectedType = $scope.typeList.filter(...)
Here's a quick example of this approach http://plnkr.co/edit/wvq8yH7WIj7rH2SBI8qF

When and when not to use {{...}} inside the AngularJS view file?

I am trying to use a video player (the jwplayer) inside my AngularJS project. This post consists with 2 parts: part 1 for background, part 2 is my question. Thanks.
PART 1: Solving the "Error loading player: No playable sources found" problem.
At first the video is not showed, just a black rectangle on the page ui, and a quite misleading console message saying "No suitable players found and fallback enabled".
Hours later I happened to change the jwplayer size configuration from "height:450, width:800" to "height:'100%', width:'100%'". This time jwplayer shows a message on the page ui: "Error loading player: No playable sources found".
That gives me direction. My jwplayer usage looks like this:
<!-- this is my index.html -->
<div id="jw_container">Loading the player ...</div>
<script>
var player = jwplayer("jw_container").setup({
file:"{{model.my_video.video_url}}",
......
Change that file line to a hardcoded absolute video url will work, indicating that is the real problem. So eventually I got:
file: angular.element(document.getElementById('ng-wrapper')).scope().model.my_video.video_url,
and then problem solved, for now. (But still ugly, not intuitive, in my opinion.)
================================ SEPARATOR ===================
PART 2: My real question
Coming from the world of traditional template engines, one might tend to use {{...}} wherever he wants. But in AngularJS, situation is different.
Besides the above example, this is another example bit me before:
<img title="{{my_title}}" src='logo.png'> <!-- This works -->
<img src="{{my_image}}"> <!-- This doesn't. Use ng-src instead. -->
So in general, when and when not to use {{...}} inside the AngularJS view file?
Only for Part 1: If you're working with jwplayer and Angular then I highly recommend calling jwplayer from Angular as opposed to the other way round (what you're doing).
e.g.
myModule.controller('MyController', function ($scope, stuffINeed) {
jwplayer.key = "myKey";
jwplayer("myElement").setup({
file: "MyFileName"
});
As long as jwplayer.js has been loaded (link in index.html or use something like require.js) you're good to go!
Wrap the Jw Player as a Directive. You can use something like this: https://github.com/ds62987/angular-jwplayer
Trying to give my own thoughts on this topic.
Rule #1: Never write {{...}} inside AngularJS's <script>...</script> tag. Simply because AngularJS's template system doesn't work in thay way. Instead, use this:
//This is the usage in the view
angular.element(document.getElementById('the_id')).scope().foo
Alternatively, you can define an extra helper in view file:
//This is another usage in the view
function bar(foo) {
//do something with foo
}
and then use your model in a "usual" way via controller file:
//This is inside controller file
function YourCtrl($scope) {
bar($scope.foo);
}
That is it.
Yet I am still not sure when and when not to use {{...}} inside the html part of view. Leave this part to be answered by someone else. (I am now just a new AngularJS learner in week 2.)
Edit : I added test plunker : http://plnkr.co/edit/BMGN4A
Can you provide a full example? Because I write as below but get message Cannot read property 'videoUrl' of undefined although I have $scope.videodata.videoUrl = "bla bla"; in my controller.
<script type="text/javascript">
jwplayer("myElement").setup({
file: angular.element(document.getElementById('myElement')).scope().videodata.videoUrl,
image : "http://i3.ytimg.com/vi/2hLo6umnjcQ/mqdefault.jpg",
autostart : "false",
id : 'playerID',
width: '700px',
height: '400px',
primary : "flash",
stretching : "uniform",
modes : [{
type : 'html5'
}, {
type : 'download'
}, {
type : 'flash',
src : 'player.swf'
}]
});
</script>

Resources