Ionic 4 datetime picker does not update its variable - ionic-framework

I am using the ionic 4 ion-datetime picker to try to select a date in a form, though when a new date is selected, it does not update the variable.
I've tried using all of the different combinations I know, but I've included all of them here in this example.
<form [formGroup]="upsert" (ngSubmit)="submitUpsert()">
<ion-datetime
display-format="MM DD, YYYY"
picker-format="MM DD YYYY"
min="2010"
max="2030"
class="form__group__item--input has-value"
formControlName="date"
placeholder="Select Date"
[value]=date
[(ngModel)]="event.today"
(ngModelChange)="event.today"
></ion-datetime>
</form>
and in my form controller I have:
constructor(
private db: DatabaseService,
private modalController: ModalController,
private formBuilder: FormBuilder
) {
this.upsert = this.formBuilder.group({
title: ['', Validators.compose([Validators.required])],
date: ['', Validators.compose([Validators.required])],
notes: [''],
location: [''],
timezone: [''],
});
this.event.today = moment().toISOString(true);
this.event.timezone = moment().format('ZZ');
}
And eventually, I use the form submit action
submitUpsert() {
console.log("new date", this.event.today);
if(this.upsert.invalid) {
const error = {
code: "001",
message: this.upsert.status
};
this.showError(error);
} else {
this.db.upsert(this.upsert.value).then(eid => {
console.log("Success", eid);
this.hideUpsert();
}, error => {
this.showError(error);
console.error(error);
});
}
}
In the latest beta API, it does not mention to use [(ngModel)] or (ngModelChange), but it was the only way I could get a default date (of today) pre-selected.
Unfortunately, it's not updating the model nor the form object. Any thoughts?

You are using both ngModel and FormControlName. Use either one. you can update the form control value using setValue() method.
this.event.today = moment().toISOString(true);
this.upsert.get('date').setValue(this.event.today);

Try bind the model like this <ion-datetime [(ngModel)]="newsItem.validTo" name="validTo"></ion-datetime> The name attribute is required if the ion-datetime is within an ngForm.
This worked for me in Ionic 4.

Related

Customize tcomb-form-native's data filds

I'm trying to customize the Data field of atcomb-form-native module.
I wish the date fields were a classic input field but I still tried different methods, I didn't succeed.
I tried to override the datepicker field style but put the style when opening the picker to insert the date and not around the message.
Instead of 'Tap here to select a date' I would like to insert a phrase at will. How can I do?
Also, how can I customize the date format? I tried following this issue of github but it didn't solve the problem.
This is the part of code for formatting the data:
config: {
format: date => {
let toBeFormatted = new Date(date);
return String('Valida dal' + toBeFormatted.format('DD/MM/YYYY'));
},
dateFormat: date => {
let toBeFormatted = new Date(date);
return String('Valida dal' + toBeFormatted.format('DD/MM/YYYY'));
},
timeFormat: date => {
let toBeFormatted = new Date(date);
return String('Valida dal' + toBeFormatted.format('DD/MM/YYYY'));
},
}
Okay. I can give you my code. I had a little trouble finding it, but finally, everything is in the tcomb documentation.
The two important points to answer your question are :
"defaultValueText" and "format: (date) => ..."
import React, { Component } from "react";
import Expo from "expo";
import t from "tcomb-form-native";
import moment from 'moment';
import { StyleSheet, Text, Date} from "react-native";
import { Button } from "react-native-elements";
const Form = t.form.Form;
Form.stylesheet.dateValue.normal.borderColor = '#d0d2d3';
Form.stylesheet.dateValue.normal.backgroundColor = '#ffffff';
Form.stylesheet.dateValue.normal.borderRadius= 5,
Form.stylesheet.dateValue.normal.color = 'grey';
Form.stylesheet.dateValue.normal.borderWidth = 1;
const User = t.struct({
pseudo: t.String,
birthday: t.Date,
});
const options = {
order: ['pseudo','birthday'],
fields: {
pseudo: {
placeholder: 'Enter Name',
error: 'Name is empty?',
},
birthday: {
mode: 'date',
label: 'birthday',
config: {
defaultValueText: 'Enter birthday', // Allows you to format the PlaceHolders !!
format: (date) => {
return moment(date).format('DD-MM-YYYY'); // Allows you to format the date !!
},
}
},
},
};
... ...
export default class SignUp extends Component {
state = {...
render() {
return (
<View style={styles.container}>
<Form
type={User}
ref={c => (this._form = c)} // assign a ref
options={options} //set form options
/>
<Button
title="Sign Up!"
buttonStyle={styles.button}
onPress={this.handleSubmit}
/>
</View>
);
}
}
} ...

How to format data before displaying it on ag-grid

I've just discovered ag-grid. I'm using it on angular2+ and loading data from api. One of fields is date, but its in ISO format. I've been trying to format it, is there any way to do it, is it possible to add pipe or some other way? Usually i do it like this {{ someISODate | date: 'dd.MM.yyyy HH:mm'}}. Do i really have to format it manually in component before displaying it? Also I was wondering if its possible to add two fields under one column. Why? Well i have column author, and in data that im getting from api i have author.firstname and author.lastname, and now I wanna display both fields in same column. Any hints or examples are more than welcomed.
columnDefs = [
{headerName: 'Datum kreiranja', field: 'createdAt' }, //<-- wanna format it
{headerName: 'Vrsta predmeta', field: 'type.name' },
{headerName: 'Opis', field: 'description'},
{headerName: 'Kontakt', field: 'client.name'},
{headerName: 'Autor', field: 'author.firstname'}, //<-- wanna display author.lastname in same cell
{headerName: 'Status', field: 'status.name'}
];
You can do this by using cellRenderer (or valueFormatter as pointed in the UPDATE) and moment library.
{
headerName: 'Datuk kreiranja', field: 'createdAt',
cellRenderer: (data) => {
return moment(data.createdAt).format('MM/DD/YYYY HH:mm')
}
}
If you don't want to use moment, then below is how you can do it.
cellRenderer: (data) => {
return data.value ? (new Date(data.value)).toLocaleDateString() : '';
}
For Author field as well,
cellRenderer: (data) => {
return data.author.firstname + ' ' + data.author.lastname;
}
Reference: ag-grid: Cell Rendering
UPDATE
As suggested by #Mariusz, using valueFormatter makes more sense in this scenario. As per documentation, Value Formatter vs Cell Renderer
value formatter's are for text formatting and cell renderer's are for
when you want to include HTML markup and potentially functionality to
the cell. So for example, if you want to put punctuation into a value,
use a value formatter, but if you want to put buttons or HTML links
use a cell renderer.
You can use valueFormatter
{headerName: 'Datuk kreiranja', field: 'createdAt', valueFormatter: this.dateFormatter},
Create a small function:
dateFormatter(params) {
return moment(params.value).format('MM/DD/YYYY HH:mm');
}
First of all thanks to Paritosh.
The issue I was facing is the date field I was receiving from API is on the below format
"endDateUTC":"2020-04-29T12:00:00",
I have followed Paritosh solution using cellrenderer along with moment library but the value was always formatted to today's date for some reason.
The below solution is using valueFormatter with moment library.
This is for Angular2+ version. The job is really simple
In your .ts file:
import * as moment from 'moment';
{
headerName: 'End Date',
field: 'endDateUTC',
minWidth: 80,
maxWidth: 100,
valueFormatter: function (params) {
return moment(params.value).format('D MMM YYYY');
},
},
And the output you will get is:
End date:
29 APR 2020
Please feel free to change the date format you need.
Hope this will be helpful to some one.
I just want to expand on Vishwajeet's excellent answer from April 2019. Here's how I would use his code, and which import commands would be required:
import { Component, OnInit, ViewChild, LOCALE_ID, Inject } from '#angular/core';
constructor(#Inject(LOCALE_ID) private locale: string)
{
}
columnDefs = [
{ headerName: 'Last name', field: 'lastName' },
{ headerName: 'First name', field: 'firstName' },
{ headerName: 'DOB', field: 'dob', cellRenderer: (data) => { return formatDate(data.value, 'd MMM yyyy HH:mm', this.locale); }},
{ headerName: 'Policy start', field: 'policyStartDate', cellRenderer: (data) => { return formatDate(data.value, 'd MMM yyyy HH:mm', this.locale); } },
{ headerName: 'Policy end', field: 'policyEndDate', cellRenderer: (data) => { return formatDate(data.value, 'd MMM yyyy HH:mm', this.locale); } }
]
And your agGrid would contain something like this:
<ag-grid-angular
class="ag-theme-material"
[rowData]="rowData"
[columnDefs]="columnDefs"
</ag-grid-angular>
This works really nicely, but I decided to move the date formatting into it's own cell renderer for a few reasons:
The code above will display null values as "1 Jan 1970 01:00"
You would need to repeat this code, plus the imports and #Inject, into any control which uses it.
It repeats the logic each time, so if you wanted to change the date format throughout your application, it's harder to do. Also, if a future version of Angular broke that date formatting, you'd need to apply a fix for each occurrence.
So, let's move it into it's own cell renderer.
My DateTimeRenderer.ts file looks like this:
import { Component, LOCALE_ID, Inject } from '#angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { formatDate } from '#angular/common';
#Component({
selector: 'datetime-cell',
template: `<span>{{ formatTheDate(params.value) }}</span>`
})
export class DateTimeRenderer implements ICellRendererAngularComp {
public params: ICellRendererParams;
constructor(#Inject(LOCALE_ID) public locale: string) { }
agInit(params: ICellRendererParams): void {
this.params = params;
}
formatTheDate(dateValue) {
// Convert a date like "2020-01-16T13:50:06.26" into a readable format
if (dateValue == null)
return "";
return formatDate(dateValue, 'd MMM yyyy HH:mm', this.locale);
}
public onChange(event) {
this.params.data[this.params.colDef.field] = event.currentTarget.checked;
}
refresh(params: ICellRendererParams): boolean {
return true;
}
}
In my app.module.ts file, I need to import this Component:
import { DateTimeRenderer } from './cellRenderers/DateTimeRenderer';
#NgModule({
declarations: [
AppComponent,
DateTimeRenderer
],
imports: [
BrowserModule,
AgGridModule.withComponents([DateTimeRenderer])
],
providers: [],
bootstrap: [AppComponent]
})
And now, back in my Component which uses the agGrid, I can remove LOCALE_ID, Inject from this line:
import { Component, OnInit, ViewChild, LOCALE_ID, Inject } from '#angular/core';
..remove it from our constructor...
constructor()
{
}
..import our new renderer...
import { DateTimeRenderer } from './cellRenderers/DateTimeRenderer';
..and change the columnDefs to use the new renderer:
columnDefs = [
{ headerName: 'Last name', field: 'lastName' },
{ headerName: 'First name', field: 'firstName' },
{ headerName: 'DOB', field: 'dob', cellRenderer: 'dateTimeRenderer' },
{ headerName: 'Policy start', field: 'policyStartDate', cellRenderer: 'dateTimeRenderer' },
{ headerName: 'Policy end', field: 'policyEndDate', cellRenderer: 'dateTimeRenderer' }
]
frameworkComponents = {
dateTimeRenderer: DateTimeRenderer
}
And I just need to make sure my agGrid knows about this new frameworkComponents section:
<ag-grid-angular
class="ag-theme-material"
[rowData]="rowData"
[columnDefs]="columnDefs"
[frameworkComponents]="frameworkComponents" >
</ag-grid-angular>
And that's it.
Again, the nice thing about this is I can use this date formatter anywhere throughout my code, and all the logic is in one place.
It's just shocking that, in 2020, we actually need to write our own date formatting function for an up-to-date grid like agGrid... this really should've been included in the agGrid libraries.
For Angular, if you want to do this without moment.js you can try something like below:
import { Component, OnInit, Inject, LOCALE_ID } from '#angular/core';
import { formatDate } from '#angular/common';
#Component({
selector: 'app-xyz'
})
export class xyzComponent implements OnInit {
constructor( #Inject(LOCALE_ID) private locale: string ) {
}
columnDefs = [
{headerName: 'Submitted Date', field: 'lastSubmittedDate', cellRenderer: (data) => {
return formatDate(data.value, 'dd MMM yyyy', this.locale);
}];
}
This component is using format date of angular/common
(Working & Optimized solution for date formatting is here!)
Tested on Angular 8 with dynamic data where date is coming like 2019-11-16T04:00:00.000Z.
In Ag-grid if you use valueFormatter, then no need of including "field:'Order Date'".
Also following Ragavan Rajan's answer. So you need to install moment.js in your angular CLI.
Working code and installation is below:
//Install moment.js in angular 8 cli.(no need of --save in latest versions
npm install moment
//In your component.ts
import * as moment from 'moment';
//Inside your colDef of ag-grid
{
headerName: "Effective Date",
field: "effectiveDate",
valueFormatter: function (params){
return moment (params.value).format ('DD MMM, YYYY');
}
/*
* This will display formatted date in ag-grid like 16 Nov, 2019.
* field name's value is from server side.(column name).
*/
If you are using AdapTable then you can do it via their Format Column function which can be applied either at design-time or run-time. And there you can choose pretty much any DateTime format that you want.
https://demo.adaptabletools.com/style/aggridformatcolumndemo
if you have two subfields like "Start Date" and "End Date" then you are supposed to do like this:
{
headerName: "Date Range",
children: [
{
field: 'StartDate',
cellRenderer: (data) => {
return data ? (new Date(data.value)).toLocaleDateString() : '';
}
},
{
field: 'EndDate',
cellRenderer: (data) => {
return data ? (new Date(data.value)).toLocaleDateString() : '';
}
}
],
}
I'm Using Angular 10, And I achieved the date formatting by using cellRenderer and DatePipe
{
field: "fieldName",
cellRenderer: (res) => {
const datepipe : DatePipe = new DatePipe("en-US");
var x = datepipe.transform(res.data.fieldName.split('T')[0],"yyyy-MM-dd");
return x;
}
},
Split('T') is used because when you call the Api, date comes in this format
"2021-07-31T00:00:00.000Z".
Try this:
{
headerName: 'Order Date',
field: 'OrderDate',
valueFormatter: function (params) {
var nowDate = new Date(parseInt(params.value.substr(6)));
return nowDate.format("yyyy-mm-dd");
}

Angular: composite ControlValueAccessor to implement nested form

Composition of ControlValueAccessor to implement nested form is introduced in an Angular Connect 2017 presentation.
https://docs.google.com/presentation/d/e/2PACX-1vTS20UdnMGqA3ecrv7ww_7CDKQM8VgdH2tbHl94aXgEsYQ2cyjq62ydU3e3ZF_BaQ64kMyQa0INe2oI/pub?slide=id.g293d7d2b9d_1_1532
In this presentation, the speaker showed a way to implement custom form control which have multiple value (not only single string value but has two string field, like street and city). I want to implement it but I'm stuck. Sample app is here, does anybody know what should I correct?
https://stackblitz.com/edit/angular-h2ehwx
parent component
#Component({
selector: 'my-app',
template: `
<h1>Form</h1>
<form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" novalidate>
<label>name</label>
<input formControlName="name">
<app-address-form formControlName="address"></app-address-form>
<button>submit</button>
</form>
`,
})
export class AppComponent {
#Input() name: string;
submitData = '';
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = fb.group({
name: 'foo bar',
address: fb.group({
city: 'baz',
town: 'qux',
})
});
}
onSubmit(v: any) {
console.log(v);
}
}
nested form component
#Component({
selector: 'app-address-form',
template: `
<div [formGroup]="form">
<label>city</label>
<input formControlName="city" (blur)="onTouched()">
<label>town</label>
<input formControlName="town" (blur)="onTouched()">
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => AddressFormComponent)
}
]
})
export class AddressFormComponent implements ControlValueAccessor {
form: FormGroup;
onTouched: () => void = () => {};
writeValue(v: any) {
this.form.setValue(v, { emitEvent: false });
}
registerOnChange(fn: (v: any) => void) {
this.form.valueChanges.subscribe(fn);
}
setDisabledState(disabled: boolean) {
disabled ? this.form.disable() : this.form.enable();
}
registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
}
and error message I got
ERROR TypeError: Cannot read property 'setValue' of undefined
at AddressFormComponent.writeValue (address-form.component.ts:32)
at setUpControl (shared.js:47)
at FormGroupDirective.addControl (form_group_directive.js:125)
at FormControlName._setUpControl (form_control_name.js:201)
at FormControlName.ngOnChanges (form_control_name.js:114)
at checkAndUpdateDirectiveInline (provider.js:249)
at checkAndUpdateNodeInline (view.js:472)
at checkAndUpdateNode (view.js:415)
at debugCheckAndUpdateNode (services.js:504)
at debugCheckDirectivesFn (services.js:445)
I think FormGroup instance should be injected to nested form component somehow...
Couple issues, on your AppComponent change your FormBuilder to:
this.form = fb.group({
name: 'foo bar',
address: fb.control({ //Not using FormGroup
city: 'baz',
town: 'qux',
})
});
On your AddressFormComponent you need to initialize your FormGroup like so:
form: FormGroup = new FormGroup({
city: new FormControl,
town: new FormControl
});
Here's the fork of your sample: https://stackblitz.com/edit/angular-np38bi
We (at work) encountered that issue and tried different things for months: How to properly deal with nested forms.
Indeed, ControlValueAccessor seems to be the way to go but we found it very verbose and it was quite long to build nested forms. As we're using that pattern a lot within our app, we've ended up spending some time to investigate and try to come up with a better solution. We called it ngx-sub-form and it's a repo available on NPM (+ source code on Github).
Basically, to create a sub form all you have to do is extends a class we provide and also pass your FormControls. That's it.
We've updated our codebase to use it and we're definitely happy about it so you may want to give a try and see how it goes for you :)
Everything is explained in the README on github.
PS: We also have a full demo running here https://cloudnc.github.io/ngx-sub-form

Angular 2, unable to patch value to form controls

In my angular 2 app, I have an edit assignment component which loads when user clicks on edit button. It displays a form and fills all form controls with already existing values from firebase database which user can then edit. It loads all the values in the form controls when I click the edit button for the first time. Then if I go back or navigate to any other link and then click on the edit button again the form controls do not patch values from firebase database.
The images below will help you understand the problem better:
I click on the edit button first time everything works fine
Then if I go back to some other link and then return by clicking the edit button again the form does not patch form controls with values from firebase database.
I am validating my form like this
constructor(
private _routeParams: ActivatedRoute,
private _db: AngularFireDatabase,
private _fb: FormBuilder,
private _uploadService: UploadService,
private _getAsnService: GetAssignmentService,
private _editAsnSvc: EditAssignmentService,
) {
console.log("in constructor");
this.validate();
}
ngOnInit() {
console.log("in init");
this.getRouteParams();
this.assignment = this._getAsnService.getAssignment(this.asnDetailKey); // this.assignment is FirebaseObjectObservable retrieved from firebase DB
this.setInputValues();
this.getCourses();
this.validate();
}
validate() {
this.form = this._fb.group({
course: ['', Validators.compose([Validators.required])],
batch: ['', Validators.compose([Validators.required])],
subject: ['', Validators.compose([Validators.required])],
name: ['', Validators.compose([Validators.required])],
description: ['', Validators.compose([Validators.required])],
dueDate: ['', Validators.compose([Validators.required])]
});
this.today = new Date().toJSON().split('T')[0];
}
setInputValues() {
this.assignment.subscribe(asnDetails => {
console.log("setting values"); // <----- These are the values you see in console
this._assignment = asnDetails;
console.log(this._assignment); // <----- _assignment contains all values but form controls are unable to patch them
this.form.get('course').patchValue(this._assignment.course);
this.form.get('batch').patchValue(this._assignment.batch);
this.form.get('subject').patchValue(this._assignment.subject);
this.form.get('name').patchValue(this._assignment.AsnName);
this.form.get('description').patchValue(this._assignment.AsnDesc);
this.form.get('dueDate').patchValue(this._assignment.dueDate);
this.batches = this._db.list('/batches/' + this._assignment.course);
});
}
Thanks
* Edited *
I removed the setInputValues method and did the validation inside the validateForm method (i changed name from validate to validateForm) by passing asnDetails to it as told by #developer033
ngOnInit() {
this.getRouteParams();
this.assignment = this._getAsnService.getAssignment(this.asnDetailKey);
this.assignment.subscribe(asnDetails => this.validateForm(asnDetails));
this.getCourses();
}
validateForm(asnDetails) {
this.form = this._fb.group({
course: [asnDetails.course, Validators.compose([Validators.required])],
batch: [asnDetails.batch, Validators.compose([Validators.required])],
subject: [asnDetails.subject, Validators.compose([Validators.required])],
name: [asnDetails.AsnName, Validators.compose([Validators.required])],
description: [asnDetails.AsnDesc, Validators.compose([Validators.required])],
dueDate: [asnDetails.dueDate, Validators.compose([Validators.required])]
});
this.today = new Date().toJSON().split('T')[0];
this.batches = this._db.list('/batches/' + asnDetails.course);
}
I also created a initForm method and call it from constructor to initialize form.
initForm() {
this.form = this._fb.group({
course: ['', Validators.compose([Validators.required])],
batch: ['', Validators.compose([Validators.required])],
subject: ['', Validators.compose([Validators.required])],
name: ['', Validators.compose([Validators.required])],
description: ['', Validators.compose([Validators.required])],
dueDate: ['', Validators.compose([Validators.required])]
});
}
I think thats the cleaner way to validate model driven forms dynamically.
Your validate() method actually is wrong. It is initialising the form with default values and with validators which get executed automatically. The name validate() is misleading. In your case you create a new form with this._fb.group() everytime and you loose all the former values. The first time its just working because the request takes some time and in the meantime your validate() aka initializeForm() method did run. I guess in all other cases the request may is cached and so the setInputValues is called before your validate method. Just move the call to validate() before calling the asnService and you should be good. Greetings Chris

Angular2: add and submit a new formGroup name and values with model driven form

Adding to a FormArray via input field
To add values, via a form input, to an existing array I can use (click)="addAddress()" in the html where addAddress is defined in the component.ts to update values in an array in the form AppComponent:
ngOnInit() {
this.myForm = this._fb.group({
name: ['', [Validators.required, Validators.minLength(5)]],
addresses: this._fb.array([
this.initAddress(),
])
});
}
initAddress() {
return this._fb.group({
street: ['', Validators.required],
postcode: ['']
});
}
addAddress() {
const control = <FormArray>this.myForm.controls['addresses'];
control.push(this.initAddress());
}
And back in the html ngFor is used to add a set of input fields each time the 'add' button is clicked":
<div formArrayName="addresses">
<div *ngFor="let address of myForm.controls.addresses.controls; let i=index" >
<span>Address {{i + 1}}</span>
<div [formGroupName]="i">
<input type="text" formControlName="street">
<input type="text" formControlName="postcode">
</div>
</div>
</div>
Like the full working example here: https://embed.plnkr.co/sUjE1ULYhfDHLNBw2sRv/1
Adding to form FormGroup via input field
I would like to understand how to add a completely new FormGroup via form input.
So taking the case of the example above...
Rather than adding to an array of addresses:
{
"name": "",
"addresses": [
{
"street": "Baker Street",
"postcode": "w2"
},
{
"street": "Bond Street",
"postcode": "w1"
}
]
}
Each time an address is added a new FormGroup is created where the user adds the FormGroupName for each via form input. For example:
{
"name":"",
"home":{
"street":"Baker Street",
"postcode":"w2"
},
"work":{
"street":"Bond Street",
"postcode":"w1"
}
}
Using the addControl method this can be achieved like so:
app.component.html excerpt:
<div class="margin-20">
<a (click)="addGroup(newName.value)" style="cursor: default">Add Group +</a>
</div>
app.component.ts excerpt:
ngOnInit() {
this.myForm = this._fb.group({
});
}
addGroup(newName:string) {
let data = this._fb.group({
foo: ['what a load of foo'],
bar: ['see you at the bar']
})
this.myForm.addControl(newName, data);
document.getElementById('newName').value='';
}
Working example in this plunk:
For an example where a corresponding input field is published for each formControl that is added in the new named group I created this plunk