How to use Angular structural directive with multiple inputs - javascript

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'] }"

Related

Append index dynamically to attribute

I have this button element:
<button v-on:click="changeRecord(element)" v-b-modal.modal-5>Aendern</button>
it is generated dynamically inside a v-for loop.
Instead of hard coding the attribute name like above v-b-modal.modal-5 I want to concatenate it like this:
v-b-modal.modal-{{index}}
Is there a way to do this?
I'm using vue-cli 3 and bootstrap-vue.
I haven't used this framework before but looking at the second example from the docs I think something like the following should work.
<button v-on:click="changeRecord(element)" v-b-modal="`modal-${index}`">Aendern</button>
You will need to ensure that the variable index is made available when you set up the v-for
EDIT: For clarity, the above works because in VueJS the input to a directive is evaluated as an expression. The above example uses backticks string interpolation but the same can be done using pretty much any valid expression like "'modal-'+index" or based on some property on the item we are looping over "`modal-${item.id}`".
Unlike directives, class or other attributes are interpreted as plain strings unless they are bound using v-bind in which case they are treated as expressions. The example in the docs uses a simple string as an input so it's hard to tell from that particular example that it can be used in this way.
It is possible to add dynamic attributes like following
<p class="text" v-bind="options">{{ message }}</p>
Inside the computed, define the value for options
export default {
data:()=> {
return {
message: 'Hello world!',
id: 12345
}
},
computed: {
options() {
return {
[`v-b-modal.modal-${this.id}`]: "whatever"
}
}
}
}

Angular 5 Component Selector in variable returning string instead of component content

I have some data that contains the selector of a component:
{
label: 'This is a label',
componentSelector: '<app-mycomponent></app-mycomponent>'
}
In my app.component.html
Instead of doing this (for example):
<div>
<app-mycomponent></app-mycomponent>
</div>
I would like to do this:
{{data.componentSelector}}
At the moment when I try this it's returning a string instead of replacing it with the contents of the component.
How can I do this?
You cannot interpolate a component, because it is made up of typescript, html, and css. it has to compile to be displayed, if you think about it, it makes sense.
On another note, even if you could interpolate, it would be a poor Angular Pattern, and could have unexpected outcomes especially in production. Stick to the best practices.

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

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

How to use ng-translate with variables resolved from controller?

I'm using ng-tranlate for i18n.
I'd like to combine a translated label together with a variable resolved from controller binding. How can I achieve the following?
<div translate="my.lang.text">some more: {{controller.attribute}}</div>
This does not work, and ng-translate ignores any content between the divs. why?
The translate directive will replace the content of the element with the translation you pass to it.
The use case you are describing looks like a parameterized translation. If you want to keep the use of the directive, you could pass the variable through the translate-values directive:
<div translate="my.lang.text"
translate-values="{value: 'some more: ' + controller.attribute}"></div>
You have to specify that your translation is parameterized:
JSON
"my.lang.text": "This is a parameterized string {value}"
I believe the translate directive replaces all the element's content with the translation.
In this case, you probably want to use the translate filter instead.
<div>{{'my.lang.text' | translate}} some more: {{controller.attribute}}</div>
As an alternative, you could also avoid this issue by giving the translated value it's own element.
<div><span translate="my.lang.text"></span> some more: {{controller.attribute}}</div>
If the translation is always intended to have have a value appended to it, then using a parameterized translation is probably the best solution (as suggested by Michael https://stackoverflow.com/a/33419608/90305)

How do I get the current child-scope of a ngRepeated element in AngularJS?

This seems like a simple question, but I've been googling around for a while and can't seem to find it.
In my JS I have something called parseTags(book) that takes a JSON comma-separated list of tags (book.tags) and parses it into an array:
$scope.parseTags = function(book){
book.tags = book.tags.split(',');
};
In my HTML I have something like this:
<div ng-repeat="book in books" ng-init="parseTags(book)">{{book.title}}</div>
Is there a way just to get the child scope from within the $scope.parseTags function? Instead of passing in book each time?
Something like:
$scope.parseTags = function($childScope){
$childScope.tags = $childScope.tags.split(',');
}
<div ng-repeat="book in books" ng-init="parseTags()">{{book.title}}</div>
parseTags function is executed in context of the current child scope. So parseTags can also be written as:
$scope.parseTags = function() {
this.book.tags = this.book.tags.split(',');
};
Demo: http://plnkr.co/edit/DUkDVOMjjj0khh5KCYo7?p=preview
you should only use ng-init for special cases when you need to use a property of the ng-repeat, I think you will better doing this kind of functionality in the controller. Unless there is a really specific reason you can't do that. I haven't seen your use of tags in the html, but looks like the kind of functionality a filter would do.
From angularjs docs:
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
source: angularjs docs

Categories

Resources