I try to use a service in a custom validator to check if a username already exists.
import {Component} from 'angular2/core';
import {
Control,
ControlGroup,
FormBuilder
} from "angular2/common";
import {CharacterService} from "./character-service";
#Component({
selector: 'register-character-form',
template: `
<h2 class="ui header">A new adventurer is coming...</h2>
<form (ngSubmit)="register()" [ngFormModel]="characterForm" class="ui form">
<div class="field">
<label>Nom</label>
<input ngControl="name">
</div>
<button type="submit" class="ui button">Enter in the adventure</button>
</form>
`,
providers: [CharacterService]
})
export class RegisterCharacterFormCmp {
characterForm: ControlGroup;
name: Control;
constructor(private _characterService: CharacterService, fb: FormBuilder) {
this.name = fb.control('', this.characterNameValidator);
this.characterForm = fb.group({
name: this.name
});
}
register(): void {
//TODO: To implement
}
// Not works, this binds the control
characterNameValidator(control: Control) {
return this._characterService.isCharacterNameAlreadyExists(control.value) ? {nameCharacterAlreadyExistsError: true} : null;
}
}
It doesn't work. In the characterNameValidator, 'this' references the control and not my class. The service is undefined. How can I use my service in the validator ?
More globally, how can I pass arguments in a custom validator ?
You need to control what this means in your validation. You can do so with bind
this.name = fb.control('', this.characterNameValidator.bind(this));
Everything else should work as expected then.
You can use an arrow function to keep the context the same.
this.name = fb.control('', (control: Control) => this.characterNameValidator(control));
Related
Looking for some insight with the Redux Form handleSubmit function. I am following along to Set Griders course from Udemy. Everything is running smoothly until here. Other error/bugs I was able to research and solve them myself. I am really struggling to find a solution here.
singin.js
import React, { Component } from 'react'
import { reduxForm } from 'redux-form'
import { Checkbox, Form, FormField, Button, Grid, GridColumn, Input } from 'semantic-ui-react'
class SignIn extends Component {
onFormSubmitHandler({ email, password }) {
console.log('Values', email, password)
}
render() {
// this.props are brought to us by reduxForm. Allowing to grab the field inputs and submit action
const { handleSubmit, fields: { email, password } } = this.props
return (
<Grid centered columns={2}>
<GridColumn>
<Form onSubmit={handleSubmit(this.onFormSubmitHandler.bind(this))}>
<FormField>
<label>Email:</label>
<Input placeholder='Email' {...email} />
</FormField>
<FormField>
<label>Password:</label>
<Input placeholder='Password' {...password} />
</FormField>
<FormField>
<Checkbox label='I agree to the Terms and Conditions' />
</FormField>
<Button action='submit'>Sign In</Button>
</Form>
</GridColumn>
</Grid>
)
}
}
export default reduxForm({
form: 'signin',
fields: ['email', 'password']
})(SignIn)
rootReducer
import { combineReducers } from 'redux'
import SetsReducer from './SetsReducer'
import { reducer as form } from 'redux-form'
const rootReducer = combineReducers({
sets: SetsReducer,
form
})
export default rootReducer
The console.log inside onFormSubmitHandler logs: 'Values undefined undefined'
Edit: Project on GitHub
I solved the issue. The fix involved importing 'Field' from redux-form and replacing the input element with elements. Passing {Input} as the component to render
I am trying to retrieve a single input value and log it. This is an edit form with existing name value prop.
I am not setting state 'name' with field input's value for some reason. I am not sure how to structure the connect part which I think is my problem. In particular, I am not clear on how to write mapStateToProps to include both my non-form state and form state.
partial scaled down code:
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { connect } from 'react-redux';
import { updateStable } from '../../actions/index';
const selector = formValueSelector('myEditForm');
class EditStuff extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name
};
}
componentDidMount() {
this.props.initialize({
name: this.props.name || ''
});
}
componentDidUpdate() {
// this.state.name is not getting set from input value
this.props.updateLocalActiveStuffData(this.state.name);
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Name"
name="name"
class="name"
type="text"
component={renderField}
/>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
displayEditForm: state.displayEditForm, //my own non-form related state
name: selector(state, 'name') //form input 'name'
};
}
export default connect(mapStateToProps, { updateStuff })(
reduxForm({
form: 'myEditForm'
})(EditStuff)
);
I think if you can't retrieve the value it's because the name of your form in reduxForm and the formValueSelector name are different 'StableEditForm' != 'myEditForm'
You have more info on selectors here
If you want to initialise your form with values, you should set it from your state in mapStateToProps with the initialValues props, something like this:
function mapStateToProps(state) {
return {
initialValues: state.displayEditForm, // value of your form
};
}
A great exemple here
I hope this can help you
I'm not sure where I'm going wrong with ionViewDidLeave. I'm getting an error from the terminal that says "cannot find name ionViewDidLeave". Is there something I have to import to use it? I have already imported the navController.
Here is my
ts.file
import { Component } from '#angular/core';
import { NavController, ModalController } from 'ionic-angular';
import { EditPost } from '../edit-post/edit-post';
import { LoadingController } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class Home {
buttonColor: string = '#787083';
constructor (public navCtrl: NavController, public modalCtrl: ModalController, public loading: LoadingController) {
//OTHER FUNCTIONS
/*Navigate to edit page */
editPost(){
this.buttonColor = '#553481'; //change button background color on click
this.navCtrl.push(EditPost, {})
.catch(() => {
// Page requires authentication, re-direct to Login page
this.navCtrl.setRoot(Login, {routeToPage: 'EditPost'});
});
ionViewDidLeave(){
this.buttonColor = '#787083';
};
}// end of editPost()
}//close class
HTML
<ion-footer class="footer">
<ion-segment small class="footer">
<ion-segment-button id="post" value="post" (click)="postEvent()" [ngStyle]="{'background-color': buttonColor}" small> <span class="footer">NEW POST</span></ion-segment-button>
<ion-segment-button id="edit" value="edit" (click)="editPost()" [ngStyle]="{'background-color': buttonColor}" small> <span class="footer">Edit Post</span></ion-segment-button >
</ion-segment>
</ion-footer>
When you write inside an method
ionViewDidLeave()
you are calling a function from the current scope (editPost) function. The right way to call from the object would be:
this.ionViewDidLeave()
but I guess it's not right to call it (ionViewDidLeave is part of Ionic's page lifecycle), and I guess too that what you want to do is define this method and you have a type in your code. The right code should be:
export class Home {
buttonColor: string = '#787083';
constructor (public navCtrl: NavController, public modalCtrl: ModalController, public loading: LoadingController) {
editPost(){
this.buttonColor = '#553481'; //change button background color on click
this.navCtrl.push(EditPost, {})
.catch(() => {
// Page requires authentication, re-direct to Login page
this.navCtrl.setRoot(Login, {routeToPage: 'EditPost'});
});
}// end of editPost()
ionViewDidLeave(){
this.buttonColor = '#787083';
};
}//close class
I'm trying to show an image get from a remote server with angular 2.
In my component I have an object that is an "university_info" object that is my model.
export class myClass
{
university_info : university_info;
}
myFunction()
{
this.university_info = new university_info(responseFromServer[image])
}
export class university_info
{
imageBase64 : string
constructor(image : string)
{
this.imageBase64 = image
}
}
All is working fine. I get base64 image but when I try to show it in HTML in this way :
<img [src]="'data:image/jpg;base64,'+university_info.imageBase64" />
That's is what I get :
I feel like this thread lacks concrete examples which made me have some difficulties:
Import DomSanitizer:
import { DomSanitizer } from '#angular/platform-browser';
define in constructor:
constructor(private _sanitizer: DomSanitizer) { }
Sanitize the Base64 string you want to pass as your image source (use trustResourceUrl):
this.imagePath = this._sanitizer.bypassSecurityTrustResourceUrl('data:image/jpg;base64,'
+ toReturnImage.base64string);
Bind to html:
<img [src]="imagePath">
Solution: Just use 'data:image/jpg;base64' into your image tag like this
<img src="{{'data:image/jpg;base64,' + imagePath}}" />
You can try to use _sanitizer.bypassSecurityTrustUrl to tell angular src value is safe. Take a look at this class from angular:
class DomSanitizationService {
sanitize(context: SecurityContext, value: any) : string
bypassSecurityTrustHtml(value: string) : SafeHtml
bypassSecurityTrustStyle(value: string) : SafeStyle
bypassSecurityTrustScript(value: string) : SafeScript
bypassSecurityTrustUrl(value: string) : SafeUrl
bypassSecurityTrustResourceUrl(value: string) : SafeResourceUrl
}
and be low an example for safe html:
export class AppComponent {
private _htmlProperty: string = '<input type="text" name="name">';
public get htmlProperty() : SafeHtml {
return this._sanitizer.bypassSecurityTrustHtml(this._htmlProperty);
}
constructor(private _sanitizer: DomSanitizationService){}
}
Please find enclosed a proper example of how to upload an image in Base64 in Angular 2/4 and also its display. the actual base64 string is dumped into the debugger console and of course can be stored in database etc.
import { Component, OnInit } from '#angular/core';
// Base 64 IMage display issues with unsafe image
import { DomSanitizer } from '#angular/platform-browser';
#Component({
selector: 'app-test',
template: `
<h1>Test 001 </h1>
<div class="form-group">
<label>Image</label>
<input type="file" class="form-control" accept="application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint,
text/plain, application/pdf, image/*" (change)="changeListener($event)">
</div>
<img *ngIf="base64Image" [src]="domSanitizer.bypassSecurityTrustUrl(base64Image)" />
`,
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
private base64Image: string;
constructor(private domSanitizer: DomSanitizer) { }
ngOnInit() {
}
changeListener($event): void {
this.readThis($event.target);
}
readThis(inputValue: any): void {
var file: File = inputValue.files[0];
var myReader: FileReader = new FileReader();
myReader.onloadend = (e) => {
this.base64Image = myReader.result;
console.log(this.base64Image);
}
myReader.readAsDataURL(file);
}
}
You have to make sure that university_info.imageBase64 is a string type then you code will work.
DEMO : http://plnkr.co/edit/pI35tx9gXZFO1sXj9Obm?p=preview
import {Component,ViewChild,Renderer,ElementRef,ContentChildren} from '#angular/core';
#Component({
selector: 'my-app',
template: `
<img [src]="'data:image/jpg;base64,'+imagePath"/>
`
})
export class App {
imagePath:string="iVBORw0KG...";
}
I would like to propose an alternative solution that builds on the one provided by #gatapia.
The proposed solution is to use the #ViewChild decorator tag (see docs here https://angular.io/docs/ts/latest/api/core/index/ViewChild-decorator.html), to retrieve the element reference within the component, and set the value directly as illustrated in the code snippet below. Important to note that the element being referenced via the ViewChild should be bound with to a local variable using the #, as illustrated in the code snipped below.
Also as the ElementRef docs explains, using the ElementRef directly still exposes risk of XSS also present when using DomSanitizer.
#Component({
template: `
<div>
<img #imgRef> // Note that the #imgRef reference is required to be identified by Angular
</div>
`,
})
export class MyComponent implements OnInit {
src:string;
#ViewChild('imgRef') img:ElementRef;
constructor() {
// In your case, this will be resolved from the server
this.src = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNTAgMjUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojREQwMDMxO30NCgkuc3Qxe2ZpbGw6I0MzMDAyRjt9DQoJLnN0MntmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTI1LDMwIDEyNSwzMCAxMjUsMzAgMzEuOSw2My4yIDQ2LjEsMTg2LjMgMTI1LDIzMCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAJIi8+DQoJPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIxMjUsMzAgMTI1LDUyLjIgMTI1LDUyLjEgMTI1LDE1My40IDEyNSwxNTMuNCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAxMjUsMzAgCSIvPg0KCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMjUsNTIuMUw2Ni44LDE4Mi42aDBoMjEuN2gwbDExLjctMjkuMmg0OS40bDExLjcsMjkuMmgwaDIxLjdoMEwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMQ0KCQlMMTI1LDUyLjF6IE0xNDIsMTM1LjRIMTA4bDE3LTQwLjlMMTQyLDEzNS40eiIvPg0KPC9nPg0KPC9zdmc+DQo=';
}
ngOnInit() {
// Sets the value of the element
this.img.nativeElement.src = this.src;
}
}
The following plunkr provides a working code snippet of the above https://plnkr.co/edit/JXf25Pv8LqrFLhrw2Eum?p=preview
This question gets high google ranking so I thought I'd put my findings here. Using data binding to set the [src] property of an image can be problematic especially on some older mobile devices. So if you have performance issues with the sanitizer+binding approach you will have to set the src property using the DOM directly:
constructor(private el: ElementRef) {}
...
public imageChanged(base64: string) {
const im: HTMLImageElement = this.el.nativeElement.querySelector('#imgid');
im.src = data;
}
This may be ugly but its lightning fast.
I've been trying to implement a form in MapsAddrForm.jsx using Redux-Form and I can't seem to change the value of my input element. When the page loads, the input element does not respond to keyboard input, and when the form field submits, it returns an empty object to the parent component DistrictFinder. Beyond these two files, I've also added form:formReducer as an argument to combineReducers much like the simple example in the Redux-Form tutorials. Is there any way to restore the ability for the DistrictFinder to receive data objects from the address form? For reference, I'm using React 15.1.0, React-redux 4.4.5, ES6, and Redux-Form 5.3.1, all compiled using Webpack.
MapsAddrForm.jsx
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {reduxForm} from 'redux-form';
class MapsAddrForm extends Component {
constructor(props) {
super(props);
}
render() {
const {fields: {address,address2}, handleSubmit} = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<input type="text" placeholder="Your address" {...address}/>
</div>
<button type="submit">Submit</button>
</form>
);
}
}
export default reduxForm({
form: 'addressForm',
fields: ['address']
})(MapsAddrForm);
DistrictFinder.jsx
import React, { Component, PropTypes } from 'react'
import MapsAddrForm from './MapsAddrForm.jsx'
import { connect } from 'react-redux'
import { changeAddress } from '../actions/index.jsx'
class DistrictFinder extends Component {
constructor(props) {
super(props);
this.handleAddrSubmit = this.handleAddrSubmit.bind(this);
}
handleAddrSubmit(data) {
console.log("Address received: " + JSON.stringify(data));
}
render() {
const {address, district} = this.props
return (
<div class="GMaps">
<h1>Find your district!</h1>
<MapsAddrForm onSubmit={this.handleAddrSubmit} />
<p>My district number is: {district}</p>
</div>
);
}
}
DistrictFinder.propTypes = {
district: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired
};
function mapStateToProps(state) {
const { district } = state.infoChange;
return {
district
};
};
export default connect(mapStateToProps)(DistrictFinder);
I ran into this as well on redux-form#6.2.0
After looking at the source for one of the examples, I noticed the call to combineReducers has an explicit key "form" to the object argument. When I added this explicit key, the fields became editable.
// Correct
const reducer = combineReducers({
form: reduxFormReducer // mounted under "form"
})
If you have an existing app, like I do, you likely have this style es6 syntax from the various redux starters.
// Incorrect: results in the witnessed bad behavior
const reducer = combineReducers({
reduxFormReducer
})
See the combineReducers call at https://github.com/erikras/redux-form/blob/master/examples/simple/src/index.js#L10
It'd be interesting to see if this could be a constant that could be passed in and leveraged by the lib. "form" feels like a easily corruptible "namespace".