I am using an @ViewChild reference in my component:
@ViewChild('tableCellWidget', { static: false }) tableCellWidget: ElementRef<HTMLInputElement>;
And I am attempting to access the "value" of the element in which I have attached the tableCellWidget
viewChild reference to in order to dynamically set the [innerHTML]
of the trailing span element to the current value of the form element loaded via <select-widget-widget>
template below (this is inside a loop of a dynamically generated mat-table:
<td mat-cell *matCellDef="let data; let rowIndex = index">
<ng-container *ngIf="column !== ACTIONS_COLUMN_NAME">
<div *ngIf="layout[i] && layout[i].widget">
<select-widget-widget
#tableCellWidget
[class]="dragDropEnhancedMode && childTemplateModel ? getActiveItemClass(childTemplateModel[i]) : ''"
(change)="onCellChange($event, rowIndex, column)"
[logic]="layout[i]?.options?.logic || {}"
[rowIndex]="rowIndex + paginationRowOffset"
[dataIndex]="[].concat(rowIndex + paginationRowOffset)"
[layoutIndex]="(layoutIndex || []).concat(i)"
[layoutNode]="layout[i]">
</select-widget-widget>
<span *ngIf="tableConfig?.columnTypes[column] === 'default'" [innerHTML]="getPlainText()"></span>
</div>
</ng-container>
</td>
Here is the code for the innerHTML call:
getPlainText() {
const myValue = this.tableCellWidget?.nativeElement?.value || 'no value';
const myValue2 = this.tableCellWidget?.nativeElement;
console.log('tableCellWidget value', myValue); // UNDEFINED
console.log('tableCellWidget nativeElement', myValue2); // UNDEFINED
console.log('this.getPlainTextHasValue', this.getPlainTextHasValue); // FALSE
return myValue;
}
get getPlainTextHasValue(): boolean {
// returns false;
return hasValue(this.tableCellWidget?.nativeElement?.value);
}
The logs are all returning undefined
and false
as seen in the code. Here is an example of the actual DOM html I'm working with and the element I am trying to use to set the innerHTML on the span from:
<div _ngcontent-sen-c24="" class="ng-star-inserted">
<!--bindings={
"ng-reflect-ng-if": "false"
}-->
<select-widget-widget _ngcontent-sen-c24="" ng-reflect-layout-node="[object Object]" ng-reflect-layout-index="0,1" ng-reflect-data-index="0" ng-reflect-row-index="0" ng-reflect-logic="[object Object]" class="">
<!---->
<hidden-widget _nghost-sen-c37="" class="ng-star-inserted">
<input _ngcontent-sen-c37="" type="hidden" value="Rob Bischoff-4" ng-reflect-form="[object Object]" id="control1660760198229_1660760203705" name="accountName" class="ng-untouched ng-dirty ng-valid"><!--bindings={
"ng-reflect-ng-if": "false"
}-->
</hidden-widget>
</select-widget-widget>
</div>
Any help much appreciated. There's obviously something missing from my approach.
Update 1: I have another viewChild element in my component. It appears that when I compare the console.logs for that element and my element, I can see that they are much different. It appears that my viewchild is referencing the angular template vs the underlying native element it represents? From the pic below you can see the log output for this.filterRef vs this.tableCellWidget. With this its obvious to me why its undefined but not obvious how I can obtain the reference to the underlying element instead.
Update2: Based on Chris answer below, here is the contents of select-widget-widget
:
import { ChangeDetectionStrategy } from '@angular/core';
import { Component } from '@angular/core';
import { ComponentFactoryResolver } from '@angular/core';
import { ComponentRef } from '@angular/core';
import { Input } from '@angular/core';
import { OnChanges } from '@angular/core';
import { OnInit } from '@angular/core';
import { ViewChild } from '@angular/core';
import { ViewContainerRef } from '@angular/core';
@Component({
// tslint:disable-next-line:component-selector
selector: 'select-widget-widget',
template: `
<ng-container #widgetContainer></ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SelectWidgetComponent implements OnChanges, OnInit {
newComponent: ComponentRef<any> = null;
@Input() layoutNode: any;
@Input() layoutIndex: number[];
@Input() dataIndex: number[];
@Input() rowIndex: number;
@Input() childTemplateModel: any;
@Input() flexContainerClass: string;
@ViewChild('widgetContainer', { static: true, read: ViewContainerRef }) widgetContainer: ViewContainerRef;
constructor(private componentFactory: ComponentFactoryResolver) {}
ngOnInit() {
this.updateComponent();
}
ngOnChanges() {
this.updateComponent();
}
updateComponent() {
if (!this.newComponent && (this.layoutNode || {}).widget) {
this.newComponent = this.widgetContainer.createComponent(this.componentFactory.resolveComponentFactory(this.layoutNode.widget));
}
if (this.newComponent) {
for (const input of ['layoutNode', 'layoutIndex', 'dataIndex', 'rowIndex', 'childTemplateModel', 'flexContainerClass']) {
this.newComponent.instance[input] = this[input];
}
}
}
}
And here is the contents of hidden.component.ts (which is one of many components that can be dynamically pulled via <select-widget-widget>
and is the component that creates the hidden input in my example code that I am trying to capture the value of for the row span text:
import cloneDeep from 'lodash/cloneDeep';
import has from 'lodash/has';
import { AbstractControl, FormControl } from '@angular/forms';
import { ChangeDetectionStrategy } from '@angular/core';
import { Component } from '@angular/core';
import { Input } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { OnInit } from '@angular/core';
import { ViewEncapsulation } from '@angular/core';
import { JsonSchemaFormService, SyncComponents } from '../json-schema-form.service';
import { decodeHtmlValue, isFormControlParentInFormArray, retainUndefinedNullValue, safeUnsubscribe, setValueByType } from '../shared/utility.functions';
import { hasValue, isInterpolated } from '../shared/validator.functions';
import { Subscription } from 'rxjs';
@Component({
// tslint:disable-next-line:component-selector
selector: 'hidden-widget',
template: `
<input [formControl]="formControl" [id]="'control' + layoutNode?._id + '_' + componentId" [name]="controlName" type="hidden" />
<!-- DnD - View for hidden element -->
<div *ngIf="jsf?.formOptions?.dragDropEnhancedMode || jsf?.formOptions?.debug" class="dnd-hidden-input">
<strong> <mat-icon>visibility_off</mat-icon> {{ controlName ? controlName : 'hidden' }} </strong> {{ controlValueText }}
</div>
`,
styles: [
`
.dnd-hidden-input {
padding: 12px 0;
}
.dnd-hidden-input strong .mat-icon {
position: relative;
top: 7px;
}
`
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.Emulated // Emulated | Native | None | ShadowDom,
})
export class HiddenComponent implements OnInit, OnDestroy {
componentId: string = JsonSchemaFormService.GetUniqueId();
formControl: AbstractControl;
controlName: string;
controlValueInit: any;
controlValue: any;
controlValueText: any;
options: any;
syncComponentsSubscription: Subscription;
@Input() layoutNode: any;
@Input() layoutIndex: number[];
@Input() dataIndex: number[];
@Input() rowIndex: number;
constructor(public jsf: JsonSchemaFormService) { }
ngOnInit() {
this.options = cloneDeep(this.layoutNode.options) || {};
this.jsf.initializeControl(this);
if (!hasValue(this.controlValue) && hasValue(this.options.defaultValue)) {
this.controlValue = this.options.defaultValue;
this.jsf.triggerSyncComponents();
}
this.controlValueInit = setValueByType(this.options.dataType, this.controlValue);
if (this.controlValue) {
this.controlValueText = `: ${this.controlValue}`;
}
/** TESTING - Tries to add backward compatibility for missing Admin value: `dynamicValue` */
// if (isInterpolated(this.controlValue) && !this.isDynamicValue) {
// this.options.dynamicValue = this.controlValue;
// }
this.syncComponentsSubscription = this.jsf.syncComponents.subscribe((value: SyncComponents) => {
if (!value.targets.length || value.targets.includes(this.controlName)) {
if (has(value, `data.${this.controlName}`)) {
this.controlValue = value.data[this.controlName];
}
this.syncChanges();
}
});
this.jsf.registerComponentInit({ componentId: this.componentId, name: this.controlName });
}
ngOnDestroy() {
safeUnsubscribe(this.syncComponentsSubscription);
}
updateValue(value: any) {
const typedValue = retainUndefinedNullValue(setValueByType(this.options.dataType, value));
this.jsf.updateValue(this, typedValue);
}
get isDynamicValue(): boolean {
return hasValue(this.options.dynamicValue);
}
syncChanges() {
let value: any;
/**
* NOTE - Try to maintain interpolated value. Old way relied on value from form.data, but that can be lost after changed.
* Interpolated values for Hidden inputs need to persist.
*/
/** TESTING - Tries to add backward compatibility for missing Admin value: `dynamicValue` */
// if (isInterpolated(this.controlValue) && !this.isDynamicValue) {
// this.controlValueInit = this.controlValue;
// this.options.dynamicValue = this.controlValue;
// }
if (this.isDynamicValue) {
// NEW - Interpolated value set by Admin, should always be used to set latest value from.
value = this.options.dynamicValue;
} else if (isInterpolated(this.controlValueInit)) {
// OLD - Uses `controlValueInit`, but init value can be lost when Hidden value has been changed and form is re-rendered.
value = this.controlValueInit;
} else {
// Either way, use current value if not interpolated.
value = this.controlValue;
}
const values = this.jsf.formGroup.value;
/** Check for reference to FormControl data */
if (this.jsf.hasFormControlDataVariables(value)) {
let autocompleteData = {};
let formControlInFormArray: FormControl;
/** Check if this FormControl is part of a FormArray */
if (isFormControlParentInFormArray(<FormControl>this.formControl)) {
formControlInFormArray = <FormControl>this.formControl;
}
const result = this.jsf.getAutoCompleteFormControlData(value, formControlInFormArray);
value = result.newValue;
autocompleteData = result.autocompleteData;
const keys = Object.keys(autocompleteData);
for (let j = 0; j < keys.length; j++) {
values[keys[j]] = decodeHtmlValue(autocompleteData[keys[j]]);
}
}
const parsedValue = this.jsf.parseVariables(value, values);
const typedValue = retainUndefinedNullValue(setValueByType(this.options.dataType, parsedValue));
this.controlValue = typedValue;
if (this.controlValue) {
this.controlValueText = `: ${this.controlValue}`;
}
this.updateValue(this.controlValue);
}
}