Custom Control not updating parent form validation - angular-reactive-forms

I've been working on adding a custom control to a form, I would like to do this because I know we have multiple components that may make up a form.
So for example you may have something like in app.components.ts:
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { FormGroup, FormBuilder, FormControl, Validators, AbstractControl } from '#angular/forms';
import { Model } from './cool-array/cool-array.component';
#Component({
selector: 'app-root',
styleUrls: ['./app.component.css'],
template:`
<!--The content below is only a placeholder and can be replaced.-->
<div class="col-md-6">
<form [formGroup]="form" >
<div class="form-group">
<label >Name</label>
<input formControlName="name" type="text" class="form-control" >
</div>
<app-cool-array formControlName="items"></app-cool-array>
</form>
</div>
<div class="col-md-6">
<div class="row">
IsValid Form: <strong> {{form.valid}}</strong>
Total Errors: <strong>{{form.errors? form.errors.length:0}}</strong>
</div>
<div class="row">
<table class="table table-stripped">
<thead>
<tr>
<th>Error</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let error of form.errors">
</tr>
</tbody>
</table>
</div>
</div>
`
})
export class AppComponent implements OnInit, AfterViewInit {
/**
*
*/
constructor(private formBuilder:FormBuilder) {
}
form:FormGroup;
model:MyModel;
ngOnInit(): void {
this.model = new MyModel();
this.form = this.formBuilder.group({ });
this.form.addControl('name', new FormControl('', Validators.required));
this.form.addControl('items', new FormControl([],[(control)=>MyValidator.MustHaveOne(control) ]))
}
ngAfterViewInit(): void {
console.log(this.form.errors)
}
}
export class MyValidator{
static MustHaveOne(control:AbstractControl){
if(control.value.length === 0) return {'length':'Items Must Have at least 1 item'};
return null;
}
}
export class MyModel{
name:string='';
items:Model[]=[];
}
You also may want to add the child component:
import { Component, OnInit } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'app-cool-array',
styleUrls: ['./cool-array.component.css'],
providers:[
{
provide:NG_VALUE_ACCESSOR,
multi:true,
useExisting:CoolArrayComponent
}
],
template:`
<button (click)="onAdd()" class="btn btn-primary" >Add</button>
<button (click)="onRemove()" class="btn">Remove</button>
<table class="table table-striped table-responsive">
<thead>
<tr>
<th>Something Required</th>
<th>Something Not Requred</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of items" (click)="onSelectedItem(item)" [ngClass]="{'selected-item':item === selectedItem}">
<td><input class="form-control" required [(ngModel)]="item.somethingRequired"/></td>
<td><input class="form-control" [(ngModel)]="item.somethingNotRequired"/></td>
</tr>
</tbody>
</table>
`
})
export class CoolArrayComponent implements OnInit, ControlValueAccessor {
onChange:any = ()=>{};
onTouched: any = () => { };
writeValue(obj: any): void {
this.items = obj;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
disabled:boolean=false;
items:Model[]=[];
selectedItem:Model;
constructor() { }
ngOnInit() {
}
onAdd():void{
this.items.push(new Model());
}
onRemove():void{
let index = this.items.indexOf(this.selectedItem);
if(index>-1)
this.items.splice(index, 1);
}
onSelectedItem(event){
this.selectedItem = event;
}
}
export class Model{
somethingRequired:string;
somethingNotRequired:string;
get isValid():boolean{
return this.somethingRequired !==undefined && this.somethingRequired.length>0;
}
}
When the child component becomes invalid this should set the form to be invalid.
I have tried adding a CustomValidator, however, it is never fired when the values change in the underlying array.
Can someone please explain why this is?

Okay, so I had to do a few things here to get this to work I will not post the entire code just the relevant parts.
I had to do is add a new custom Validator
static ArrayMustBeValid(control:AbstractControl){
if(control.value){
if(control.value.length>0){
let items:Model[] = control.value;
let invalidIndex = items.findIndex(x=> !x.isValid);
if(invalidIndex === -1) return null;
return {'array': 'array items are not valid'};
}
}
return {'array':'array cannot be null'};
}
Then I had to add an update event on the input this should fire on keyup
onUpdate(){
this.onChange(this.items);
}
Had to add the validator to FormControl in app.component.ts
this.form.addControl('items', new FormControl([],[(control)=>MyValidator.MustHaveOne(control), (control)=>MyValidator.ArrayMustBeValid(control) ]))

Related

Datatable data in vuejs and laravel is not showing on mobile

I am working with laravel and vuejs I show the data with dataTable, in the mobile version it does not show me the api data, the problem is in the mobile version that is not shown, in the desktop version if the data is shown, the edit buttons do not open in mobile version either. I have tried link an api from the internet and if it shows me the data.
Controller:
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Models\Admin\Dependence;
use App\Http\Controllers\Controller;
use App\Http\Controllers\ResponseApiController;
class DependenceController extends ResponseApiController
{
public function getDependencies()
{
$dependencies = Dependence::orderBy('created_at', 'desc')->get();
$message = $this->sendResponse($dependencies, 'Dependencias listadas correctamente');
return $message;
}
}
Route:
Route::get('dependencies', 'Admin\DependenceController#getDependencies');
Vue - Libraries in global file:
//DataTables
import 'datatables.net-bs4';
import 'datatables.net/js/jquery.dataTables.min.js';
import 'datatables.net-bs4/css/dataTables.bootstrap4.min.css';
import 'datatables.net-bs4/js/dataTables.bootstrap4.min.js';
import 'datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css';
import 'datatables.net-responsive-bs4/js/responsive.bootstrap4.min.js';
//DataTable Global
Vue.prototype.$tableGlobal = function(table) {
this.$nextTick(() => {
$(table).DataTable();
});
}
Template:
<template>
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col-auto mr-auto">
<h2 class="text-secondary">Dependencies List</h2>
</div>
<div class="col-auto">
<button class="right btn btn-primary btn-sm" v-b-modal.modal-1 #click="showModal = true">Add</button>
</div>
</div>
</div>
<!-- /.card-header -->
<div class="card-body animated fadeIn">
<table id="tblDependencies" class="table table-bordered table-hover dt-responsive">
<thead>
<tr>
<th>Description</th>
<th>Options</th>
</tr>
</thead>
<tbody>
<tr v-for="dependence in dependencies" :key="dependence.id">
<td>{{ dependence.description }}</td>
<td>
<!-- Change Status -->
<b-button size="sm" #click="statusUpdate(dependence)" class="mr-1" :class="[dependence.status == 1 ? 'btn-success' : 'btn-danger']">
<b-icon :icon="dependence.status == 1 ? 'check' : 'x' "></b-icon>
</b-button>
<!-- Edit -->
<b-button v-b-modal.editDependence size="sm" #click="editDependence(dependence)" class="btn btn-warning">
<b-icon icon="pencil-fill"></b-icon>
</b-button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
<!-- /.col -->
</div>
<AddDependence ref="dependenceModal" #reload="getDependencies"></AddDependence>
<EditDependence ref="editModal" :editDependence="this.dataEditDependence" #reload="getDependencies"></EditDependence>
</div>
</template>
<script>
// Services
import * as dependenceService from '../../services/dependence';
// Components
import AddDependence from '../../components/Admin/Dependence/AddDependence.vue';
import EditDependence from '../../components/Admin/Dependence/EditDependence.vue'
export default {
data() {
return {
dependencies: [],
dataEditDependence: {}
}
},
mounted() {
this.getDependencies();
},
components: {
AddDependence,
EditDependence
},
methods: {
getDependencies: async function() {
try {
const response = await dependenceService.getDependencies();
if(response.status == 200) {
this.dependencies = response.data.data;
this.reloadTable();
}
} catch (error) {
this.loading = false
console.log(error);
if (error.response.status == 422){
console.log(error);
}
}
},
statusUpdate: async function(dependence) {
try {
const response = await dependenceService.activateDesactivate(dependence.id);
if (response.status == 200) {
if(response.data.data.status == 1) {
dependence.status = 1;
}else {
dependence.status = 0;
}
this.$alerta('Actualizado', 'Estado actualizado correctamente', 'success');
this.reloadTable();
}
} catch (error) {
console.log(error);
}
},
editDependence(dependence) {
this.dataEditDependence = dependence;
},
reloadTable() {
$('#tblDependencies').DataTable().destroy();
this.$tableGlobal('#tblDependencies');
},
}
}
</script>
http_service:
import store from '../store';
import axios from 'axios';
export function http() {
return axios.create({
baseURL: store.state.apiURL,
});
}
Service:
import {http} from './http_service';
export function getDependencies() {
return http().get('/dependencies');
}

React -Redux: How to pass the text box value as an argument to dispatch method while dispatching the action on submit event

please advise how to pass the input value to the dispatch method.I want to pass the policy number to the action method in index.js which is in action folder.The retrievepolicy method need to get the policy number as an argument
On submit event,the retrievepolicy method will be dispatched
import React from 'react'
import { connect } from 'react-redux'
import { sayHello } from '../actions'
import { retrievePolicy } from '../actions'
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
policyNumber: ""
}
this.handleChange = this.handleChange.bind(this);
}
render() {
const { saySomething, retrievePolicy, whatsUp } = this.props;
return (
<div class="container">
<div class="text-white bg-dark" >
<button class="btn btn-info" onClick={saySomething}>PRESS TO DISPATCH
FIRST ACTION</button>
<h2>{whatsUp}</h2>
<table>
<tr>
<td><label for="exampleInputEmail" class="label">Policy Number</label>
</td>
<td><input type="textbox" class="textarea" id="policyNumberid"
name="policyNumberid"
value={this.state.policyNumber} onChange={this.handleChange}
/></td>
</tr>
<tr>
<td>Data Location</td>
<select class="form-control" id="timezone" name="timezone"
selected="selected" >
<option value="" selected disabled hidden>Choose here</option>
<option value="Test">\\w</option>
<option value="Stage">\\ws</option>
</select>
</tr>
<tr><button class="btn btn-info" onClick={retrievePolicy} >Retrieve
Policy Information</button></tr>
</table>
</div>
</div>
)
}
handleChange(typedText) {
alert('hi')
this.setState({ policyNumber: typedText.target.value }, () => {
console.log(this.state.policyNumber);
});
}
};
const mapStateToProps = (state) => ({
whatsUp: state.policy,
stateObject: state
})
const mapDispatchToProps = (dispatch) => (
{
saySomething: () => { dispatch(sayHello()) },
retrievePolicy: (policyNumber) => {
dispatch(retrievePolicy(policyNumber)) }
}
)
Button = connect(mapStateToProps, mapDispatchToProps)(Button)
export default Button

Angular6 filter

I'm trying to build a filter pipe in Angular 6, but it's not working properly.
.html
<form>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" name="searchString" placeholder="Type to search..." [(ngModel)]="searchString">
</div>
</div>
</form>
<table class="table">
<tr *ngFor="let user of users | filter2 : searchString">
{{user.name}}
</tr>
</table>
pipe
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter2'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (!items) return [];
if (!searchText) return items;
searchText = searchText.toLowerCase();
return items.filter(it => {
return it.toLowerCase().includes(searchText);
});
}
}
I get this error when I write inside input:
ERROR TypeError: it.toLowerCase is not a function
What am I doing wrong? Thank you for your time!
users
You can try it.name.toString().toLowerCase().includes(searchText);
Component
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
users:any[];
constructor(){
this.users = [
{name:"John"},
{name:"Tom"},
{name:"Carlton"},
{name:"Judy"},
{name:"Ada"}
];
}
}
Html
<form>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" name="searchString" placeholder="Type to search..." [(ngModel)]="searchString">
</div>
</div>
</form>
<table class="table">
<tr *ngFor="let user of users | filter2 : searchString">
{{user.name}}
</tr>
</table>
Pipe
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter2'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (!items) return [];
if (!searchText) return items;
searchText = searchText.toLowerCase();
return items.filter(it => {
return it.name.toLowerCase().includes(searchText);
});
}
}

Datatable not refreshing on save

I want my table to refresh on save click, it's working for normal table with *ngFor, but I'm using Smartadmin angular template. I think the solution may be related to table.ajax.reload() , but how do i execute this in angular way.
save-tax.component.ts
import { Component, OnInit, Input ,Output, EventEmitter } from '#angular/core';
// Forms related packages
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { FormValidation } from 'app/shared/validators/formValidation'; //custom form validation
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { FlashMessagesService } from 'angular2-flash-messages';
#Component({
selector: 'save-tax',
templateUrl: './save-tax.component.html'
})
export class SaveTaxComponent implements OnInit {
#Output() reloadTableData = new EventEmitter();
saveTaxForm: FormGroup;
constructor(private _fb: FormBuilder,
private _config: ConfigurationService,
private _flash: FlashMessagesService) { }
ngOnInit() {
this.saveTaxForm_builder();
}
saveTaxForm_builder() {
this.saveTaxForm = this._fb.group({
tax_title: [null, [
Validators.required
]],
tax_rate: [null, [
Validators.required,
Validators.pattern(FormValidation.patterns().price),
]],
});
}
tTitle = "input"; tRate = "input";
validInput(val) {
var classSuccess = "input state-success";
val == 'tax_title' ? this.tTitle = classSuccess : null;
val == 'tax_rate' ? this.tRate = classSuccess : null;
}
invalidInput(val) {
var classError = "input state-error";
val == 'tax_title' ? this.tTitle = classError : null;
val == 'tax_rate' ? this.tRate = classError : null;
}
classReset() {
this.tTitle = "input";
this.tRate = "input";
}
save_tax() {
if (this.saveTaxForm.value) {
this._config.createTax(this.saveTaxForm.value).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-success', timeout: 1000 });
this.saveTaxForm.reset();
this.classReset();
this.reloadTableData.emit(); // Emitting an event
} else {
this.saveTaxForm.reset();
this.classReset();
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-danger', timeout: 3500 });
}
},
error => {
this.saveTaxForm.reset();
this.classReset();
this._flash.show("Please contact customer support. " + error.status + ": Internal server error.", { cssClass: 'alert alert-danger', timeout: 5000 });
});
} else {
this._flash.show('Something went wrong! Please try again..', { cssClass: 'alert alert-warning', timeout: 3000 });
}
}
}
datatable.component.ts
import {Component, Input, ElementRef, AfterContentInit, OnInit} from '#angular/core';
declare var $: any;
#Component({
selector: 'sa-datatable',
template: `
<table class="dataTable responsive {{tableClass}}" width="{{width}}">
<ng-content></ng-content>
</table>
`,
styles: [
require('smartadmin-plugins/datatables/datatables.min.css')
]
})
export class DatatableComponent implements OnInit {
#Input() public options:any;
#Input() public filter:any;
#Input() public detailsFormat:any;
#Input() public paginationLength: boolean;
#Input() public columnsHide: boolean;
#Input() public tableClass: string;
#Input() public width: string = '100%';
constructor(private el: ElementRef) {
}
ngOnInit() {
Promise.all([
System.import('script-loader!smartadmin-plugins/datatables/datatables.min.js'),
]).then(()=>{
this.render()
})
}
render() {
let element = $(this.el.nativeElement.children[0]);
let options = this.options || {}
let toolbar = '';
if (options.buttons)
toolbar += 'B';
if (this.paginationLength)
toolbar += 'l';
if (this.columnsHide)
toolbar += 'C';
if (typeof options.ajax === 'string') {
let url = options.ajax;
options.ajax = {
url: url,
complete: function (xhr) {
options.ajax.reload();
}
}
}
options = $.extend(options, {
"dom": "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-6 col-xs-12 hidden-xs text-right'" + toolbar + ">r>" +
"t" +
"<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
oLanguage: {
"sSearch": "<span class='input-group-addon'><i class='glyphicon glyphicon-search'></i></span> ",
"sLengthMenu": "_MENU_"
},
"autoWidth": false,
retrieve: true,
responsive: true,
initComplete: (settings, json)=> {
element.parent().find('.input-sm', ).removeClass("input-sm").addClass('input-md');
}
});
const _dataTable = element.DataTable(options);
if (this.filter) {
// Apply the filter
element.on('keyup change', 'thead th input[type=text]', function () {
_dataTable
.column($(this).parent().index() + ':visible')
.search(this.value)
.draw();
});
}
//custom functions
element.on('click', 'delete', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( $(this).hasClass('delete') ) {
row.remove().draw(false);
console.log(row);
}
else {
//$(table).$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
console.log($(this).attr("class"))
});
//end custom functions
if (!toolbar) {
element.parent().find(".dt-toolbar").append('<div class="text-right"><img src="assets/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;"></div>');
}
if(this.detailsFormat){
let format = this.detailsFormat
element.on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( row.child.isShown() ) {
row.child.hide();
tr.removeClass('shown');
}
else {
row.child( format(row.data()) ).show();
tr.addClass('shown');
}
})
}
}
}
tax-list.component.html
<!-- NEW COL START -->
<article class="col-sm-12 col-md-12 col-lg-12">
<!-- Widget ID (each widget will need unique ID)-->
<div sa-widget [editbutton]="false" [custombutton]="false">
<header>
<span class="widget-icon"> <i class="fa fa-percent"></i> </span>
<h2>Tax Rule List</h2>
</header>
<!-- widget div-->
<div>
<!-- widget content -->
<div class="widget-body no-padding">
<sa-datatable [options]="tableData" paginationLength="true" tableClass="table table-striped table-bordered table-hover" width="100%">
<thead>
<tr>
<th data-hide="phone"> ID </th>
<th data-hide="phone,tablet">Tax Title</th>
<th data-class="expand">Tax Rate</th>
<th data-hide="phone,tablet">Status</th>
<th data-hide="phone,tablet"> Action </th>
</tr>
</thead>
</sa-datatable>
</div>
<!-- end widget content -->
</div>
<!-- end widget div -->
</div>
<!-- end widget -->
</article>
<!-- END COL -->
tax-list.component.ts
import { FlashMessagesService } from 'angular2-flash-messages';
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { Component, OnInit } from '#angular/core';
declare var $: any;
#Component({
selector: 'tax-list',
templateUrl: './tax-list.component.html'
})
export class TaxListComponent implements OnInit {
tableData: any;
constructor(private _config: ConfigurationService, private _flash: FlashMessagesService) { }
ngOnInit() {
this.fetchTableData();
this.buttonEvents();
}
fetchTableData() {
this.tableData = {
ajax: (data, callback, settings) => {
this._config.getTaxRules().subscribe(data => {
if (data.success) {
callback({
aaData: data.data
});
} else {
alert(data.msg);
}
},
error => {
alert('Internal server error..check database connection.');
});
},
serverSIde:true,
columns: [
{
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{ data: 'tax_title' },
{ data: 'tax_rate' },
{ data: 'status' },
{
render: function (data, type, row) {
return `<button type="button" class="btn btn-warning btn-xs edit" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Edit</button>
<button type="button" class="btn btn-danger btn-xs delete" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Delete</button>`;
}
}
],
buttons: [
'copy', 'pdf', 'print'
]
};
}
buttonEvents(){
document.querySelector('body').addEventListener('click', (event) => {
let target = <Element>event.target; // Cast EventTarget into an Element
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('edit')) {
this.tax_edit(target.getAttribute('data-element-id'));
}
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('delete')) {
this.tax_delete(target.getAttribute('data-element-id'));
}
});
}
tax_edit(tax_id) {
}
tax_delete(tax_id) {
this._config.deleteTaxById(tax_id).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-info fade in', timeout: 3000 });
this.fetchTableData();
} else {
this._flash.show(data.msg, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
}
},
error => {
this._flash.show(error, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
});
}
reloadTable(){
this.ngOnInit();
}
}
You can add a refresh button in your widget using <div class='widget-toolbar'>...</div> and using (click) event binding, attach a method with it. I named it as onRefresh() ...
<div sa-widget [editbutton]="false" [colorbutton]="false">
<header>
<span class="widget-icon">
<i class="fa fa-chart"></i>
</span>
<h2>Sample Datatable</h2>
<div class="widget-toolbar" role="menu">
<a class="glyphicon glyphicon-refresh" (click)="onRefresh('#studentTable table')"></a>
</div>
</header>
<div>
<div class="widget-body no-padding">
<sa-datatable id="studentTable" [options]="datatableOptions" tableClass="table table-striped table-bordered table-hover table-responsive">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Rank</th>
<th>Options</th>
</tr>
</thead>
</sa-datatable>
</div>
</div>
</div>
Focus at the method which I have given in the click event binding, I passed the id of the <sa-datatable> with table, that is #studentTable table that mentions the table tag according to the datatable implementation of the smartadmin.
Now in the component, add a method 'onRefresh()' which should be like
onRefresh(id: any) {
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
In this method, that #studentTable table will come in this id which is a parameter in the method. Using jQuery you can do the table.ajax.reload().
But you need to declare the jQuery at the top.
declare var $ : any;
app.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
declare var $: any;
#Component({
selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
})
export class OrderComponent implements OnInit, OnDestroy {
datatableOptions:{
...
}
constructor(){
...
}
ngOnInit(){
...
}
onRefresh(){
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
}

Angular 2 - Required field validation if checkbox is selected

Hy guys I'm new to Angular2 and in JS frameworks in general. I'm flowing tutorials on official site and haven't been able to find the solution to this problem.
So I have checkbox which is optional but if the checkbox is "checked" a new input field is shown. this part is not a problem. The problem is that I'm using modal based validation and I can't figure out how to make this new input field required only if the checkbox is checked.
this is may implementation so far:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<!--{{form}}-->
<div formGroupName="test">
<div class="field">
<div class="checkbox">
<input type="checkbox" name="entryRecurring" value="" id="entryRecurring" formControlName="entryRecurring" />
<label for="entryRecurring">
<div class="checkbox_icon"></div>
Recurring Entry
</label>
</div>
</div>
<div *ngIf="form.value.test.entryRecurring">
<div class="field">
<label for="entryRecurringAmount">Repeat Amount</label>
<input type="text" name="entryRecurringAmount" value="" id="entryRecurringAmount" formControlName="entryRecurringAmount" />
</div>
</div>
</div>
<div class="field last">
<button name="submit" id="submit" class="btn btn_sushi" [disabled]="!form.valid">Submit</button>
</div>
import {Component, Input, OnInit, OnChanges} from '#angular/core';
import { Validators } from '#angular/common';
import { REACTIVE_FORM_DIRECTIVES, FormGroup, FormControl, FormBuilder } from '#angular/forms';
import { FormMessages } from './../helpers/formMessages.component';
import {EntriesService} from './entries.service';
import {ValidationService} from '../helpers/validation.service';
import {Category, CategoryByType} from '../../mock/mock-categories';
#Component({
selector: 'entryForm',
templateUrl: 'app/components/entries/entriesEdit.template.html',
directives: [REACTIVE_FORM_DIRECTIVES, FormMessages],
providers: [EntriesService, ValidationService]
})
export class EntriesEditComponent implements OnInit, OnChanges {
#Input() control: FormControl;
public form:FormGroup;
public submitted:boolean = false;
// private selectedId: number;
categories: Category[];
categoriesSortedByType: CategoryByType[];
constructor(
private _fb:FormBuilder,
private _entriesService: EntriesService
// private _router: Router
) {
this.form = this._fb.group({
test: this._fb.group({
entryRecurring: [''],
entryRecurringAmount: [''],
})
});
}
onSubmit() {
this.submitted = true;
// console.log(this.form.value);
if (this.form.dirty && this.form.valid) {
this._entriesService.saveEntry(this.form.value);
}
}
You could do that by using a custom validation service.
import {NgFormModel} from "angular2/common";
import {Component, Host} from 'angular2/core';
#Component({
selector : 'validation-message',
template : `
<span *ngIf="errorMessage !== null">{{errorMessage}}</span>
`,
inputs: ['controlName : field'],
})
export class ControlMessages {
controlName : string;
constructor(#Host() private _formDir : NgFormModel){
}
get errorMessage() : string {
let input = this._formDir.form.find(this.controlName);
let checkBx = this._formDir.form.find('checkBoxName');
if(input.value.trim() === '' && checkBx.checked) {
return 'The input field is now required'
}
return null;
}
}
Then use the new component like bellow
<div *ngIf="form.value.test.entryRecurring">
<div class="field">
<label for="entryRecurringAmount">Repeat Amount</label>
<input type="text" name="entryRecurringAmount" value="" id="entryRecurringAmount" ngControl="entryRecurringAmount" />
<validation-message field="entryRecurringAmount"></validation-message>
</div>
</div>
Hope that helped!

Resources