I create a custom directive and set the selector value to be "[unless-directive]".
The directive get a Boolean and use it to change the view as so:
import {Directive, TemplateRef, ViewContainerRef} from 'angular2/core';
#Directive({
selector: '[unless-directive]',
inputs: ['givenBoolean : myDirectiveFunction']
})
export class UnlessDirective {
private _templateRef: TemplateRef;
private _viewContainerRef: ViewContainerRef;
constructor(_templateRef: TemplateRef, _viewContainerRef: ViewContainerRef) {
this._templateRef = _templateRef;
this._viewContainerRef = _viewContainerRef;
}
set myDirectiveFunction(condition: boolean) {
!condition ? this._viewContainerRef.createEmbeddedView(this._templateRef)
: this._viewContainerRef.clear();
}
}
In my template I tried to pass the condition like so:
<div name="customDirective">
<h2>Custom Directive</h2>
<div>
Enter true or false:
<br/>
<input type="text" #condition (keyup)="0"/>
<div *unless-directive [givenBoolean]="condition.value != 'false'">
Only shown if 'false' wad enterded!
</div>
</div>
</div>
When I running the code I get this error:
EXCEPTION: Template parse errors: Can't bind to 'givenBoolean' since
it isn't a known native property (" ... Only shown if 'false' wad enterded!"): StructualDirectivesComponent#47:39
I guess my syntax is wrong, but I can't find where or why?
I looked it up on Angular2 Docs, but the example use the same name for the input and the selector, the thing that I'm trying to avoid.
Can anyone know a better way or can find my syntax problem?
Thanks.
The * prefix syntax is only a syntatic sugar. It expands the directive declaration.
The * prefix syntax is a convenient way to skip the <template> wrapper tags and focus directly on the HTML element to repeat or include. Angular sees the * and expands the HTML into the <template> tags for us.
This is documented in * and <template> and Directive decorator/Lifecycle hooks.
So, in your case, the [givenBoolean] property is not expected to be in the directive. In other words, this:
<div *unless-directive [givenBoolean]="condition.value != 'false'">
Only shown if 'false' wad enterded!
</div>
Becomes, actually:
<template [unless-directive]="">
<div [givenBoolean]="condition.value != 'false'">
Only shown if 'false' wad enterded!
</div>
</template>
And since givenBoolean is not a property in the component (not the directive), the error appears.
So if you want custom behavior, I suggest you experiment using the expanded version and only after it works you go to the * syntax, it will be simpler to reason about.
Related
I am able to save the filters locally using stateStorage="local" stateKey="myKey". So when the user leaves the component and returns, the data is still filtered based upon whatever filters they have set.
The problem is, the user has no idea what they are filtering on anymore, as the filter headers do not show them anything anymore, just the default label. I can access the filters via local storage, and delete them using localStorage.removeItem("myKey");, but I cannot for the life of me figure out how to get this filter information to display in the filter headers. We are not using lazyLoad, as suggested in another answer. You'd think this would be built in any time a filter is saved because not knowing what you are filtering on seems like a major flaw.
For more clarity, I have attached the primeFaces documentation below. If you select 'Red' 'White' and 'Green' from the multiselect dropdown, it will display your selected filter in the header (Red, White, Green) above. I need this information to display anytime the user enters the component if they have filters saved (both with text input, and with the dropdowns).
https://www.primefaces.org/primeng/#/table/filter
I am using multi-select dropdown filters, text input, as well as calendar filters. Here is a snippet of the html, which includes examples of these three filter types:
<th *ngFor="let col of columns" [ngSwitch]="col.field">
<input *ngSwitchCase="'userID'" pInputText type="text" size="12" placeholder="contains" (input)="table.filter($event.target.value, col.field, col.filterMatchMode)" [value]="table.filters['userID'] ? table.filters['userID'].value : ''">
<div class="ui-g ui-fluid">
<p-calendar #calendar1 class="ui-fluid" *ngSwitchCase="'myDate'" [monthNavigator]="true" [showOnFocus]="false" [yearNavigator]="true" yearRange="2010:2060" [showIcon]="true"
[showOtherMonths]="false" [showButtonBar]="true" [appendTo]="attach" [style]="{'overflow': 'visible'}"
[(ngModel)]="calendar1Filter"
(ngModelChange)="table.filter($event, 'myDate', calendar1Option)"
(onSelect)="table.filter($event, 'myDate', calendar1Option)">
<p-footer>
<div class="ui-grid-row">
<div class="ui-grid-col-3"><label style="font-weight: bold; color: #337ab7">Mode:</label></div>
<div class="ui-grid-col-6"><p-dropdown [options]="calendar1Options" [style]="{'width':'60px', 'padding-top': '0px'}" [(ngModel)]="calendar1Option" (onChange)="onChangeModCalOpt(calendar1, 1)" ></p-dropdown> </div>
</div>
</p-footer>
</p-calendar>
</div>
<div class="ui-fluid">
<p-multiSelect *ngSwitchCase="'myDropdown'" appendTo="body" [options]="dropdownOptions" pInputText type="text" size="12" [style]="{'width':'100%'}" defaultLabel="All" [(ngModel)]="myDropdownFilter" (onChange)="table.filter($event.value, col.field, 'in')"></p-multiSelect>
</div>
</th>
I did this almost a year ago; it involved some tricky code, as you'll see below, because the table element was beneath an *ngIf directive. I'm not sure if your case is the same, but if it is, here's what I had to to do get it to work. In the example, I have a fooTable that has a custom filter on the status column.
foo.component.ts:
import { ChangeDetectorRef, Component, ViewChild } from "#angular/core";
#Component({
selector : 'foo',
templateUrl : 'foo.component.html'
})
export class FooComponent {
// members /////////////////////////////////////////////////////////////////////////////////////////////////////////
showFooTable: boolean = true;
statusFilter: string[] = [];
// constructor(s) //////////////////////////////////////////////////////////////////////////////////////////////////
constructor(private cd: ChangeDetectorRef) { }
// getters and setters /////////////////////////////////////////////////////////////////////////////////////////////
/**
* Due to the Angular lifecycle, we have to do some tricky things here to pull the filters from the session,
* if they are present. The workarounds done in this function are as follows:
*
* 1) Wait until the element is accessible. This is not until the *ngIf is rendered, which is the second call to
* this function. The first call is simply 'undefined', which is why we check for that and ignore it.
* 2) Check and see if the filters for this object are even part of the template. If not, just skip this step.
* 3) If we find the filters in the session, then change this object's filters model to equal it and call the change
* detection manually to prevent Angular from throwing an ExpressionChangedAfterItHasBeenCheckedError error
* #param fooTable the reference to the foo table template
*/
#ViewChild('fooTable') set fooTable(fooTable: any) {
if(fooTable != undefined) {
let filters = fooTable.filters['status'];
if (filters != undefined && filters.value != undefined) {
this.statusFilter = filters.value;
}
this.cd.detectChanges();
}
}
}
foo.component.html:
<ng-container *ngIf="showFooTable">
<div id="filters">
<p-checkbox [(ngModel)]="statusFilter" value="up" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Up
<p-checkbox [(ngModel)]="statusFilter" value="down" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Down
<p-checkbox [(ngModel)]="statusFilter" value="unknown" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Unknown
</div>
<p-table #fooTable
stateStorage="session"
stateKey="foo-table-state">
</p-table>
</ng-container>
Turbo tables filters can be access like so. table.filters['myColumn']?.value. You will need to set the input values in the header, [value]="table.filters[col.field]?.value"
...
<tr>
<th *ngFor="let col of columns" [ngSwitch]="col.field" class="ui-fluid">
<input pInputText
type="text"
(input)="table.filter($event.target.value, col.field, col.filterMatchMode)"
[value]="table.filters[col.field]?.value">
</th>
</tr>
...
https://www.primefaces.org/primeng/#/table/state
Just figured this out. What I ended up doing was attaching the default label to a model like so:
<p-multiSelect [defaultLabel]="table.filters[col.field]?.value || 'All'">
If the table has the filter value in its state, it will populate the label else default to 'All'.
I have child-component inside parent-component. The child-component has a property item-checked with type boolean.
I need to inject the child-component to the parent-component using innerHTML, but that would break the binding.
I have tried using Templatize, but not sure what's wrong. It says: Cannot read property '__templatizeOwner' of null
<div id="page"></div>
<paper-toggle-button checked="{{powerChecked}}">
<font color="white">hide/show</font>
</paper-toggle-button>
The toggle execute this code
this.$.page.innerHTML = "
<child-component name='child-component' id='map' power-checked={{powerChecked}}>
</child-component>"
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?
I am trying to learn angular material 2 and came across this #auto attribute in autocomplete.I understand auto can be replaced with any text, but why there need a # here before auto and what is there any name of this attribute?
<md-input-container>
<input mdInput placeholder="State" [mdAutocomplete]="auto" [formControl]="stateCtrl">
</md-input-container>
<md-autocomplete #auto="mdAutocomplete">
^^^^ what is name of this property
<md-option *ngFor="let state of filteredStates | async" [value]="state">
{{ state }}
</md-option>
</md-autocomplete>
It is a template reference variable that allows us to get reference to html element or something else if we declare directive on this element.
We can declare template reference variable via (1)
#var
ref-var
#Default behavior
In most cases, Angular sets the reference variable's value to the html element on which it was declared (2) .
<div #divElem></div>
<input #inputEl>
<table #tableEl></table>
<form #formEl></form>
In the preceding all template reference variables will refer to the corresponding elements.
#divElem HTMLDivElement
#inputEl HTMLInputElement
#tableEl HTMLTableElement
#formEl HTMLFormElement
#Directives can change default behavior
But a directive can change that behavior and set the value to something else, such as itself.
Angular assigns references with empty value to component (3)
If we have component like:
#Component({
selector: '[comp]',
...
})
export class SomeComponent {}
and template as:
<div comp #someComp></div>
then #someComp variable will refer to component itself (SomeComponent instance).
Angular doesn't locate directives in references with empty value (4)
If we change #Component decorator to #Directive
#Directive({
selector: '[comp]',
...
})
export class SomeDirective {}
then #someComp variable will refer to HTMLDivElement.
How we can get SomeDirective instance in this case?
Fortunately, Template reference variable can have value (5)
#var="exportAsValue"
ref-var="exportAsValue"
We can define exportAs property within #Component/#Directive decorator (6):
exportAs is a name under which the component instance is exported in a
template. Can be given a single name or a comma-delimited list of
names.
#Directive({
selector: '[comp]',
exportAs: 'someDir',
...
})
export class SomeDirective {}
and then use exportAs value as value for template reference variable within template (7):
<div comp #someComp="someDir"></div>
After that #someComp will refer to our directive.
Now let's imagine we have several directives applied to this component. And we want to get specific directive instance.exportAs property is a good choice to solve this problem.
Let's go back to your code
If you open source code of MdAutocomplete component you can see:
#Component({
...
exportAs: 'mdAutocomplete'
})
export class MdAutocomplete {
...
Since in your template you have
#auto="mdAutocomplete"
Then #auto variable will refer to instance of MdAutocomplete component. This reference is used in MdAutocompleteTrigger directive:
#Directive({
selector: 'input[mdAutocomplete], input[matAutocomplete],' +
'textarea[mdAutocomplete], textarea[matAutocomplete]',
...
})
export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
#Input('mdAutocomplete') autocomplete: MdAutocomplete;
because you're passing auto variable to input within template
<input mdInput placeholder="State" [mdAutocomplete]="auto"
We can omit value and use only variable name in this case like
<md-autocomplete #auto>
but
assignment value to value of exportAs property precisely indicates us where to get the instance.
if md-autocomplete is a directive then auto variable will refer to HTMLElement.
So prefer specifying value for template reference variable if you doubt what it will refer to.
I'm trying to include classes based on parameters of a json, so if I have the property color, the $= makes the trick to pass it as a class attribute (based on the polymer documentation)
<div class$="{{color}}"></div>
The problem is when I'm trying to add that class along an existing set of classes, for instance:
<div class$="avatar {{color}}"></div>
In that case $= doesn't do the trick. Is any way to accomplish this or each time that I add a class conditionally I have to include the rest of the styles through css selectors instead classes? I know in this example maybe the color could just simple go in the style attribute, it is purely an example to illustrate the problem.
Please, note that this is an issue only in Polymer 1.0.
As of Polymer 1.0, string interpolation is not yet supported (it will be soon as mentioned in the roadmap). However, you can also do this with computed bindings. Example
<dom-module>
<template>
<div class$="{{classColor(color)}}"></div>
</template>
</dom-module>
<script>
Polymer({
...
classColor: function(color) {
return 'avatar '+color;
}
});
<script>
Edit:
As of Polymer 1.2, you can use compound binding. So
<div class$="avatar {{color}}"></div>
now works.
Update
As of Polymer 1.2.0, you can now use Compound Bindings to
combine string literals and bindings in a single property binding or text content binding
like so:
<img src$="https://www.example.com/profiles/{{userId}}.jpg">
<span>Name: {{lastname}}, {{firstname}}</span>
and your example
<div class$="avatar {{color}}"></div>
so this is no longer an issue.
The below answer is now only relevant to versions of polymer prior to 1.2
If you are doing this a lot, until this feature becomes available which is hopefully soon you could just define the function in one place as a property of Polymer.Base which has all of it's properties inherited by all polymer elements
//TODO remove this later then polymer has better template and binding features.
// make sure to find all instances of {{join( in polymer templates to fix them
Polymer.Base.join = function() { return [].join.call(arguments, '');}
and then call it like so:
<div class$="{{join('avatar', ' ', color)}}"></div>
then when it is introduced by polymer properly, just remove that one line, and replace
{{join('avatar', color)}}
with
avatar {{color}}
I use this a lot at the moment, not just for combining classes into one, but also things like path names, joining with a '/', and just general text content, so instead I use the first argument as the glue.
Polymer.Base.join = function() {
var glue = arguments[0];
var strings = [].slice.call(arguments, 1);
return [].join.call(strings, glue);
}
or if you can use es6 features like rest arguments
Polymer.base.join = (glue, ...strings) => strings.join(glue);
for doing stuff like
<div class$="{{join(' ', 'avatar', color)}}"></div>
<img src="{{join('/', path, to, image.jpg)}}">
<span>{{join(' ', 'hi', name)}}</span>
of just the basic
Polymer.Base.join = (...args) => args.join('');
<div class$="{{join('avatar', ' ', color)}}"></div>
<template if="[[icon_img_src]]" is="dom-if">
<img class$="{{echo_class(icon_class)}}" src="[[icon_img_src]]">
</template>
<span class$="{{echo_class(icon_class, 'center-center horizontal layout letter')}}" hidden="[[icon_img_src]]">[[icon_text]]</span>
<iron-icon icon="check"></iron-icon>
</div>
</template>
<script>
Polymer({
echo_class: function(class_A, class_Z) {
return class_A + (class_Z ? " " + class_Z : "");
},