AG-GRID angular material component not working when using external html template - ag-grid

I am trying to implement an angular material select component in the AG-GRID as per the example described here
However when I click on the cell that should make the select appear, it does not appear and in the console the following error appears:
core.js:4002 ERROR TypeError: Cannot read property 'element' of undefined
at mat-select.component.ts:37
This corresponds to the following method in the MatSelectComponent:
// dont use afterGuiAttached for post gui events - hook into ngAfterViewInit instead for this
ngAfterViewInit() {
window.setTimeout(() => {
this.group.element.nativeElement.focus();
});
this.selectFavouriteVegetableBasedOnSelectedIndex();
}
This component has been copied exactly from the example given, except that I put the template and styles in their own HTML/SCSS files respectively.
#Component({
selector: 'radio-cell',
template: './mat-select.component.html'
styles: ['./mat-slect.component.scss']
})
However, when I include the HTML template within the component like in the example it works!
#Component({
selector: 'radio-cell',
template: `
<mat-card>
<div class="container" #group tabindex="0" (keydown)="onKeyDown($event)">
<mat-form-field>
<mat-select panelClass="ag-custom-component-popup" [(ngModel)]="favouriteVegetable">
<mat-option *ngFor="let vegetable of vegetables" [value]="vegetable">
{{ vegetable }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</mat-card>
`,
styles: ['./mat-slect.component.scss']
})
Is there any explaination of this behaviour or way around putting all this HTML into the component?
EDIT 1:
The component includes the 'group' template reference variable in the component like below, so should it not be available to the ngAfterViewInit() method?
#ViewChild("group", { read: ViewContainerRef, static: true })
public group;

Your error Cannot read property 'element' of undefined comes from the fact that this.group is undefined because angular tries to query it in the template but doesn't find it <div class="container"#grouptabindex="0" (keydown)="onKeyDown($event)">

Related

How do I get submitted form value in React?

Here is my React Component
import React, { Component } from 'react';
import categories from './categories.json'
import './content.css'
export default class Content extends Component {
constructor(props) {
super(props)
this.state = {
searchText: '',
categories
}
}
render() {
return(
<div className="content">
<form className="searchform" onSubmit={this.search}>
<input type="text" name="keyword" id="searchbox" placeholder="Search String"></input>
<select name="categories" id="searchcategories">
<option defaultValue="" defaultChecked>Select a category</option>
{this.state.categories.map(x =>
<option key={x.value} value={x.value}>{x.name}</option>
)}</select>
<input type="submit" value ="Search" id="searchsubmit" />
</form>
</div>
)
}
search(e) {
console.log(e.target)
e.preventDefault();
}
}
On clicking the submit button, my function search does get called. However, how do I get the submitted values?
e.target gives me the whole form DOM HTML
e.target.value is undefined
In React, you have two options for form components:
Controlled components (https://reactjs.org/docs/forms.html#controlled-components) have their value linked to component state by setting their value prop to a state variable. In this scenario, you can use your component's this.state to inspect their values.
Uncontrolled components (https://reactjs.org/docs/uncontrolled-components.html) can have references attached to their parent component when instantiated... typically like this ref={(input) => this.input = input}. When your function search is called, you can inspect the reference for the value, i.e. this.input.value.
You need to use the state variables for this.
For example sync this.state.searchText with the element whenever text changes. And then define a callback mechanism like -
onSubmit={()=>{
console.log('My searchBox's latest value is : ',this.state.searchText);
}}
Remember, in React we follow a unidirectional flow. So, we always take our data from props or states, and do not need to work on DOM traversals.

Why is a Angular Form Validated even before i press the Submit button?

I have a Reactive Angular Form. When the page Loads the form is already checked for the errors.
I am trying to use a scroll to error directive in order to scroll and focus on the error div but the form never goes there it is already validated.
import { ElementRef, HostBinding, Input } from '#angular/core';
import { Directive } from '#angular/core';
#Directive({
selector: '[scrollTo]'
})
export class ScrollDirective {
constructor(private elRef:ElementRef) {}
#HostBinding('hidden') isError:boolean = false;
#Input() set scrollTo(cond) {
console.log(cond);
this.isError = cond;
if(cond) {
this.elRef.nativeElement.scrollIntoView();
this.elRef.nativeElement.focus();
}
}
}
This is where i am checking the error but it is already checked and if i put the scrollTo outside it scrolls to at first .
<div class="row">
<div class="col-md-12">
<span *ngIf="user.get('email').touched && !user.get('email').valid && !user.get('email').pristine">
<small [scrollTo] = "user.get('email').valid">Invalid email</small>
</span>
</div>
</div>
Update
Now i am using it like this
<div class="col-md-4" [scrollTo] = "user.get('age').valid">
<md-input-container>
<input mdInput type="number" formControlName="age" placeholder="Age" validate-onblur>
</md-input-container>
</div>
The things is that on submit the form will show validation error and i want the focus and the scroll to move there ? please help ?
Move the directive implementation to the input so you can have the direct reference to it. To deactivate the firing at the start, set a property. Change that property directly in the template when form is submitted. Use DoCheck in the directive to track the input validity and fire the scrolling and focus:
HTML:
<form (submit)="start = user.get('email').valid; onSubmit(); " ....>
.....
<input [scrollTo] = "!user.get('email').valid && !start" ....>
<span [hidden] = "user.get('email').valid">
<small>Invalid email</small>
</span>
Component
start = true;
Directive:
export class ScrollDirective implements DoCheck {
constructor(private elRef:ElementRef) {}
#Input() scrollTo ;
ngDoCheck(){
if(this.scrollTo) {
this.elRef.nativeElement.scrollIntoView();
this.elRef.nativeElement.focus();
}
}
DEMO
Old answer:
You are checking user.get('email').touched in the wrapper which is false at the start, the whole span is removed and scrollTo is ignored. Move the directive to the actual input and make changes

Angular Form validation on child components

I've written a dynamic form in which there is a main part and sub parts based on a type that's selected in the main part (widget.type). Showing and hiding the sub parts is done with an ngSwitch.
HTML of the form looks like this:
<form class="widget-form cc-form" (ngSubmit)="saveChanges()" novalidate>
<div class="forms-group">
<label for="title" i18n="##title">Titel</label>
<input class="form-control" id="title" name="title" type="text" [(ngModel)]="widget.title" required />
</div>
<div class="forms-group">
<label class="checkbox-label" for="show" i18n>
<input id="show" name="show" type="checkbox" [(ngModel)]="widget.show" /> <span>Titel tonen in app</span>
</label>
</div>
<div class="forms-group">
<label for="type" i18n="##type">Type</label>
<select class="form-control" id="type" name="type" [(ngModel)]="widget.type" required>
<option value="text-widget" i18n="##Text">Tekst</option>
<option value="tasklist-widget" i18n="##Tasklists">Takenlijst</option>
<option value="image-widget" i18n="##Text">Afbeelding(en)</option>
<option value="video-widget" i18n="##Video">Youtube</option>
<option value="link-widget" i18n="##Link">Link</option>
<option value="contacts-widget" i18n="##Contacts">Contactpersonen</option>
<option value="attachment-widget" i18n="##Attachments">Bijlage(n)</option>
</select>
</div>
<ng-container [ngSwitch]="widget.type">
<text-widget *ngSwitchCase="'text-widget'" [data]="widget"></text-widget>
<tasklist-widget *ngSwitchCase="'tasklist-widget'" [data]="widget"></tasklist-widget>
<image-widget *ngSwitchCase="'image-widget'" [data]="widget"></image-widget>
<video-widget *ngSwitchCase="'video-widget'" [data]="widget"></video-widget>
<link-widget *ngSwitchCase="'link-widget'" [data]="widget"></link-widget>
<contacts-widget *ngSwitchCase="'contacts-widget'" [data]="widget"></contacts-widget>
<attachment-widget *ngSwitchCase="'attachment-widget'" [data]="widget"></attachment-widget>
</ng-container>
</form>
Every widget is it's own component.
The problem is that the form validation only checks the inputs from the main part and disregards the sub part (widget components). How can I make sure the input fields from the widgets are included in the validation?
I tried adding an isValid() method to the widget components but I couldn't get the instances of the components, probably because they are used in an ngSwitch. #ContentChild, #ContentChildren, #ViewChild etc. all returned undefined.
For future googlers,
I had a similar issue to this, albeit with fewer child components and after digging through #penleychan's aforementioned thread on the subject I found a little gem that solved this for me without the need to implement a custom directive.
import { ControlContainer, NgForm } from '#angular/forms';
#Component({
....
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
})
This works for my nested form. Just needs to be added to components,
which ones directly contains inputs
https://github.com/angular/angular/issues/9600#issuecomment-522898551
Hope i'm not too late. I recently stumbled on this issue too with template approach since reactive form did not fit what I needed to do...
The issue is something to do with ControlValueAccessor that your component need to implement. However I couldn't get that working.
See: https://github.com/angular/angular/issues/9600
Solution provided by andreev-artem works well, and I also added my solution to wrap it inside ngModelGroup instead of in the form's root object controls property.
For your case you're not using ngModelGroup you could just have this directive
#Directive({
selector: '[provide-parent-form]',
providers: [
{
provide: ControlContainer,
useFactory: function (form: NgForm) {
return form;
},
deps: [NgForm]
}
]
})
export class ProvideParentForm {}
Usage: In your component at the root element before you have [(ngModel)] add the directive. Example:
<div provide-parent-form>
<input name="myInput" [(ngModel)]="myInput">
</div>
Now if you output your form object in your console or whatever you can see your component's controls under controls property of your form's object.
Decided to have an isValid method on the child component which indicates if the widget is filled out correctly. The form can only be saved when the form and widget component are both valid.
All widget components implement an IWidgetComponent interface which requires a changed EventEmitter property and an isValid method. One of the child widget components looks like this.
#Component({
selector: 'video-widget',
templateUrl: './video.component.html',
styleUrls: ['./video.component.css'],
providers: [YouTubeIdExistsValidator]
})
export class VideoComponent implements OnInit, OnDestroy, IWidgetComponent {
#Input("data")
widget: IWidget;
#Output("change")
changed = new EventEmitter<any>();
video: any;
modelChanged: Subject<string> = new Subject<string>();
public isValid(): boolean {
return this.widget.youtube_id && this.widget.youtube_id !== "" && this.video ? true : false;
}
constructor(private youtubeService: YoutubeService) {
this.modelChanged
.debounceTime(500) // wait 500ms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(youtube_id => this.getYoutubeVideo(youtube_id));
}
ngOnDestroy(): void {
this.widget.youtube_id = "";
}
getYoutubeVideo(youtube_id: string) {
this.youtubeService
.getById(youtube_id)
.subscribe((video) => {
this.video = video;
// Indicate that video was changed
this.changed.emit();
}, (error) => {
this.video = null;
});
}
youtubeIdChanged(youtube_id: string) {
this.modelChanged.next(youtube_id);
}
ngOnInit() { }
}
The parent html looks like this:
<form #widgetForm novalidate>
...
<ng-container [ngSwitch]="widget.type">
<text-widget #ref *ngSwitchCase="'text-widget'" [data]="widget" (change)="saveChanges()"></text-widget>
<tasklist-widget #ref *ngSwitchCase="'tasklist-widget'" [data]="widget" (change)="saveChanges()"></tasklist-widget>
<image-widget #ref *ngSwitchCase="'image-widget'" [data]="widget" (change)="saveChanges()"></image-widget>
<video-widget #ref *ngSwitchCase="'video-widget'" [data]="widget" (change)="saveChanges()"></video-widget>
<link-widget #ref *ngSwitchCase="'link-widget'" [data]="widget" (change)="saveChanges()"></link-widget>
<contacts-widget #ref *ngSwitchCase="'contacts-widget'" [data]="widget" (change)="saveChanges()"></contacts-widget>
<attachment-widget #ref *ngSwitchCase="'attachment-widget'" [data]="widget" (change)="saveChanges()"></attachment-widget>
</ng-container>
...
</form>
Each time the widget changes an event is emitted (this.changed.emit()) which triggers the save form method in the parent component. In this method I check if the form and widget are valid, if it is then the data may be saved.
saveChanges() {
if (this.ref && this.ref.isValid() && this.widgetForm.valid) {
// save form
this.toastr.success("Saved!");
}
else {
this.toastr.warning("Form not saved!");
}
}

Angular2 Forms - ngControl

I'm trying to use ngControl to apply error classes based on user's input.
Somehow, I can't make it to work. I see that appropriate classes are set (line ng-invalid), but when trying to use name.valid (where name is my ngControl) it doesn't work.
html:
<div ngClass="{alert: name.invalid}">
<label for="name">Name</label>
<input ngControl="name" #name id="name" [(ngModel)]="user.name"/>
</div>
</div>
js
export class App {
userForm: any;
user: any;
constructor(
private _formBuilder: FormBuilder) {
this.user = {name: 'Ben'};
this.userForm = this._formBuilder.group({
'name': ['', Validators.required]
});
}
}
I saw on angular.io examples that they do use it like this (just for other cases, like show/hide divs)?
Here's the simple plunker: http://plnkr.co/edit/BKx4yplIOu44tk7Mfolc?p=preview
When input field is empty, I would expect that upper div gets alert class, but that doesn't happen.
In fact there are three things to change in your template:
ngClass should be [ngClass]. Otherwise the value is considered as a string and not as an expression.
#name should be #name="ngForm". Otherwise you reference the DOM element and not the control.
there is no invalid property on controls in Angular2 but only a valid one.
Here is the refactored code:
<div [ngClass]="{alert: !name.valid}">
<label for="name">Name</label>
<input ngControl="name" #name="ngForm"
required id="name" [(ngModel)]="user.name"/>
</div>
Here is the plunkr: http://plnkr.co/edit/OJfb9VDqlrRH4oHXQJyg?p=preview.
Note that you can't leverage of FormBuilder with ngControl since the latter allows you to define inline form. With FormBuilder you must use ngFormControl instead.
Here is a sample:
<div [ngClass]="{alert: !userForm.controls.name.valid}">
<label for="name">Name</label>
<input [ngFormControl]="userForm.controls.name"
id="name" [(ngModel)]="user.name"/>
</div>
See this article for more details:
http://restlet.com/blog/2016/02/11/implementing-angular2-forms-beyond-basics-part-1/

Initializing Dynamic Added Dropdown in Kendo-ui Mobile

Connected with the question below
Kendo-UI - Creating a Dynamic Form via JSON
I am creating dynamic forms. Normally when I create static forms, I write something like
$("#Field1").kendoDropDownList();
and the dropdown list is handled by kendo-ui css files. How can I add this in a dynamically added form field?
I tried to solve it in my template definition but didn't work
<script id="fieldsTemplate" type="text/x-kendo-template">
<li>
<label data-bind="attr: { for: name}, text: label"></label>
# if (get("fieldtype") == "input") {#
<input data-bind="value: value, attr: { type: type, name: name}" # if (get("required")) {# required #} # />
#}else{#
<select id="name" data-bind="source: options, value: value, attr: { type: type, name: name}" data-text-field="option_value" data-value-field="option_id" />
#}#
#$('#'+get("name")).kendoDropDownList();#
</li>
</script>
I've done something similar using data attribute initialization. You put "data-role='dropdownlist'" in your select tag. Then after the call to load the template you use
kendo.init($('#YourContainerIdHere"));
Kendo's brief documentation is here: http://docs.telerik.com/kendo-ui/getting-started/data-attribute-initialization