Custom validator in Angular 5 dynamic forms - json

I am creating a dynamic form from a configurable json in Angular 5. Everything is working fine,but i am not able to add a custom validator for a particular field.I am getting an error like
TypeError: v is not a function
Json
{
"key": "age",
"label": "Age",
"required": false,
"order": 4,
"controlType": "textbox",
"validations": ['required', 'minlength'],
"custom":['rangeValidator'],//custom validator function name
"type": "email"
}
Component to make dynamic form controls:
toFormGroup(questions) {
let group: any = {};
questions.forEach(question => {
group[question.key] = new FormControl(question.value || '', this.getValidators(question)
);
});
return new FormGroup(group);
}
getValidators(question) {
let vals = [];
question.validations.forEach((v) => {
if (v == 'required') {
vals.push(Validators.required);
}
if (v == 'minlength') {
vals.push(Validators.minLength(4))
}
});
if (question.custom || question.custom.length > 0) {
question.custom.forEach((va) => {
vals.push(va);
});
}
return vals;
}
Main Component file:
import { Component, OnInit, Input } from '#angular/core';
import { FormGroup, AbstractControl ,FormControl} from '#angular/forms';
function rangeValidator(c: FormControl) {
if (c.value !== undefined && (isNaN(c.value) || c.value > 1 || c.value < 10)) {
return { range: true };
}
return null;
}
#Component({
selector: 'app-question',
templateUrl: './dynamic-form-question.component.html',
styleUrls: ['./dynamic-form-question.component.css']
})
export class DynamicFormQuestionComponent implements OnInit {
#Input() question;
#Input() form: FormGroup;
get isValid() { return this.form.controls[this.question.key].valid; }
constructor() { }
ngOnInit() {
console.log("My form", this.form.value)
}
}
Stackblitz Url
Any ideas,Please help

there
if (question.custom || question.custom.length > 0) {
question.custom.forEach((va) => {
vals.push(va);
});
}
you want to add your custom validators, but in fact you just add to the validators array the string "rangeValidator". So yes v is not a function :)
You should should declare you range validators as a static function of your customs validators like that :
export class CustomValidators {
static rangeValidator(c: FormControl) {
if (c.value !== undefined && (isNaN(c.value) || c.value > 1 || c.value < 10)) {
return { range: true };
}
return null;
}
then import it in and use it like that :
getValidators(question) {
....
if (question.custom || question.custom.length > 0) {
question.custom.forEach((va) => {
vals.push(CustomValidators[va]);
});
}
return vals;
}
see the forked stackblitz
NB : this fork only resolve the current matter. You global example form validation still doesnt work

Related

Date picker in Html is not working as expected

I'm working on a requirement where date picker is displayed based on min and max dates when minimum and maximum are provided. But the format shows as attached enter image description here
Requirement is enter image description here
Below is the code snippet i have added HTML and TS.
<mat-form-field>
<input matInput type="date" [min]="todayDate [max]="maxReleaseDate" name='spoDate'
#spoDate="ngModel" [(ngModel)]="newSpoDate" autocomplete="off" required
[disabled]="spoPoCreateDate">
<mat-error *ngIf="spoDate && spoDate.untouched && spoDate.invalid">This is required field
</mat-error>
</mat-form-field>
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { DatePipe } from '#angular/common';
import { Order } from 'app/general-component/models/order';
import { GetService } from 'app/general-component/services/get.service';
import { PostService } from 'app/general-component/services/post.service';
import { environment } from 'environments/environment.prod';
#Component({
selector: 'app-spo-date-popup',
templateUrl: './spo-date-popup.component.html',
styleUrls: ['./spo-date-popup.component.css']
})
export class SpoDatePopupComponent implements OnInit {
currentSpoDate: any;
newSpoDate: any
orderId: any;
selectedOrganization: any;
itemId: any;
orderData: Order;
todayDate: any;
spoCreatePoDateNote: any;
maxReleaseDate: any
orderLineId: any;
currentOrderId: any;
loggedInUser: string;
userGrantList: any;
spoPoCreateDate: boolean;
constructor(private route: ActivatedRoute, private getService: GetService, private postService: PostService, private datePipe: DatePipe) { }
ngOnInit(): void {
let date = this.datePipe.transform(new Date(), 'dd/MM/yyyy');
this.todayDate = (date as string).split('/').reverse().join('-');
this.route.queryParams.subscribe(
(params) => {
this.orderId = params["orderId"];
this.selectedOrganization = params["selectedOrganization"];
this.itemId = params["ItemId"];
this.orderLineId = params["OrderLineId"];
this.currentOrderId = params["OrderId"];
this.loggedInUser = params["loggedInUser"]
},
(error) => console.log("error" + error)
)
this.grantCheck();
this.getService.getData(environment.getOrderByIdURL + this.currentOrderId, this.selectedOrganization).subscribe(res => {
this.orderData = res as Order;
if (this.orderData) {
this.orderData.OrderLine.forEach((e) => {
if (this.orderLineId === e.OrderLineId) {
this.currentSpoDate = e.Extended.PODropDate ? e.Extended.PODropDate : "";
this.maxReleaseDate = e.Extended.ReleaseDate ? this.datePipe.transform(new Date(e.Extended.ReleaseDate), 'yyy-MM-dd') : "";
console.log("SPOPODATEPOPUP :: ", this.maxReleaseDate);
}
})
}
});
let configData = {};
this.getService.getData(`${environment.configStoreURL}/REST_EX01`, this.selectedOrganization).subscribe((res) => {
let REST_EX01_CONFIG = res.ConfigStoreData;
if (res.ConfigStoreData) {
let list = REST_EX01_CONFIG.split(';');
list.forEach(e => {
let ex01Configs = e.split('=');
configData = {
...configData,
[ex01Configs[0]]: ex01Configs[1]
}
})
// #ts-ignore
this.spoCreatePoDateNote = configData.SPOPOCreateDateUpdateNote;
}
})
}
updateDate(res: any) {
console.log(res);
if (res && res.spoDate) {
if (this.orderData && this.orderData.OrderLine) {
let orderLineData = this.orderData.OrderLine.find((i) => i.OrderLineId === this.orderLineId);
let isAllOLSame = this.orderData.OrderLine.every((e) =>
e.OrderLinePromisingInfo && e.FulfillmentGroupId &&
e.FulfillmentGroupId === orderLineData.FulfillmentGroupId &&
e.OrderLinePromisingInfo.ShipFromLocationId === orderLineData.OrderLinePromisingInfo.ShipFromLocationId);
this.orderData.OrderLine.forEach((e) => {
if (this.orderLineId === e.OrderLineId) {
e.Extended.PODropDate = this.datePipe.transform(res.spoDate, 'MM/dd/yyy');
e.Extended.SPOPOCreateDateUpdated = true;
} else {
e.Extended.PODropDate = e.Extended.PODropDate;
e.Extended.SPOPOCreateDateUpdated = e.Extended.SPOPOCreateDateUpdated;
}
if (isAllOLSame) {
e.Extended.PODropDate = this.datePipe.transform(res.spoDate, 'MM/dd/yyy');
e.Extended.SPOPOCreateDateUpdated = true;
}
})
this.currentSpoDate = this.datePipe.transform(res.spoDate, 'MM/dd/yyy');
this.saveOrder(this.orderData)
}
}
// Extended.SPOPOCreateDateUpdated is flipped to true on updating
}
saveOrder(order: any) {
// let saveOrder: any;
// let postURL = environment.post_order_save;
// saveOrder = this.postService.postData(postURL, this.selectedOrganization, order);
this.postService.postData(environment.post_order_save, this.selectedOrganization, order)
.switchMap((res) => {
let data = {}
return this.postService.postData(environment.rhFetaFocURL, this.selectedOrganization, data)
})
.subscribe(response => {
},
error => { throw error; }
);
}
grantCheck() {
this.getService.getData(environment.getUserGrant + this.loggedInUser, this.selectedOrganization).subscribe(response => {
this.userGrantList = response;
let spoPoCreateDateGrant = this.userGrantList.some((res) => {
return res.ResourceId === "SPOPOCreateDateUpdate";
})
if (!spoPoCreateDateGrant) {
this.spoPoCreateDate = true;
} else {
this.spoPoCreateDate = false
}
console.log('SPOPOCREATEDATE', this.spoPoCreateDate);
});
}
}
can anyone suggest how to fix?
This is might solve your problem
<input type="date" value="2022-02-03" min="2022-02-03" max="2022-03-03">

How to Search a particular array from a json object in a json file in angular?

I want to search an array using a particular json object in a json file.
This is my json file.
{
"data": [
{
"QueryID": "203972",
"Query_Ref_No": "2019_06749",
"Description": "cannot access files",
"Location": "NULL"
},
{
"QueryID": "203973",
"Query_Ref_No": "2019_06751",
"Description": "cannot access files",
"Location": "NULL"
}
}
Below is my .html code for search. Here , i have used ion-searchbar which will take input and search through the json data and filters the array with the matched result.
<ion-searchbar
animated
icon="search"
inputmode="numeric"
showCancelButton="never"
autocomplete="on"
autocorrect="on"
(ionInput)="filterItems($event)"
(ionCancel)="onCancel()"
placeholder="Enter Request No">
</ion-searchbar>
Below is my .ts file. Here, filterItems(event) function will be called when user enters into searchbar.
I have used function searchData() to get whole json data from json file.
Now I want to filter data on the basis of QUERY_REF_NO.
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-connect-to-solve-details',
templateUrl: './connect-to-solve-details.component.html',
styleUrls: ['./connect-to-solve-details.component.scss'],
})
export class ConnectToSolveDetailsComponent implements OnInit {
constructor(public router: Router, private http: HttpClient, private c2s: ConnectToSolveService) { }
response: any = []
rev: any = []
id: any
searchdata: any[]
items: any[]
itemsarray: any[]
result: any = []
searchData()
{
this.http.get("/assets/data/C2S/concern_status.json").subscribe((searchdata: any) => {
this.searchdata = searchdata.data;
this.items = this.searchdata;
//console.log(this.searchdata)
console.log(this.items)
})
//return this.response
}
filterItems(event)
{
this.searchData();
const val = event.target.value;
if(val && val.trim() !== '')
{
this.itemsarray = this.items.filter((item) => (this.items.values["Query_Ref_No"].indexOf(val) > -1))
console.log(this.itemsarray)
}
else{
//this.isItemAvailable = false;
}
//console.log(event)
}
}
I am getting error as: ERROR TypeError: Cannot read property 'filter' of undefined
It would be of great help if anyone would tell how to search this.
Thanks in advance.
You can have searchData return the promise and await it inside the filterItems function.
searchData() {
return this.http
.get("/assets/data/C2S/concern_status.json").subscribe()
}
async filterItems(event) {
try {
const response = await this.searchData();
this.searchData = response;
this.items = response.data;
const val = event.target.value;
if(val && val.trim() !== '') {
this.itemsarray = this.items.filter((item) => items["Query_Ref_No"].indexOf(val) > -1)
console.log(this.itemsarray)
}
}
catch (error) {
console.log(error);
}
}

Pipe Translater in Html Element

So, dont know if anyone can help me. I have the below line of code in html :
<ion-item class="item-style">
<ion-label>{{ 'SettingsPage.ChangeLanguage' | translate }}</ion-label>
<ion-select [(ngModel)]="language" (ionChange)="onLanguageChange();" cancelText={{cancelText}} okText={{okText}}>
<ion-select-option value="en">{{ 'SettingsPage.English' | translate }}</ion-select-option>
<ion-select-option value="mt">{{ 'SettingsPage.Maltese' | translate }}</ion-select-option>
</ion-select>
</ion-item>
and the following methods in the .ts :
onLanguageChange() {
this.translate.use(this.language);
console.log(this.language);
this.globalVariableService.languageChanged = true;
this.globalVariableService.setCurrentLanguage(this.language);
this.storage.set('Language', this.language).then(() => {
this.sectorDataServiceProvider.populateSectorData().then(data => {
console.log('this.sectorInfo ', this.sectorDataServiceProvider.sectorInfo);
});
});
this.setOkAndCancelText();
}
setOkAndCancelText() {
if (this.language === 'en') {
this.cancelText = 'Cancel';
this.okText = 'OK';
} else if (this.language === 'mt') {
this.cancelText = 'Le';
this.okText = 'Iva';
}
}
I wish to remove the s setOkAndCancelText() which is being used to fill the cancelText={{cancelText}} parameter when the language changes within the app, and have something similar to:
<p [innerHTML]="'TermsOfUsePage.Header' | translate"></p>
Any ideas how i can make this possible please?
Instead of reliaing on libraries, you can create your custom service and add your local strings i.e key and value as following:
#Injectable({
providedIn: 'root'
})
export class LocalizationService {
localizedStrings: any;
constructor() {
this.localizedStrings = {
en: {
SettingsPage: {
cancelText: 'Cancel'
}
},
mt: {
SettingsPage: {
cancelText: 'Le'
}
}
}
}
getResource(keyString: string, workingLanguage: string): Promise<string> {
return new Promise((resolve, reject) => {
let resourceValue = null;
if(this.localizedStrings[workingLanguage].hasOwnProperty(keyString)) {
resourceValue = this.localizedStrings[workingLanguage][keyString];
} else {
resourceValue = this.getPropertyByKeyPath(this.localizedStrings[workingLanguage], keyString);
// if(!resourceValue) {
// debugger;
// }
}
if(resourceValue) {
resolve(resourceValue);
} else {
resolve(keyString);
}
});
}
private getPropertyByKeyPath(targetObj, keyPath) {
var keys = keyPath.split('.');
if(keys.length == 0) return undefined;
keys = keys.reverse();
var subObject = targetObj;
while(keys.length) {
var k = keys.pop();
if(!subObject.hasOwnProperty(k)) {
return undefined;
} else {
subObject = subObject[k];
}
}
return subObject;
}
}
Note that it is flexible, you can go deeper in objects. Now simply create a pipe for this service:
import { Pipe } from '#angular/core';
import { LocalizationService } from '../shared/localization.service';
#Pipe({
name:"translate"
})
export class LocalizedResourcePipe {
constructor(private localizationService: LocalizationService) {
}
transform(resourceKey: string, workingLanguage: string) {
return new Promise((resolve, reject) => {
if(!resourceKey) {
resolve();
} else {
this.localizationService.getResource(resourceKey, workingLanguage)
.then((value) => {
resolve(value);
});
}
});
}
}
Now simply call it in your HTML:
<ion-select [(ngModel)]="language" (ionChange)="onLanguageChange();" cancelText={{'SettingsPage.cancelText' | translate: 'en' | async}} okText={{'SettingsPage.cancelText' | translate: 'en' | async}}>
You can could make the the language parameter dynamic as well and in pipe, give it a default value. anyway here is the way to call it for your other language:
<ion-select [(ngModel)]="language" (ionChange)="onLanguageChange();" cancelText={{'SettingsPage.cancelText' | translate: 'mt' | async}} okText={{'SettingsPage.cancelText' | translate: 'mt' | async}}>
We have done it in following ways:
Installed the following packages:
"#ngx-translate/core": "11.0.1"
"#ngx-translate/http-loader": "4.0.0",
In app.module.js, add the following in imports:
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: AppHttpLoaderFactory,
deps: [HttpClient]
}
}),
export function AppHttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, Environment.frontend('/assets/i18n/'), '.json');
}
Here we have kept our en.json file under i18n folder where all key values of text are there.
After this you can use it in html template:
<ion-select [(ngModel)]="language" (ionChange)="onLanguageChange();" cancelText={{'cancel.text' | translate }} okText={{okText}/>
Here cancel.text is a key in en.json file.
Try this stackblitz example
Translate Service
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class TranslateService {
data: any = {};
constructor(private http: HttpClient) { }
use(lang: string): Promise<{}> {
return new Promise<{}>((resolve, reject) => {
const langPath = `assets/languageFiles/${lang || 'en'}.json`;
this.http.get<{}>(langPath).subscribe(
translation => {
this.data = Object.assign({}, translation || {});
resolve(this.data);
},
error => {
this.data = {};
resolve(this.data);
}
);
});
}
}
Translate Pipe
import { Pipe, PipeTransform } from '#angular/core';
import { TranslateService } from './translate.service';
#Pipe({
name: 'translate',
pure:false
})
export class TranslatePipe implements PipeTransform {
constructor(private translate: TranslateService) {}
transform(key: any): any {
return this.translate.data[key] || key;
}
}

IE Datepicker too long

I have a date picker that is to be IE friendly. However when I use it, it's displaying far too long.
HTML:
<label class="mr-sm-2" for="startDate">Start Date:</label>
<date-picker
class="mr-sm-2"
name="startDate"
formControlName="startDate">
</date-picker>
datepicker.component.ts:
import * as _ from "lodash" ;
import * as moment from 'moment';
import { Component, Input, forwardRef, ViewEncapsulation } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'date-picker',
template: `
<ng-container *ngIf="isGoodBrowser; else datePickerFallback">
<input
type="date"
class="form-control text-right"
[class.is-invalid]="invalid"
[(ngModel)]="value"
[disabled]="disabled"
/>
</ng-container>
<ng-template #datePickerFallback>
<div class="fallback-date-picker" [class.invalid]="invalid">
<my-date-picker
[options]="options"
[disabled]="disabled"
[(ngModel)]="value"
(dateChanged)="value = $event">
</my-date-picker>
</div>
</ng-template>
`,
encapsulation: ViewEncapsulation.None,
styles: [
`.fallback-date-picker.invalid .mydp {
border-color: #dc3545;
}`,
`.fallback-date-picker .mydp .selection {
text-align: right;
padding-right: 65px;
}`
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatePickerComponent),
multi: true
}
]
})
export class DatePickerComponent implements ControlValueAccessor {
private _value: any = null;
private propagateChange = _.identity;
isGoodBrowser: boolean = true;
disabled: boolean = false;
options: any = {
dateFormat: 'dd/mm/yyyy',
editableDateField: false
};
constructor() {
const userAgent = window.navigator.userAgent;
if (userAgent.indexOf('MSIE') >= 0) this.isGoodBrowser = false;
if (userAgent.indexOf('Trident') >= 0) this.isGoodBrowser = false;
if (userAgent.indexOf('Edge') >= 0) this.isGoodBrowser = true;
}
#Input() invalid: boolean = false;
#Input()
set value(v: any) {
if (v) {
if (_.isString(v)) {
const mDate = moment(v);
if (this.isGoodBrowser) {
this._value = v;
} else {
this._value = {
date: {
year: mDate.year(),
month: mDate.month() + 1,
day: mDate.date()
}
};
}
this.propagateChange(v);
} else if (v && v.jsdate) {
const mDate = moment(v.jsdate);
this.propagateChange(mDate.format('YYYY-MM-DD'));
} else {
this.propagateChange(null);
}
} else {
this._value = null;
this.propagateChange(null);
}
}
get value() {
return this._value;
}
writeValue(value: any) {
this.value = value ? value : null;
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched() { }
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
}
The images shows how the date picker displays, it goes across off the screen.
I feel like the problem might be in the html layout as this doesn't happen everytime I use the date picker.
I have solved the issue and it has nothing to do with the code shown.

Unable to sort in reverse using PipeTransform in angular4

I am unable to sort the data. I refered from this website -
http://www.advancesharp.com/blog/1211/angular-2-search-and-sort-with-ngfor-repeater-with-example
My data is not getting sorted in descending order -
Code -
transaction.component.ts file -->
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'orderBy' })
export class TransactionComponent implements OnInit,PipeTransform {
isDesc: boolean = false;
direction;
column;
sort(property){
this.direction = this.isDesc ? 1 : -1;
this.isDesc = !this.isDesc; //change the direction
this.column = property;
};
transform(records: Array<any>, args?: any): any {
return records.sort(function(a, b){
if(a[args.property] < b[args.property]){
console.log("clicked on first")
return -1 *args.direction;
}
else if( a[args.property] > b[args.property]){
console.log("clicked on second")
return 1 *args.direction;
}
else{
console.log("clicked on third")
return 0;
}
});
};
}
transaction.component.html -->
<tr *ngfor="let dat of result | filter:filterdata| orderBy :
{property: 'LOG_ID',direction:direction } | paginate: { itemsPerPage:
5, currentPage: p };let i = index ">
Below is the code for sort-by pipe. Which handles all types of array string, number and array of objects. For array of objects you need to pass the key according to which you want to sort.
import { Pipe, PipeTransform } from "#angular/core";
#Pipe({
name: "sortBy"
})
export class SortByPipe implements PipeTransform {
public transform(array: any[], reverse: boolean = false, prop?: string) {
array=JSON.parse(JSON.stringify(array));
if (!Array.isArray(array)) {
return array;
}
if (array.length) {
let sortedArray: any[];
if (typeof array[0] === "string") {
sortedArray = array.sort();
}
if (typeof array[0] === "number") {
sortedArray = array.sort((a, b) => a - b);
}
if (typeof array[0] === "object" && prop) {
sortedArray = array.sort((a, b) => a[prop].toString().localeCompare(b[prop].toString()));
}
if (reverse) {
return sortedArray.reverse();
} else {
return sortedArray;
}
}
return array;
}
}
import it and add to declaration in your AppModule. And below is how to use.
<span>{{['asd', 'def', 'bghi', 'nhm'] | sortBy: reverse}}</span>
<br>
<span>{{[2, 8, 3, 6, 34, 12] | sortBy: reverse}}</span>
<br>
<span>{{[{name:'name2'} , {name:'name1'} , {name:'name3'}] | sortBy: reverse : 'name' | json}}</span>
Here is a demo on Stackblitz
With this you can pass a boolean value that decide the reverse order or not. Also you can toggle that Just click the button it will change the order.
Hope this helps :)