I have a FormControl in my Angular 8 app like this:
this.closingOrderFormGroup = this._fb.group({
final_price: new FormControl('', [ Validators.required ] ),
});
I tried to add/remove Validators.required dynamically based on some radio button check as follows:
radioChange( event ) {
const finalPriceControl: any = this.closingOrderFormGroup.get('final_price');
if ( event.value == 'completed' ) {
finalPriceControl.setValidators([Validators.required]);
}
else if ( event.value == 'rejected' ) {
finalPriceControl.setValidators(null);
}
}
But after set Validators null the FormControl "status" is still Invalid. How should I change the FormControl status?
You can subscribe for value change in angular reactive form.
ngOnInit() {
this.loginForm = this.fb.group({
final_price: ['', [ Validators.required ] ]
});
this.formControlValueChanged();
}
//Subscribe this way
formControlValueChanged() {
const finalPriceControl = this.loginForm.get('final_price');
this.loginForm.get('final_price').valueChanges.subscribe(
(mode: string) => {
console.log(mode);
if (mode === 'completed') {
finalPriceControl.setValidators([Validators.required]);
} else if (mode === 'rejected') {
finalPriceControl.clearValidators();
}
finalPriceControl.updateValueAndValidity();
});
}
Hope this helps :)
Related
I have a reactive form and adding a custom async validator to check if the values entered is unique by checking the array of available values. The validator is being invoked, but the validation error 'duplicate' is not getting added to the form field errors and not displaying in the template. Not sure what i am doing wrong here.
component.ts
private registerFormGroup(): void {
this.titleField = new FormControl(
{ value: this.auditTemplate.title, disabled: true },
[Validators.required],
[TemplateNameValidator.createValidator(this.auditTemplateService)]
);
this.templateForm = this.formBuilder.group({
title: this.titleField,
tags: [this.auditTemplate.tags]
});
}
name validator:
export class TemplateNameValidator {
static createValidator(auditTemplateService: AuditTemplateService): AsyncValidatorFn {
console.log("Static factory call");
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if(isEmptyInputValue(control.value)) {
return of(null);
} else {
return control.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap((name: string) =>
auditTemplateService.isNameUnique(name)
.pipe
(
tap(response => console.log('inside switchmap', response)),
map(isUnique => !isUnique ? { 'duplicate' : true } : null),
catchError(() => of(null))
)
)
);
}
};
}
}
function isEmptyInputValue(value: any): boolean {
return value === null || value.length === 0;
}
in the template html, if i try to display the error, it is not displaying anything:
<span>Duplicate: </span>{{templateNameField.errors?.duplicate}}<br>
Thanks
AsyncValidators are used every time the AbstractControl state is changed, so you don't need to use the control.valueChanges observable.
Additionally, the observable you're returning from the validator function should send a complete signal after sending null or an error. If you return a hot observable, then the async validator will not work.
Try replacing control.valueChanges.pipe( with of(control.value).pipe(. Also, if your service method .isNameUnique() doesn't emit a complete signal, add take(1) before your the first tap() operator.
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (isEmptyInputValue(control.value)) {
return of(null);
} else {
return of(control.value).pipe(
distinctUntilChanged(),
debounceTime(500),
switchMap((name: string) =>
auditTemplateService.isNameUnique(name).pipe(
take(1),
tap((response) => console.log("inside switchmap", response)),
map((isUnique) => (!isUnique ? { duplicate: true } : null)),
catchError(() => of(null))
)
)
);
}
};
I have this useSiren hook that should update its state with the incoming json argument but it doesnt.
On the first call the json is an empty object, because the fetch effect has not been run yet.
On the second call its also an empty object (triggered by loading getting set to true in App)
And on the third call its filled with valid data. However, the valid data is not applied. The state keeps its initial value.
I guess somehow setSiren must be called to update it, since initial state can only be set once. But how would I do that? Who should call `setSiren?
import { h, render } from 'https://unpkg.com/preact#latest?module';
import { useEffect, useState, useCallback } from 'https://unpkg.com/preact#latest/hooks/dist/hooks.module.js?module';
import htm from "https://unpkg.com/htm#latest/dist/htm.module.js?module";
const html = htm.bind(h);
function useFetch({
method = "GET",
autoFetch = true,
href,
body
}) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState()
const [response, setResponse] = useState()
const [isCancelled, cancel] = useState()
const [json, setJson] = useState({})
const sendRequest = async payload => {
try {
setLoading(true)
setError(undefined)
const response = await fetch(href.replace("http://", "https://"), {
method
})
const json = await response.json()
if (!isCancelled) {
setJson(json)
setResponse(response)
}
return json
} catch (err) {
if (!isCancelled) {
setError(err)
}
throw err
} finally {
setLoading(false)
}
}
if (autoFetch) {
useEffect(() => {
sendRequest(body)
return () => cancel(true)
}, [])
}
return [{
loading,
response,
error,
json
},
sendRequest
]
}
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
function Action(props) {
const [{ loading, error, json }, sendRequest] = useFetch({ autoFetch: false, href: props.href, method: props.method })
const requestAndUpdate = () => {
sendRequest().then(props.onRefresh)
}
return (
html`
<button disabled=${loading} onClick=${requestAndUpdate}>
${props.title}
</button>
`
)
}
function App() {
const [{ loading, json }, sendRequest] = useFetch({ href: "https://restlr.io/toggle/0" })
const [{ state, actions }, setSiren] = useSiren(json)
return (
html`<div>
<div>State: ${loading ? "Loading..." : (state.properties && state.properties.value)}</div>
${actions.map(action => html`<${Action} href=${action.href} title=${action.title || action.name} method=${action.method} onRefresh=${setSiren}/>`)}
<button disabled=${loading} onClick=${sendRequest}>
REFRESH
</button>
</div>
`
);
}
render(html`<${App}/>`, document.body)
Maybe what you want to do is to update the siren state when the json param changes? You can use a useEffect to automatically update it.
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
useEffect(() => { // here
setSiren(json)
}, [json])
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
The pattern mentioned by #awmleer is packaged in use-selector:
import { useSelectorValue } from 'use-selector';
const { entities=[], actions=[], title} = json;
const siren = useSelectorValue(() => ({
state: entities.find(entity => entity.class === 'state') || {},
actions,
title
}), [json]);
Disclosure I'm author and maintainer of use-selector
This is my code
<button v-if="$can('permission-a')">Add User</button>
<button v-if="$can('permission-b')">Add Employee</button>
And this is my $can method
data() {
return {
returnAccess: false
};
},
methods: {
$can(permissionName) {
let route = window.routes.permission;
let returnAccess;
axios
.get(route + `/${permissionName}`)
.then(resounse => {
if (resounse.data == 101) {
this.returnAccess = true;
}
})
.catch(error => {
this.returnAccess = false;
});
return this.returnAccess;
},
}
$can method return false for add user button and return true for add employee button. But add user button also showing because of true return for add employee button.
How can i solve this issue. Any one can help me?
Thanks in advance.
First of all you don't need to return a value
create a permissions object:
data() {
return {
permissions: {}
};
and a method like:
methods: {
$can(permissionName) {
let route = window.routes.permission;
let returnAccess;
axios
.get(route + `/${permissionName}`)
.then(resounse => {
if (resounse.data == 101) {
this.permissions.permissionName = true;
}
})
.catch(error => {
this.permissions.permissionName = false;
});
},
}
now foreach this in your created()
created() {
let permissionNames = ['a', 'b'];
permissionNames.forEach(function(permissionName) {
this.$can(permissionName) {
});
}
this way you create a object with keys as the permisson.
now in html you can simple do:
<button v-if="permission.somePermission">Add User</button>
<button v-if="permission.somePermission">Add Employee</button>
NOTE: not tested, but hope you get the idea
I'm applying Angular 4 in my project and I am having trouble with Custom Validation using HTTP request.
I also tried this: How to implement Custom Async Validator in Angular2.
But it doesn't work in my project.
Here's what I've done so far:
Validation biding:
userName: ['', [Validators.required, Validators.minLength(3), this.validateUserName.bind(this)]]
Value changes event:
let userNameControl = this.individualForm.get('userName');
userNameControl.valueChanges.subscribe(value => {
this.setErrorMessagesForUserNameControl(userNameControl)
}
);
Validation function:
validateUserName(control: FormControl): Promise<any> {
let promise = new Promise<any>(
(resolve, reject) => {
if (this.oldUserNameForCheck != undefined) {
this._individualUpdateService.isUserNameExisting(this.oldUserNameForCheck, control.value).subscribe(
(res) => {
if (res.isUserNameExisting) {
console.log("existing");
resolve({'existing': true});
} else {
console.log("NOT existing");
resolve(null);
}
},
(error) => {
console.log(error);
}
);
} else {
resolve(null);
}
}
);
return promise;
}
Error Messages
I just try to validate the username by sending it to the back-end.
Here's the logs
As you can see, there's a message for "required", "minlength". Those work fine. But the message for Custom validation is not quite clear.
Async validator should be in the next parameter
userName: ['',
[ Validators.required, Validators.minLength(3) ],
[ this.validateUserName.bind(this) ]
]
Also its better to have a factory method with required dependencies that would create validator, instead of using 'bind(this)'
userNameValidator(originalUsername, individualUpdateService) {
return (control: FormControl): Promise<any> => {
return new Promise<any>((resolve, reject) => {
if (originalUsername != undefined) {
individualUpdateService.isUserNameExisting(originalUsername, control.value).subscribe(
(res) => {
if (res.isUserNameExisting) {
console.log("existing");
resolve({ 'existing': true });
} else {
console.log("NOT existing");
resolve(null);
}
},
(error) => {
console.log(error);
}
);
} else {
resolve(null);
}
})
}
}
userName: ['',
[ Validators.required, Validators.minLength(3) ],
[ this.userNameValidator(this.oldUserNameForCheck, this._individualUpdateService) ]
]
I'm a little uncertain of this approach when updating an existing or adding a new object to a redux store but am having trouble getting this to work using the accepted methods i.e. Object.assign, update() or spread operators. I can get it working as follows:
const initialState = {
cart: []
}
export default function cartReducer(state = initialState, action) {
switch (action.type) {
case ADD_TO_CART:
let copy = _.clone(state.cart);
let cartitem = _.find(copy, function (item) {
return item.productId === action.payload.productId;
});
if (cartitem) {
cartitem.qty = action.payload.qty;
} else {
copy.push(action.payload);
}
return {
...state,
cart: copy
}
default:
return state
}
}
Although this works, I'm using Underscore to copy the state and check whether the item already exists in state which seems unnecessary and overkill?
This is the code for the redux. Use the .find function to find if an element is already in the array.
Example Code:
const inCart = state.cart.find((item) =>
item.id === action.payload.id ? true : false
);
return {
...state,
cart: inCart
? state.cart.map((item) =>
item.id === action.payload.id
? { ...item, qty: item.qty + 1 }
: item
)
: [...state.cart, { ...item, qty: 1 }],
};
With Redux-Toolkit you can mutate the state objects so you can likely simplify this a bit.
const basket = createSlice({
name: "cart",
initialState,
reducers: {
addToCart: (state, { payload }) => {
const inCart= state.cart.find((item) => item.id === payload.id);
if (inCart) {
item.qty += payload.qty;
} else {
state.items.push(payload);
}
},
},
});