I am building an Angular form that needs repeatable form elements inside an ngRepeat.
<form name="form">
<div ng-repeat="x in [1,2,3,4]">
<input name="something_{{$index}}" ng-model="hi" required>
<div ng-messages="form.something_{{$index}}.$error">
<ng-message="required">This is required</ng-message>
</div>
</div>
<pre>{{form | json: 4}}</pre>
</form>
Angular now supports dynamically declared input names so that you don't have to do something like:
<div ng-repeat="x in [1,2,3,4] ng-form="repeated-form"></div>
And you can use {{$index}} inside the ngRepeat to declare items dynamically. But this doesn't seem to work with ngMessages, which throws an error when I try to bind the index into it.
i.e. this:
<div ng-messages="form.something_{{$index}}.$error">
throws this:
Error: [$parse:syntax] Syntax Error: Token '{' is an unexpected token at column 16 of the expression [form.something_{{$index}}.$error] starting at [{{$index}}.$error].
How can we dynamically declare which property on the form to watch, if ng-messages can't watch the form value that is declared with its {{$index}}?
PLNKR: http://plnkr.co/edit/4oOasbtffTgKqmxcppUG?p=preview (check console)
ng-messages="form['something_' + $index].$error"
Should work. I generally wouldn't put {{ }} in any of the ng directives because most of the ng directives execute with priority level 0 (including the {{ }} directive, ngBind). Also, the ng directives all use $evaluate on their argument, so they look at variable values in the scope by default.
Priority 0 for multiple directives on the same element means that Angular can't guarantee which directive will be applied first. Because of that, it is generally best to avoid using ngDirectives together as behavior can vary. ngIf is an exception as it executes with priority 600 (which is why directives aren't evaluated for an ng-if element not currently in the DOM no matter what).
<div ng-repeat="x in [0,1,2,3]">
<input name="something_{{$index}}" ng-model="hi" required>
<div ng-messages="form['something_' + $index].$error">
<ng-message="required">This is required</ng-message>
</div>
</div>
http://plnkr.co/edit/k5nzkpkJwSuf5dvlMMZi?p=preview
Related
I want to check the condition in Angular.
I want to look at two numbers that would enter Div if these two numbers were equal.
How can I check this condition?
I tried the following method and it was wrong.
<div *ngIf="'{{item1.menuID==item2.menuID}}'">
{{item1.title}}
</div>
The code you have there would evaluate to a string given you're adding single quotes around the condition, also you don't need to interpolate to access an object inside an ngIf.
If you want to evaluate if item1.menuID and item2.menuID are equal you would do
<div *ngIf="item1.menuID == item2.menuID">
{{item1.title}}
</div>
You don't require to use interpolation within [anyAttributeEnclosingSquareBrackets] or any angular attribute like *ngIf, *ngFor or some attributes like formControlName
For example:
<child-component [childAttribute]="parentComponentVariable">
<div *ngFor="let x of arrayVariable">Array Variable is present in component</div>
<input type="text" formControlName="name">
You can directly compare like below code:
<div *ngIf="item1.menuID==item2.menuID">
{{item1.title}}
</div>
Assuming that, item1 and item2 are not private variables of component and are available or initialise expectedly
I get an exception Error TypeError and Error Context when the submit button is clicked. If I will delete the ngIf directive It will work as excepted, The Full StackTrace:
PlayerNameFormComponent.html:8 ERROR TypeError: Cannot read property 'value' of undefined
at Object.eval [as handleEvent] (PlayerNameFormComponent.html:8)
at handleEvent (core.js:13547)
at callWithDebugContext (core.js:15056)
at Object.debugHandleEvent [as handleEvent] (core.js:14643)
at dispatchEvent (core.js:9962)
at eval (core.js:12301)
at SafeSubscriber.schedulerFn [as _next] (core.js:4343)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:240)
at SafeSubscriber.next (Subscriber.js:187)
at Subscriber._next (Subscriber.js:128)
PlayerNameFormComponent.html
<form (ngSubmit)="onSubmit(firstPlayer.value, secondPlayer.value)"> // The line that throws the exception
<div class="input-field col s6">
<label for="firstPlayer">First Player Name</label>
<input #firstPlayer id="firstPlayer" name="firstPlayer" type="text" class="validate">
</div>
<div *ngIf="isMultiplePlayers" class="input-field col s6">
<label for="secondPlayer">Second Player Name</label>
<input #secondPlayer id="secondPlayer" name="secondPlayer" type="text" class="validate">
</div>
<button type="submit" class="waves-effect waves-light btn">Start</button>
</form>
PlayerNameFormComponent.ts
export class PlayerNameFormComponent {
isMultiplePlayers = true;
public onSubmit(firstPlayer: string, secondPlayer: string) {
console.log(firstPlayer);
console.log(secondPlayer);
}
}
EDIT:
I changed my form tag to - <form (ngSubmit)="onSubmit(firstPlayer?.value, secondPlayer?.value)"> and now its print to console the firstPlayer input value and instead of secondPlayer value its prints null
Thanks for any kind of help :)
Template reference variables can be accessed anywhere in template, so the docs state clearly. This article is very helpful to understand what happens with structular directives, in this case your *ngIf.
What happens with the *ngIf is that it creates it's own template, so your template reference is accessible inside the template, the template created by *ngIf and only in that scope.
Here's excerpt from the website:
Sample code that throws error:
<div *ngIf="true">
<my-component #variable [input]="'Do you see me?'>
</div>
{{ variable.input }}
The reason for this is the ngIf directive - or any other directive used together with a star (*). These directives are so called structural directives and the star is just a short form for something more complex. Every time you use a structural directive (ngIf, ngFor, ngSwitchCase, ...) Angular's view engine is desugaring (it removes the syntactical sugar) the star within two steps to the following final form (using the previous ngIf as an example):
<ng-template [ngIf]="true">
...
</ng-template>`
Did you notice? Something extraordinary emerged out of the previous HTML containing the structural directive - the ng-template node. This node is the reason for limiting the scope of the template reference variable to exactly this inner DOM part only - it defines a new template inside. Because of limiting a variable to its defining template any variable defined within the ng-template (and this means within the DOM part of the ngIf) cannot be used outside of it. It cannot cross the borders of a template.
But how to solve your underlying problem, i.e getting the values... You almost have a template-driven form set up already, so you could do that, or then go the reactive way :)
Your problem doesn't come from your *ngIf. Try to remove temporary the error, using Elvis operator:
onSubmit(firstPlayer?.value, secondPlayer?.value)
Or you can make sure into your onSubmit that the HTML element firstPlayer and secondePlayer return HTMLObject.
Into your component, do this:
onSubmit(firstPlayer, secondPlayer) {
console.log(firstPlayer, secondPlayer);
}
And into your HTML template, change the (ngSubmit) line with:
<form (ngSubmit)="onSubmit(firstPlayer, secondPlayer)">
If the result is correction you get ...
<input id="firstPlayer" name="firstPlayer" type="text" class="validate">
...
... into the console.
If it is really undefined, use [ngModel]="firstPlayer" and check if the error still occurs.
You can also just the line as given below to get it working if you don't want to go to the reactive forms approach as pointed by #AT82.
<div *ngIf="isMultiplePlayers" class="input-field col s6">
to
<div [hidden]="isMultiplePlayers" class="input-field col s6">
Try to avoid *(star) directives which cause these errors due to structural changes they do in the DOM.
In order give feedback on the validity in my input forms I'm using ng-class. These statements will look something like:
<div ng-class="{ 'has-error': !frmSomeName.vcHeader.$valid && frmSomeName.vcHeader.$dirty }">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
I dislike the lengthiness of the statement, and would like to replace it with something alike:
<div validation-state="frmSomeName.vcHeader">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
In order to avoid having to duplicate ngClass' behavior I'd like the the directive to add the ng-class directive.
This plnkr demonstrates my attempt at adding the attribute, and although it works in the simplest scenario, it is faulty and will not function with transclusion (or other more complex directives).
I know it doesn't work because of the misuse of the compile and link stages, however I'm not sure on how to actually make it work properly. Therefore my question: How do I add different directive-attribute from a directive-attribute?
The elements within the transclude directive aren't receiving the same scope. If you use the angualr $compile method and apply the scope of the transclude directive to the scope of the child directives it should work. The following should be added to your simpleTransclude directive:
link: function(scope, element) {
$compile( element.contents() )( scope )
}
remember to pass $compile into the directive.
I've forked your plnkr and applied the simpleTransclude scope to it's contents.
I am new to angularjs and trying following html template. But angularjs only replace the first {{ fragment.id }} expression in text area with the value. All the other {{ fragment.id }} expressions are left unmodified in generated html code.
<div class="input-box" ng-repeat="fragment in project.fragments">
<textarea id="code-{{ fragment.id }}" class="code-input" ng-model="content">{{ fragment.content }}</textarea>
<button class="btn btn-xs btn-primary" style="margin-top:5px;" ng-click="execute({{ fragment.id }})"><i class="glyphicon glyphicon-play"></i> Run</button>
<script type="text/javascript">
applyCodeMirror({{ fragment.id }});
</script>
<div id="result-{{ fragment.id }}" ng-model="result"></div>
</div>
I also have a another question is it possible to use angularjs expression to generate JavaScript code like I have done above.
A few things (the code above is concerning)
Use ng-model to bind $scope variables to inputs - your textarea becomes: <textarea id="code-{{ fragment.id }}" class="code-input" ng-model="fragment.content"></textarea>
{{}} is used to output Angular $scope variables to the view - you would not use {{}} in JavaScript code to get Angular variables, so the <script>{{fragment.id}}</script> is invalid. If you want to use the Angular $scope variables outside Angular (not recommended, but we all have our reasons) - use angular.element(elem).scope() to get an instance of the scope (where elem is an element within the ng-controller declaration)
Angular directives (like your ng-click) do not need {{}} to get $scope variables - your ng-click becomes: ng-click="execute(fragment.id)"
I want to bind tag name to variable in AngularJs. Direct way doesn't work:
<div ng-app ng-init="list=['pre', 'div', 'em']">
Check the list: {{list}}
<div data-ng-repeat="item in list">
{{item}}: <{{item}}>content</{{item}}>
</div>
</div>
How to do it right?
You're going to want to make a Directive and use the $compile service module.
Angular template system works on DOM tree, not on strings, so template must be valid HTML and usage of {{}} for tagname is impossible. We can write own directive for it (see Max answer) or if here is small set of options it can be more easy to use ng-include and set of templates for options.