What makes an Angular.JS directive require curly braces around it? - javascript

I've been doing a codeschool tutorial on Angular.JS and one section in particular confused me. Here's the code snippet I'm referring to:
<section class="tab" ng-controller="TabController as tabs">
<ul class="nav nav-pills">
<li ng-class="{active:tabs.isSet(1)}">
<a href ng-click="tabs.setTab(1)">Description</a></li>
<li ng-class="{active:tabs.isSet(2)}">
<a href ng-click="tabs.setTab(2)">Specs</a></li>
<li ng-class="{active:tabs.isSet(3)}">
<a href ng-click="tabs.setTab(3)">Reviews</a></li>
</ul>
</section>
This line in particular needs curly braces in the directive to be correct by CodeSchool standards:
<li ng-class="{active:tabs.isSet(1)}">
My question is, how come this other line's directive comes through correctly without the need of curly braces:
<a href ng-click="tabs.setTab(1)">Description</a></li>
To me, it would seem that since I'm accessing the function tabs.setTab() I would need to wrap it in the same way as before. Can anyone explain why this is the case?
Here's the TabController JS code by the way for reference:
app.controller('TabController', function(){
this.tab = 1;
this.setTab = function(newValue){
this.tab = newValue;
};
this.isSet = function(tabName){
return this.tab === tabName;
};
});

First of all, you should understand that these conventions are completely arbitrary. They were decided by the angular authors. They could have easily used some other character, like an exclamation point for example...
ng-class="!active: true, otherClass: false!"
These values are not code. They are just strings (plain text). There is some javascript in angular that parses these strings and makes sense of them. It loops over every character and looks out for certain key characters, like curly braces.
That being said, there is a reason they chose the characters they did. They are trying to mimic javascript in html. The values passed to ng-click represent a function call in javascript. Not coincidentally, this is how you call a function in javascript...
tabs.setTab(1)
The value passed to ng-class, however, is not mimicking a function call. It is mimicking an object. This is how you declare an object in javascript...
{ active: tabs.isSet(1) }
This represents an object with a key of active and a value of tabs.isSet(1), which evaluates to a boolean telling angular whether that class should be applied

It depends on the directive you're using. If you're just trying to set a boolean, a function call or a single boolean controller parameter might suffice. Other directives might be more complicated and have different parameter requirements. You'll find out when you start making your own directives.
In the case of ngClass, it is because you can set multiple classes at once, conditionally. There are several ways of doing this, but the syntax is fairly clear (documentation).
This is an example from the docs:
<p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
Here's the related plunkr: https://plnkr.co/edit/?p=preview

Related

indexOf for angular typescript

I am trying to write a similar true/false statement, Similar to how the .selected() method would work in Angular typescript.
The idea is to show an image if the calculation or *ngIf statement evaluates to True. The code is written in the app.html side
Code:
<img src="/project/x.png" *ngIf="selectedimage.indexOf(v) !== -1"><a href="{{i['link']}}" target="blank" (click)="update_viewed(z)">
There is a *ngFor statement right before this code, with ;let z = index at the end. The overall code creates a # of rows dynamically depending on how many elements exist in an array. (code not provided for this here.) Then, if the user clicks on the href link, the index value is passed into a method, which pushes it to an array. in console.log(this.selectedimage) I can see the values being added in each time I click the href reference. Not sure why the ngIf logic isn't working.
All help much appreciated.
You cannot use indexOf with *ngIf on the template, I would recommend you to create a function that returns true/false based on the logic. Use indexOf within the function and call it with *ngIf

How to use Angular structural directive with multiple inputs

I want to implement something similar with angular-permisssion. And with requirement to control the element's existance, I need to use angular structural directive.
At the beginning, i think such syntax would work:
<h2 *permissionIf [permissionIfExcept]="'Read'">Except</h2>
However, it doesn't work that way.
Moreover, the offical guide only teach you how to write custom structural directive with single input.
With multi-inputs, some third-party tutorials involve a bit. But that's using the angular template micro-syntax to achieve data binding. Then one problem occurs:
template syntax doesn't support pure key-value inputs:
<h2 *permissionIf="except: map.except;only: 'test'">Except</h2>
It expands into this(which is illegal):
<h2 template="permissionIf except: map.except;only: 'test'">Except</h2>
A stupid temporary solution is add a useless variable declaration.
<h2 *permissionIf="let i;except: map.except;only: 'test'">Except</h2>
Another inconvenient way is to use template element to wrap the code.
<template permissionIf [permissionIfExcept]="'Read'">
<h2>Except</h2>
</template>
The above all are not accepetable enough. But I can't find a bette way to resolve it.
Hope some guys can give some suggestion:).
The input names need all to be prefixed with the selector of the directive, followed by the input name capitalized (i.e. permissionIfExcept). Example:
#Directive({
selector: '[permissionIf]'
})
export class PermissionIfDirective implements AfterContentInit {
private _permissionIf:string[];
#Input()
set permissionIf(value: string[]) {
this._permissionIf=value;
console.log('permissionIf: ', value);
}
private _except:string;
#Input()
set permissionIfExcept(value: string) {
this._except = value;
console.log('except: ', value);
}
}
To use them with the '*' syntax:
<div *permissionIf="permissions;except:'Read'"></div>
Note here you're using the name following the prefix uncapitalized (i.e. except). Also note the : in the assignment.
The explicit syntax (using template) would look like this:
<template [permissionIf]="permissions" [permissionIfExcept]="'Read'">
</div></div>
</template>
but with <ng-container> it could look like
<ng-container *permissionIf="permissions;except:'Read'">
<div></div>
</ng-container>
Plunker example
See also the source of NgFor as an example.
#Günter Zöchbauer answer is almost correct.
Actually right now to make his answer working you need to explicitly rename the secondary #Input name.
So it should be:
#Input("permissionIfExcept")
set permissionIfExcept(value: string) {
this._except = value;
console.log('except: ', value);
}
Then one problem occurs: template syntax doesn't support pure key-value inputs:
True
A stupid temporary solution is add a useless variable declaration.
I think you are using this in a way it was not meant to be.
From the docs:
The microsyntax parser title-cases all directives and prefixes them with the directive's attribute name, such as ngFor. For example, the ngFor input properties, of and trackBy, become ngForOf and ngForTrackBy, respectively. That's how the directive learns that the list is heroes and the track-by function is trackById.
https://angular.io/guide/structural-directives#microsyntax-examples
Bottom line is in the context of your question, the microsyntax accepts "expression", followed by optional "keyed expression"s and I'm afraid those are your only options.
One could of course pass an object as the first expression—similar to ngIf—, the difference being you can teach your directive how to evaluate the expression:
*permissionIf="{ only: 'whatever', except: ['things', 'stuff'] }"

meaning of :: in angular formly

i'm trying to learn how to work with angular firmly and i'm having trouble understanding some of the syntax used in the guides and examples on the official website.
when defining a button form control i saw this template:
<div><button type="{{::to.type}}" class="btn btn-{{::to.btnType}}" ng-click="onClick($event)">{{to.text}}</button></div>
my question is: what is the meaning of "::" before the "to.type" and "to.btnType"?
how is it being used?
how is that different from defining it like this:
<a ng-class="{'btn-primary': to.isPrimary, active: to.isActive}" class="btn, btn-default"/>
It is a one-time binding expression, it stops the proliferation of watchers which can often cause performance issues.
Here is some useful reading: http://blog.thoughtram.io/angularjs/2014/10/14/exploring-angular-1.3-one-time-bindings.html
This is the one-time binding expression.
In your case, when to.type will have a value set, it will be updated in the HTML template. Then, if the value to.type changes again, the HTML template won't be updated with the new value.
More information can be found on AngularJS website at https://docs.angularjs.org/guide/expression#one-time-binding.

How to get object value in Angular when brackets conflict?

I have a weird situation: due to our Django backend, we changed our frontend Angular braces to brackets, but now I can't access an object value dynamically, for example:
<div>[[ house.basement ]]</div>
<div>[[ house.attic ]]</div>
The above works, but below would not:
<div>[[ house[floor] ]]</div>
Is there a straight-forward solution here? I can use a function to look for the floor, but it'd be much less efficient.
Here is my simplified controller:
var MyCtrl = function($scope) {
$scope.floor = 'basement';
$scope.house = {
'basement': 'boo';
'attic': 'yay';
};
};
There's a radio button that controls floor model, but I don't think that's too relevant here.
EDIT
So based on comments, I was not being clear, I want to access the value of $scope.house based on how $scope.floor changes. This works perfectly fine in normal angular:
<div>{{ house[floor] }}</div>
And it would display 'boo'. The problem is because we changed braces to brackets, the interpreter breaks down and we're not sure how to escape properly.
Use ng-bind.
<div ng-bind='house[floor]'></div>
http://plnkr.co/edit/diL34NCmoUPcd3AZZ3jb?p=info

Can I avoid the object variable name in ng-repeat loop?

When defining an ng-repeat directive to iterate over an array, the syntax specifies ng-repeat="friend in friends", and then within the template you use the interoplation operator like so {{friend.name}}.
Is it possible to have the properties assigned to the current item scope, rather than a variable in it? So I could call just {{name}} instead of {{friend.name}}?
The reason being is that my directive is being used in the scope of two different templates - for example I might have a directive "userActions" that is used in both a repeater, and also inside an unrelated template where {{friend.name}} doesn't make sense. I would like to avoid artificially manufacturing the friend object where it doesn't have a semantic meaning.
My use case is something like this:
I have a grid that renders blocks of various types. Some psuedo code:
<div ng-repeat="block in blocks">
< cat block />
< friend block >
<userActions directive />
</ friend block >
< guitar block />
.... more blocks
</div>
I also have a friend page, that contains the exact same user actions:
..fragment of friend page..
<element ng-control="friend">
<userActions directive />
</element>
Now, if I want to user a property of the block inside the repeater, the syntax is {{block.name}}. So the template for userActions contains this.
However, once I use this template in the friend page, I must create {{block.name}} inside the scope of the friend controller. This does not make sense though, because the block only exists in the context of the block grid. I shouldn't have to create this block.
What I want to be able to do, is just to call {{name}} from within the userActions directive template, since both the block scope and the controller contain it. I don't want to create a block object, then artificially set block.name in each scope where I want to use the userActions directive.
Here's a jsFiddle to illustrate the cause
I've decided to combine the informative answers of Mathew Berg and ganaraj with my newfound knowledge to create a helpful answer to this.
The short answer is You really don't want to do that.
The longer answer is this:
When using ng-repeat="block in blocks" , a new scope is created for each block element, and the properties of every block object are create in scope.block of each block. This is a good thing, because this means all properties can be accessed by reference, updated or $watched.
If ng-repeat wouldn't have done that, and all properties would just be slapped unto the block's scope, then all primitives in block (strings, ints, etc) would just be copied from the block object to the block scope object. A change in one will not reflect on the other, and that's bad. More info on that here.
Ok so now that we've decided that's a good thing rather than a bad thing, how do we overcome the semantic issue? I've decided to use the friendData object container as the object on the scope of my directive, and so the directive expects the friend-data attribute to hold the relevant properties
angular.module('myApp',[])
.directive("lookActions", function(){
return {
restrict: 'E',
template: "<input value='Kill -{{ friendData.name }}-' type='button'>",
scope : {
friendData : '='
}
}
});
This way I can assign this object regardless of which context I'm calling my directive template.
Given these controller contexts:
function gridCtrl($scope) {
$scope.blocks = [{ type: "cat", name: "mitzi"},{ type: "friend", name: "dave"},{ type: "guitar", name: "parker"}];
}
function friendCtrl($scope) {
$scope.data={
name: "dave"
}
}
How to call the directive -
Within an ng-repeat:
<div class="block" ng-repeat="block in blocks" >
<look-actions friend-data="block" />
</div>
Or in a difference context:
<div ng-controller="friendCtrl">
<look-actions friend-data="data" />
</div>
Here's the solution Fiddle
Thanks for all the help!
It all depends on how you structure your directive. It's hard to tell without a fiddle/plunkr what your code looks like so I'm taking a stab in the dark here. Right now I think what you're trying to say is that in the context of where you're using your directive friend.name does not make sense. Perhaps something more generic like person.name might be more appropriate. In that case you can do the following so that you pass in to the directive what you want the person to be associated with:
Html
<div data-ng-repeat="friend in friends">
{{ friend.name }}
<div class="myDirective" data-person="friend"></div>
</div>
javascript
.directive("myDirective", function(){
return {
restrict: 'C',
scope: {
person: "=person"
},
template: "<div>{{ person.name }}</div>"
}
});
jsfiddle: http://jsfiddle.net/5aVLf/1/

Categories

Resources