Passing data to nested component with ngFor - html

I have a parent component (ParentComponent) whose html has several child components (ChildComponent). The number of child components displayed depends on the length of an array using an ngFor. I'm trying to figure out how to set a property on each of the child components in the template.
Here is my ParentComponent html:
<div>
<p-panel header="Parent Component Manager">
<p-panel header="ChildComponents">
<childComponent *ngFor="let cComponent of cComponentsArray" [name]="childComponentTitle">
</childComponent>
</p-panel>
</p-panel>
</div>
My ParentComponent.component.ts file contains (I want to stop using this and use string values from cComponentsArray:
childComponentTitle: string = "This text is passed to child."
My ChildComponent html:
<p-accordion [multiple]="true">
<p-accordionTab header="ChildComponent name: {{name}}">
<h4>Random text</h4>
<h4>Random text</h4>
</p-accordionTab>
</p-accordion>
My ChildComponent.component.ts file contains:
#Input() name: string;
With the current code above this will set EACH of the child components "name" property to the same thing. I would like to set it according to a string value contained within cComponent of cComponentsArray.
I've tried using in ParentComponent.html
[name] = {{cComponent.name}}
without any luck. Could someone explain to me what I'm doing wrong and how to properly implement this?

[] and {{}} can never be used together.
Just use
[name]="cComponent.name"

Related

How to pass a function with a parameter to an inner component in Angular

I have a child component that requires data from a parent component in order to call its function. How do I put this in code?
parent.html
<child-component>getDailyForeCast(element.symbol)</child-component>
child.html
<canvas id="canvas"> {{ getDailyForeCast(symbol) }}</canvas>
ChildComponent
getDailyForeCast(symbol){
...}
I think you do not need to pass the function to the child component.
You only want to pass your element.symbol to the child component, you can do this using #Input in your child.
<child-component [symbol]="element.symbol"> </child-component>
In your child component you can define the variable in .ts file:
#Input('symbol') symbol;
and simply use this symbol in your html file using {{symbol}}
You can use the Input decorator to bind a property from another component. Docs: https://angular.io/api/core/Input
Now you can use the symbol in your child component and do whatever with it.
parent.component.html
<child-component [symbol]='element.symbol'></child-component>
child.component.ts
#Input() symbol;
getDailyForeCast(){
// use this.symbol and return some output
...}
child.component.html
<canvas id="canvas"> {{ getDailyForeCast() }}</canvas>
PS: If multiple components are involved, you may consider using a service.

Angular: Send element from HTML

Is there a way to send through HTML the element as an attribute? For example:
In HTML:
<ul>
<li *ngFor="let item of items">
<my-component [ngClass]="{'whatever': checkElement(my-component)}">
</li>
</ul>
In ts file:
checkElement(el): boolean {
// dummy code
const size = el.getBoundingClientRect();
return size.bottom > 100;
}
I know there are several ways to get that element, but this will be used in a huge loop and I would like to prevent searching the element in the DOM (if possible).
Thanks!
You can you a template reference variable to refer to my-component like so:
<my-component [ngClass]="{'whatever': checkElement(myComponentVariableName)}" #myComponentVariableName>
and pass that as an argument to the method checkElement(). Note that here the type of myComponentVariableName will be HTMLInputElement.
Another way to access that from the .ts file would be to use #ViewChild() like so:
#ViewChild('myComponentVariableName') myComponentVariable: ElementRef;`.
Then you can use this.myComponentVariable anywhere inside the component.
If you are worried about having multiple my-components as it is inside an *ngFor, you can convert the #ViewChild statement to make it a list like so:
#ViewChildren(my-component) myComponentList: QueryList<my-component>;
Also, track the *ngFor by index, and send the index along with the template reference variable like so:
<li *ngFor="let item of items; let i=index;">
<my-component [ngClass]="{'whatever': checkElement(myComponentVariableName, i)}" #myComponentVariableName>
</li>
Inside the checkElement() method, access the particular my-component like myComponentList[i].
Sure, very easy :
export class ParentComponent {
this = this;
}
<my-element [parentElement]="this">
now in your second component
#Input('parentElement') parentElement: ParentComponent;

Working with custom components for input generates "No value accessor for form control with path X->0->Y"

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?

Uses of the hash symbol (#) in Angular? [duplicate]

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.

angular2 custom directives inputs syntax

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.