How to combine NgForm and [(ngModel)] with MaterialSelectComponent and MaterialDropdownSelectComponent? - dart

I know that this question similar to 1 and 2, but let me explain it with detail sample code.
I combine code from angular_components material_select_example 3 and 4, using component material-select and material-dropdown-select. (for clarity, I put the full code at the bottom)
I want to process the form result with NgForm,
for example, now I use a method logConsole(NgForm form).
what I have tried with material-select,
<material-select focusList [selection]="targetLanguageSelection"
[(ngModel)]="selectionValue" #langVal="ngForm" (ngControl)="langVal"
class="bordered-list">
the logConsole() print empty Map (Map(0)). I still can get the value from targetLanguageSelection.selectedValues.
from above code, if I set ngControl="langVal", I got an exception:
No value accessor for (langVal) or you may be missing formDirectives in your directives list.
then I changed to material-dropdown-select, I can't use ngControl because the buttonText will not show the selected value.
<material-dropdown-select
[buttonText]="selectionValue == null ? 'Select Language' : selectionValue.uiDisplayName"
[(ngModel)]="selectionValue" #langVal="ngForm"
[options]="languageListOptions" displayNameRenderer>
but it still failed, the logConsole() still print empty Map (Map(0)). I still can get the value from selectionValue.
so how to get the value with NgForm?
EDIT:
I still looking at how I can get NgForm value from MaterialSelectComponent,
I already can get NgForm value with MaterialDropdownSelectComponent with ngControl not (ngControl), like below:
<material-dropdown-select
[buttonText]="selectionValue == null ? 'Select Language' : selectionValue.uiDisplayName"
[(ngModel)]="selectionValue" #langVal="ngForm" ngControl="langVal"
[options]="languageListOptions" displayNameRenderer>
</material-dropdown-select>
--
here is the material_select_demo_component.dart.
#Component(
selector: 'material-select-demo',
providers: popupBindings,
directives: [
materialInputDirectives,
formDirectives,
displayNameRendererDirective,
FocusListDirective,
FocusItemDirective,
MaterialSelectComponent,
MaterialSelectItemComponent,
MaterialButtonComponent,
MaterialDropdownSelectComponent,
DropdownSelectValueAccessor,
NgFor,
],
templateUrl: 'material_select_demo_component.html',
styleUrls: ['material_select_demo_component.css'],
)
class MaterialSelectDemoComponent {
final SelectionModel<Language> targetLanguageSelection =
SelectionModel.single();
Language selectionValue;
ExampleSelectionOptions languageListOptions =
ExampleSelectionOptions(_languagesList);
List<Language> get languagesList => _languagesList;
void logConsole(NgForm form) {
html.window.console.log(form.value);
html.window.console.log(selectionValue);
html.window.console
.log(targetLanguageSelection.selectedValues.toList().toString());
}
static const List<Language> _languagesList = <Language>[
Language('en-US', 'US English'),
Language('en-UK', 'UK English'),
Language('fr-CA', 'Canadian English'),
Language('zh-CN', 'Chichewa'),
Language('zh-TW', 'Chinese'),
Language('ny', 'Chinese (Simplified)'),
Language('zh', 'Chinese (Traditional)'),
];
}
class Language implements HasUIDisplayName {
final String code;
final String label;
const Language(this.code, this.label);
String toString() => '${label} (${code})';
#override
String get uiDisplayName => label;
}
class ExampleSelectionOptions extends StringSelectionOptions<Language>
implements Selectable<Language> {
ExampleSelectionOptions(List<Language> options)
: super(options,
toFilterableString: (Language option) => option.toString());
ExampleSelectionOptions.withOptionGroups(List<OptionGroup> optionGroups)
: super.withOptionGroups(optionGroups,
toFilterableString: (Language option) => option.toString());
#override
SelectableOption getSelectable(Language item) =>
item is Language && item.code.contains('en')
? SelectableOption.Disabled
: SelectableOption.Selectable;
}
here's the material_select_demo_component.html:
<form #languageForm="ngForm">
<section>
<material-select focusList [selection]="targetLanguageSelection" class="bordered-list">
<material-select-item *ngFor="let language of languagesList"
focusItem [value]="language"
displayNameRenderer>
</material-select-item>
</material-select>
</section>
<section>
<material-dropdown-select
[buttonText]="selectionValue == null ? 'Select Language' : selectionValue.uiDisplayName"
[options]="languageListOptions" [(ngModel)]="selectionValue" displayNameRenderer>
</material-dropdown-select>
</section>
<material-button raised (click)="logConsole(languageForm)">submit</material-button>
</form>
the pubspec.yaml file
environment:
sdk: '>=2.4.0 <3.0.0'
dependencies:
angular: ^6.0.0-alpha
angular_components: ^0.14.0-alpha
angular_forms: ^2.1.3
thank you.

I would recommend to start with something very simple and using FormBuilder:
class Language {
final String code;
final String label;
const Language(this.code, this.label);
#override
String toString() => label;
}
In your component :
StringSelectionOptions<Language> languageOptions = StringSelectionOptions<Language>(_languagesList);
Language selectedLanguage;
ControlGroup myForm;
void ngOnInit() {
myForm = FormBuilder.controlGroup({
'language': FormBuilder.controlGroup([selectedLanguage]);
});
}
void onSubmit() {
print(myForm.value);
print(selectedLanguage);
}
<form [ngFormModel]="myForm" (ngSubmit)="onSubmit()">
<material-dropdown-select
buttonText="{{selectedLanguage??'Choose ...'}}"
[(ngModel)]="selectedLanguage"
ngControl="language"
[options]="languageOptions">
</material-dropdown-select>
<material-button
[disabled]="!myForm.valid"
(trigger)="onSubmit()">Submit</material-button>
</form>

Related

Set a form value using react-hook-form within React-Admin

In my React-Admin app, I'd like to leverage react-hook-form's useFormContext for various things, such as, for example, setting the default pre-selected choice in this custom input field:
...
import {
Create, SimpleForm, SelectInput
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
const MyInput = () => {
const formContext = useFormContext();
formContext.setValue('category', 'tech');
return (
<SelectInput source="category" choices={[
{ id: 'tech', name: 'Tech' },
{ id: 'people', name: 'People' },
]}
/>
);
};
...
const ItemCreate = () => {
return (
<Create>
<SimpleForm>
<MyInput />
</SimpleForm>
</Create>
);
};
...
This sets the pre-selected value of the field, just as intended. But it throws a warning: Cannot update a component ("Form") while rendering a different component ("MyInput")...
Is there some way to achieve this without getting the warning?
Note: The only reason I'm using a custom input field here is because when I put useFormContext() directly into the component that contains SimpleForm it returns null (similarly described here).
The warning is related to the fact that the entire body of the MyInput() function is executed during each render, you need to call the setValue() function inside the useEffect hook.
Got this working by moving formContext.setValue into a useEffect hook:
...
import {
Create, SimpleForm, SelectInput
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
const MyInput = () => {
const formContext = useFormContext();
// moved the setValue into a useEffect
useEffect(() => {
formContext.setValue('category', 'tech');
});
return (
<SelectInput source="category" choices={[
{ id: 'tech', name: 'Tech' },
{ id: 'people', name: 'People' },
]}
/>
);
};
...
const ItemCreate = () => {
return (
<Create>
<SimpleForm>
<MyInput />
</SimpleForm>
</Create>
);
};
...

how to retrieve form values and labels from react hook form and antd Select

I am using antd Select and react hook form via 'Controller'. I am populating the Select options from a fetched data with structure;
{
{
"id": "232342",
"term": "hello"
}
{
"id": "232342",
"term": "hello"
}
}
the Select component properly displays the term for selection. However, i want to retrieve both the 'id'and 'term' of the selected and use it to populate another json object.
getValues(" ") retrieves the 'id' only. How do i retrieve and access both the 'id' and 'term'.
Here is a portion of code:
import React from 'react'
import { useForm, Controller } from 'react-hook-form'
import { Select } from 'antd'
const { Option } = Select
export default function PatientRegistrationForm({ options }) {
const { register, handleSubmit, getValues, control, formState: { errors } } = useForm({
defaultValues: {
customer: "",
}
})
const children = []
for (const {id, pt: {term}} of options){
children.push(<Option key={id}>{term}</Option>)
}
// Define the retrieved values from the form
const retrievedID = getValues("customer")
// Use the retreived values to populate this object
const customer = {
customerId = retrievedID
customerName = "nothing happens here"
},
return (
<div className="">
<form onSubmit={handleSubmit(onSubmit)} className="">
<section>
<Controller
control={control}
name="customer"
render={({ field }) => (
<Select {...field} defaultValue=""
bordered={true}
filterOption={true}
className="form-control"
>
{ children }
</Select>
)}
/>
</section>
</form>
</div>
);
}
Thanks in advance for your help.
You have to retrieve the option manually using something like:
const retrievedID = getValues("customer")
const retrievedOption = options.find(option => option.id === retrievedID)
const customer = {
customerId: retrievedID,
customerName: retrievedOption.term
}
thank you #sgarcia.dev for your answer. I know its been a while but i want to put it out here incase it helps someone else. It turns out it had little to do with react hook form. Ant design select component has a prop 'labelInValue' which returns an object containing both the label and value.

Angular2 ControlValueAccessor

i'm trying to understand how ControlValueAccessor work precisely.
I have studied the behavior of it with two different control component:
The first is suppose to provide a primitive value: a single number.
The second provide a complex object.
So in short:
class FirstControlComponent implements ControlValueAccessor {
// ...
value:number = 10;
writeValue(value: number) {
this.value = value;
}
// ...
}
class SecondControlComponent implements ControlValueAccessor {
// ...
value:any = {};
writeValue(value: any) {
this.value = value;
}
// ...
}
The ControlValueAccessor interface only specify a 'setter': writeValue, but no 'getter'.
So when i bind a Control to SecondControlComponent, something like:
this.form = this.builder.group({
controlName: this.builder.control(this.theObject) });
and later in the template:
<second-component ngControl='controlName'> <second-component>
Everything works just fine, because writeValue is called on init with a reference to the existing theObject object, so the control modify the same instance of the object (hope i'm clear)
BUT: if i do exactly the same thing with FirstControlComponent, because the value is not passed as a reference (cause it's a primitive), and because ControlValueAccessor do not provide a 'setter' the value in my control and the value in my host component are NOT kept in sync ...
Does this mean that we HAVE to pass Object and not primitive to custom control implementing the ControlValueAccessor? I Guess no, so i guess i must be misunderstanding something .. :)
I'm using it the right way ?
Any hints are welcome !
Thanks !
It's not clear to me what you try to do here but ControlValueAccessor is an entity that you need to register for your element. Something like that:
const CUSTOM_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => LabelsValueAccessor), multi: true});
#Directive({
(...)
providers: [CUSTOM_VALUE_ACCESSOR]
})
export class LabelsValueAccessor implements ControlValueAccessor {
(...)
}
It will then take part of the value updates (from component and from the template). When you set a value within the component on the input (for example), the writeValue method is called in your value accessor. If you want to update the input value from the value accessor, you need to leverage the registered onChange callback by Angular2
See this article (section "NgModel-compatible component") for more details:
http://restlet.com/blog/2016/02/17/implementing-angular2-forms-beyond-basics-part-2/
In Parent Component I want output of form like this {someOtherControlName: any, controlName: number} so output of Child form must be number.also in update form (example) parentform.patchValue({someOtherControlName: '', controlNmae: 3}) value of formControl must set properly
#Component({
selector: 'parent',
template: `
<div [formGroup]="form">
<second-component [formControl]='form.controlName'> <second-component>
<div formControlName="someOtherControlName"></div>
export class ParentComponent {
parentForm = new FormGroup({
controlName: new FormControl(), <<<<<======important
someOtherControlName: new FormControl()
})
}
In ControlValueAccessor:
#Component({
selector: 'counter',
template: `
<div class="number-input" [formGroup]="form">
<input type="text" class="form-control text-center" formControlName="counter" />
</div>
`,
styleUrls: ['counter.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CounterComponent,
multi: true,
},
],
})
export class CounterComponent implements ControlValueAccessor, OnDestroy, OnInit {
form = new FormGroup({
counter: new FormControl(0, Validators.required),
})
private onChange: (value: any) => void = value => {}
private onTouched: () => void = () => {}
myValue: number
private onDestroy$: Subject<void> = new Subject()
ngOnInit() {
this.form.valueChanges
.pipe(
tap(value => {
this.onChange(typeof value === 'number' ? value : value.counter) <<<< important
this.onTouched()
}),
takeUntil(this.onDestroy$)
)
.subscribe()
}
}
writeValue(value: number | { counter: number }) {
this.myValue = typeof value === 'number' ? value : value.counter
this.form.setValue({ counter: value > 0 ? value : 0 })
}
registerOnChange(fn: () => {}) {
this.onChange = fn
}
registerOnTouched(fn: () => {}) {
this.onTouched = fn
}
setDisabledState?(isDisabled: boolean): void {
// when the parent updates the
// state of the form control
if (isDisabled) {
this.form.disable()
} else {
this.form.enable()
}
}
ngOnDestroy(): void {
this.onDestroy$.next()
this.onDestroy$.complete()
}

Error Message not being displayed when using angular2 with dart and data-driven form

I have the following code snippets:
.html
<form
novalidate
(ngSubmit) = "onSubmit()"
[ngFormModel] = "formModel">
<label
class = "mdl-textfield__label"
for = "first">First</label>
<input
type = "text"
ngControl = "firstCtrl"
(keyup) = "onInputHandler($event)"
class = "mdl-textfield__input"
id = "first">
<span *ngIf =
"!formModel.hasError('minLength', ['formModel', 'firstCtrl'])">Invalid inputs
for first</span>
</div>
..
</form>
.dart
#Component(
selector: 'name-form',
templateUrl: 'name_form.html',
directives: const [
FORM_DIRECTIVES,
CORE_DIRECTIVES,
MaterialTextfield,
MaterialButton
] )
class NameForm implements AfterViewInit {
Name name = new Name( );
ControlGroup formModel;
Control firstCtrl = new Control( '', Validators.minLength( 2 ) );
NameForm( FormBuilder fb ) {
formModel = fb.group( {
"firstCtrl": firstCtrl,
);
}
void onInputHandler( event ) {
String property = event.target.id;
switch ( property ) {
case 'first':
firstCtrl.valid ? (name.first = firstCtrl.value) : name.first;
print( name.first );
break;
}
...
}
There might be an issue with the *ngIf in the template, but I am uncertain.
Could you please say why the error message is not being displayed?
Thanks
Try with:
<span *ngIf="!formModel.controls.firstCtrl.valid">Invalid inputs for first</span>
I'm not aware of any hasError function on FormBuilder.

Can't access member variables of objects in List in dart

I have a list of teams in my component. These teams are listed in the html-template for the component. I've created add and update functions and they are working, but if I try to access a member variable of team i get the error: NoSuchMethodError: undefined is not a function:
void delete(int id, String name, int onlineId){
Team tempTeam;
html.window.console.info("In delete()");
for(int i = 0; i < teamList.length; i++){
if(teamList.elementAt(i).id == selectedTeam){ // <-- this fails
tempTeam = teamList.elementAt(i);
}
}
...
html.window.console.info("Deleting team: " + name + " succeded!");
}
Here is the component:
#Component(
selector: 'admin-teams-view',
templateUrl: 'packages/fremad/components/admin/teams_view.html',
cssUrl: 'packages/fremad/components/admin/teams_view.css',
useShadowDom: false
)
class ShowAdminTeamsComponent {
List<Team> teamList;
int selectedTeam = -1;
bool isEditing = false;
...
}
Here are the html of interest
<li ng-repeat="team in teamList">
<div class="item-name" ng-click="selectTeam(team.id)">
{{team.name}}
</div>
<div class="item-edit" ng-hide="isActive(team.id)">
<form>
Name:
<input type="text" size="35" ng-model="team.name">
Online ID:
<input type="number" size="15" ng-model="team.onlineId">
<input type="button" class="adminButton redButton" value="delete" ng-click="delete(team.id, team.name, team.onlineId)">
</form>
</div>
</li>
I will be thankful for any help!
From console after doing print(teamList[i]) and print(teamList[i].name):
{id: 1, name: Fremad Famagusta Menn Senior A, onlineId: 30296} js_primitives.dart:25
NoSuchMethodError: undefined is not a function
STACKTRACE:
TypeError: undefined is not a function
at dart.J.get$name$x (http://localhost:8080/fremad/fremad_main.dart.js:40910:39)
at ShowAdminTeamsComponent.delete$3 (http://localhost:8080/fremad/fremad_main.dart.js:34640:22)
at eval (eval at <anonymous> (http://localhost:8080/fremad/fremad_main.dart.js:2922:12), <anonymous>:2:42)
at dart.Primitives_applyFunction (http://localhost:8080/fremad/fremad_main.dart.js:2609:23)
at StaticClosureMap_lookupFunction_closure.call$3 (http://localhost:8080/fremad/fremad_main.dart.js:11389:18)
at ClosureMapLocalsAware_lookupFunction_closure.call$3 (http://localhost:8080/fremad/fremad_main.dart.js:9546:79)
at CallScope.methodClosure$3 (http://localhost:8080/fremad/fremad_main.dart.js:9851:33)
at CallScope.eval$2 (http://localhost:8080/fremad/fremad_main.dart.js:9868:19)
at _UnwrapExceptionDecorator.eval$2 (http://localhost:8080/fremad/fremad_main.dart.js:9359:31)
at _UnwrapExceptionDecorator.eval$2 [as eval$1] (http://localhost:8080/fremad/fremad_main.dart.js:9372:19)
Edit:
This is how my TeamList is loaded:
void loadData() {
tableLoaded = false;
_http.get('rest/team/getTeams.json')
.then((HttpResponse response) {
teamListObject = new TeamList.fromJson(response.data);
teamList = teamListObject.teamList;
tableLoaded = true;
html.window.console.info("Success on loading table");
})
.catchError((e) {
print(e);
tableLoaded = false;
});
}
And this is the class:
class TeamList {
bool empty;
List<Team> teamList;
TeamList(this.empty, this.teamList);
Map<String, dynamic> toJson() => <String, dynamic>{
"emtpy" : empty,
"listObject": teamList
};
TeamList.fromJson(Map<String, dynamic> json) : this(
json['empty'], json['listObject']);
}
You need to change the fromJson in the TeamList class:
TeamList.fromJson(Map<String, dynamic> json) : this(
json['empty'], new List<Team>.from(json['listObject'].map((x) => new Team.fromJson(x))));
Dart does not understand nested classes in that way. You have tried to access the map listObject as a map, not a List.
What exact error message do you get ?
You should try dumping the list and the index in the for loop - using print().
Try to read the answer How to access angular.dart component´s attribute or method
from Günter Zöchbauer

Resources