I am having trouble creating an update component that reads the passed in id from the url, makes a get request, and populates a reactive form. I can confirm that the get request is returning what it should in the network tab of the browser.
In my service:
productUrl= 'http://localhost:8080/api/products';
getProduct(id: number): Observable<Product> {
const url = `${this.productUrl}/${id}`;
return this.http.get<Product>(url);
}
In my component:
product: Product;
productForm= this.fb.group({
name: ['', Validators.required],
price: ['', Validators.required]
});
ngOnInit() {
this.getProduct();
console.log(this.product);
this.productForm.controls['name'].setValue(this.product.name);
this.productForm.controls['price'].setValue(this.product.price);
}
getProduct(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.productService.getProduct(id)
.subscribe(product=> this.product = product);
}
The problem is you are setting the data to form before it comes from the backend, (subscribe is asynchronous, which means the setvalue functions will execute while the subscribe function is in the process )the best way to do is to trigger the setValue/patch function when the data has arrived from the backend like this
getProduct(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.productService.getProduct(id)
.subscribe(product=> {
this.product = product;
console.log(this.product);
this.productForm.patchValue({
price: this.product.price
name: this.product.name
});
}
);
}
I think you are setting the from before the data is come form server so you should set the form after data come form server as follows:
product: Product;
productForm= this.fb.group({
name: ['', Validators.required],
price: ['', Validators.required]
});
ngOnInit() {
this.getProduct();
}
getProduct(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.productService.getProduct(id)
.subscribe(product=> {
this.product = product;
console.log(this.product);
this.productForm.controls['name'].setValue(this.product.name);
this.productForm.controls['price'].setValue(this.product.price);
}
);
}
you can pathValue wth the value
this.productService.getProduct(id)
.subscribe(product=> {
this.product = product;
if (this.productForm) {
this.productForm.patchValue(this.product);
// this.productForm.patchValue(this.product, {emitEvent: false}); if you don't want valueChange on form be fired
}
});
Related
I've got a reactive form that I'm initializing oninit() like this, along with a couple other properties I'm using to grab the passed id out of the URL and tell whether or not the form is being used to update, or create a new entry in a mysql table. The issue I'm having is in using patchValue to pass the data returned from my service into my form:
component.ts
export class formComponent implements OnInit, AfterViewInit {
constructor(
private dataService: dataService,
private route: ActivatedRoute,
private router: Router,
private formBuilder: FormBuilder,
private ticketModel: ticketModel,
) {}
Form!: FormGroup;
isNewMode!: boolean;
id!: string;
ticket!: ticketModel[];
ngOnInit(){
this.id = this.route.snapshot.params['id'];
this.isNewMode = !this.id;
this.Form = this.formBuilder.group({
field1: ['', Validators.required],
field2: ['', Validators.required],
field3: ['', Validators.required],
field4: ['', Validators.required]
});
}
ngAfterViewInit(){
if(!this.isNewMode){
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe({
next: ticketData => {
this.ticket = ticketData;
},
});
this.Form.patchValue({
field1: this.ticket.field1, //error, "Property 'field1' does not exist on type 'ticketModel[]'"
field2: this.ticket.field2, //error, "Property 'field2' does not exist on type 'ticketModel[]'"
field3: this.ticket.field3, //error, "Property 'field3' does not exist on type 'ticketModel[]'"
field4: this.ticket.field4, //error, "Property 'field4' does not exist on type 'ticketModel[]'"
});
}
}
}
ticketModel.ts
export class ticketModel {
id: string = '';
field1: string = '';
field2: string = '';
field3: string = '';
field4: string = '';
}
service.ts
export class dataService {
constructor(private errorHandlerService: errorHandlerService, private http: HttpClient) {}
private url = "/api/tickets";
httpOptions:{ headers: HttpHeaders } = {
headers: new HttpHeaders({ "Content-Type": "application/json" }),
};
getById(id: string): Observable<ticketModel[]> {
return this.http
.get<ticketModel[]>(`${this.url}/${id}`, {responseType: "json"})
.pipe(tap((_) => console.log('returned by service: ', JSON.stringify(_))),
catchError(
this.errorHandlerService.handleError<ticketModel[]>("fetchAll", [])
)
);
}
and just in case it's helpful, this is an example of the response json I'm getting when this method is run
[{"id":18,"field1":"string data","field2":"data is here","field3":"another string goes here","field4":"this is another example string"}]
if there isn't an id that gets passed in, isNewMode is true and the form is initialized with blank values, works fine from there. When an id is passed in, I'm passing that to a method in the data service to query the database and return just that row. This also seems to work fine as I'm able to log that data in json format to the console. I just can't figure out how to get the data to patch into the form after trying this out a few different ways.
Currently, the way that I think this should work which is what this code is an example of, in patchValue() the compiler throws an error that "property field1 does not exist on type ticketModel[]" when it absolutely does exist as a property on that model.
I feel like I'm probably missing something pretty small and any help in figuring out what would be wildly appreciated, thank you!
You have declared ticket!: ticketModel[] as an Array type.
Your response is also an array -
[{"id":18,"field1":"string data","field2":"data is here","field3":"another string goes here","field4":"this is another example string"}]
then why are you not treating this.ticket as an array here ?
field1: this.ticket.field1,
Either use it this way - field1: this.ticket[0].field1 or use for loop on it to get the field1 and other values from this.
And you need to patch the form inside the subscribe block, because it's an async operation.
really your service getById should return one 'TicketModel' nor an array of TicketModel. better than mannage in the component, mannage in the service using map
//see that is an observable of "ticketModel"
getById(id: string): Observable<ticketModel> {
//you needn't use {responseType:'json'}, by defect Angular expect a Json
return this.http
.get<ticketModel[]>(`${this.url}/${id}`)
.pipe(
//just return the uniq element of the array
map(res=>res.length?res[0]:null),
tap((_) => console.log('returned by service: ', JSON.stringify(_))),
catchError(
this.errorHandlerService.handleError<ticketModel>("fetchAll", [])
)
);
}
Futhermore, you need use the "patchValue" inside subcription function, and you can use the patchValue wihout create a new object because has the same properties
if(!this.isNewMode){
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe({
next: ticketData => {
this.ticket = ticketData; //<--really you needn't use ticketData
//unless you want compare old and new Values
//here you make the patchValue
this.form.patchValue(ticketData);
})
}
(you can also put in ngOnInit instead of ngAfterViewInit)
Update another aproach to the "clasic" problem to create a component to edit/create an element.
If you has a function like
getForm(data:TicketModel=null){
data=data || {} as TicketModel
return new FormGroup({
id: new FormControl(data.id,Validators.required),
field1: new FormControl(data.field1,Validators.required),
field2: new FormControl(data.field2,Validators.required),
field3: new FormControl(data.field3,Validators.required),
field4: new FormControl(data.field4,Validators.required)
})
}
You can in ngOnInit make some like
ngOnInit(){
this.id = this.route.snapshot.params['id'];
this.isNewMode = !this.id;
if (this.isNewMode)
this.Form=this.getForm()
else
{
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe(res=>{
this.Form=this.getForm(res)
})
}
}
After reading so many posts and empty(or not?) solutions, I figured that the best thing was to post of my own. So my goal is just to get the displayName of the user so in a list of posts or whatever, the authorship doesnt look ugly with a uid...
Here's what I find relevant for the problem:
Signup reactive form, later called on onInit:
createForm() {
this.registerForm = this.fb.group(
{
name: ['', Validators.required],
email: ["", [Validators.required]],
password: [null, [Validators.required, Validators.minLength(5)]],
retype: ["", Validators.required]
},
{
validator: PasswordValidators.passwordsShouldMatch
}
);
}
The submit method:
onSubmit() {
if (this.registerForm.invalid) return;
this.registered = true
this.authService.registerUser(this.email.value, this.password.value).then(data=>{
console.log(data)
})
}
The authService calling the uid and the register method:
constructor( private afAuth: AngularFireAuth, private router: Router) {
this.afAuth.authState.subscribe(user=>{
this.userId = user.uid;
})
}
registerUser(email:string, password:string) {
return this.afAuth.auth.createUserWithEmailAndPassword(email, password)
.then((result) => {
this.router.navigate(['/login']);
console.log(result.user)
}).catch((error) => {
window.alert(error.message)
})
}
When logging this line this.afAuth.authState.subscribe(user=>{
this.userId = user.uid;, actually logging the user, i can see the whole object and the displayName, but since the register method only accepts 2 arguments, email and pass, how to I workaround this? I've tried an uproach dealing with updateprofile but I got stuck on an error...Is there any solution? Thank you
If you wan to set the display name of a user account, you won't be able to do that at the time of account creation using createUserWithEmailAndPassword. You'll have to perform a followup call to updateProfile() on the user object you get back.
result.user.updateProfile({
displayName: ...
})
.then(...)
.catch(...)
You will obviously have to pass along the name in the call to registerUser().
I'm practicing some Angular/Ionic and am having a bit of a hard time figuring out how i get all offresList of all users
this is my database look like :
This is my providre offre.ts
export class OffreProvider {
public offreListRef: firebase.database.Reference;
constructor() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.offreListRef = firebase
.database()
.ref(`/userProfile/${user.uid}/offreList`);
}
});
}
createOffre(
offreTitre: string,
offreSecteur: string,
offreVille: string,
offreDispo: string,
offreDescrip: string,
offreDate : string,
): firebase.database.ThenableReference {
return this.offreListRef.push({
titre: offreTitre,
secteur: offreSecteur,
ville: offreVille,
dispo: offreDispo,
descrip: offreDescrip,
date : offreDate
});
}
getOffreList(): firebase.database.Reference {
return this.offreListRef;
}
}
and this is how i get all offres of currentUser
mesOffres.ts
export class MesOffresPage {
public offreList: Array<any>;
constructor(public navCtrl: NavController, public navParams: NavParams,
public offreProvider: OffreProvider) {
}
creatOffre(): void {
this.navCtrl.push('CreateOffrePage');
}
ionViewDidLoad() {
this.offreProvider.getOffreList().on("value", offreListSnapshot => {
this.offreList = [];
offreListSnapshot.forEach(snap => {
this.offreList.push({
id: snap.key,
titre: snap.val().titre,
secteur: snap.val().secteur,
ville: snap.val().ville
});
return false;
});
});
}
}
now how i can get all offreslist of all Users with alloffres.ts
and display it in alloffres.html
I m stacking for 2 days
To get the offers across all users, you will have to load all user data. Something like this:
ionViewDidLoad() {
let ref = firebase.database().ref("userProfile");
ref.on("value", userListSnapshot => {
this.offreList = [];
userListSnapshot.forEach(userSnapshot => {
let offerListSnapshot = userSnapshot.child("offreList");
offerListSnapshot.forEach(offerSnapshot => {
this.offreList.push({
id: snap.key,
titre: snap.val().titre,
secteur: snap.val().secteur,
ville: snap.val().ville
});
});
})
});
}
Note that this code also loads the profile data for each user, which the it doesn't use. This is one of the many reasons why experienced Firebase developers recommend to keep separate entity types in separate top-level lists. So in your case, I'd recommend having:
userProfiles: {
uid1: {
...
},
uid2: {
...
}
},
userOffers: {
uid1: {
"-LRZ...": {
...
}
},
uid2: {
"-LRQ...": {
...
}
}
}
With the above structure you can load just the offers for all users with code very similar to what I shared above. But with the updated structure, you won't be loading the user profile data unnecessarily. And just in case you need the profile data and offers for a user, you can easily load both in separate calls. The performance will be very similar, since Firebase pipelines the requests over a single connection.
I have a basic angular component that allows some one to edit the details of a user once they go to their profile and click on "edit".
Component:
export class EditUserComponent implements OnInit {
// Define our vars
user: Users[];
editUserForm: FormGroup;
message: {};
displayMessage = false;
userID: number;
errorMessage: any = '';
constructor(
private fb: FormBuilder,
private _userService: UserService,
private activatedRoute: ActivatedRoute
) {
}
ngOnInit(): void {
// Get the userID from the activated route
this.activatedRoute.params.subscribe((params: Params) => {
this.userID = params['id'];
});
// Call our service and pass the UserID
this._userService.getUser(this.userID)
.then(res => {
this.user = res;
this.createForm();
});
}
// Generate the form
createForm() {
this.editUserForm = this.fb.group({
QID: ['', Validators.required],
favoriteColor: [''],
favoriteNumber: [''],
favoriteActor: ['']
});
}
}
Service:
// Fetch a single user
getUser(userID: number) {
return this._http.post(this.baseUrl + '/fetchUser', { "userID": userID }, { "headers": this.headers })
.toPromise()
.then(res => res.json())
.catch(err => { this.handleError(err); });
}
Interface:
export interface Users {
RecordID?: number;
QID: string;
favoriteColor?: string;
favoriteNumber?: number;
favoriteActor?: string;
}
I am trying to pass the values to my formGroup but I am having trouble figuring out how to access the values.
I assumed I could do something like this where I could access the user model and select a property from it but that is throwing an undefined error.
Would I pass the values here in the form group or bind them to the elements directly somehow? I am receiving the data back from the service just fine, just not sure how to get each of the values back to their respective fields.
createForm() {
this.editUserForm = this.fb.group({
QID: [this.user.QID, Validators.required],
favoriteColor: [''],
favoriteNumber: [''],
favoriteActor: ['']
});
}
If I understand correctly ... this is what my code looks like:
onProductRetrieved(product: IProduct): void {
if (this.productForm) {
this.productForm.reset();
}
this.product = product;
// Update the data on the form
this.productForm.patchValue({
productName: this.product.productName,
productCode: this.product.productCode,
starRating: this.product.starRating,
description: this.product.description
});
this.productForm.setControl('tags', this.fb.array(this.product.tags || []));
}
I'm using patchValue for the values and setControl for the array.
OR
Since you are creating the form after retrieving the data, you could do something like this:
createForm() {
this.editUserForm = this.fb.group({
QID: [this.user.QID, Validators.required],
favoriteColor: [this.user.favoriteColor],
favoriteNumber: [this.user.favoriteNumber],
favoriteActor: [this.user.favoriteActor]
});
}
AND just to be complete ... each input element needs a formControlName property like this:
<input class="form-control"
id="productNameId"
type="text"
placeholder="Name (required)"
formControlName="productName" />
<span class="help-block" *ngIf="displayMessage.productName">
{{displayMessage.productName}}
</span>
</div>
You can find a complete working example here: https://github.com/DeborahK/Angular2-ReactiveForms
Bind a submit event to your form, then use this.editUserForm.value to access the data from the form.
In the component template:
<form [formGroup]="editUserForm" (submit)="saveIt()">
In the Component:
saveIt() {
if (this.editUserForm.dirty && this.editUserForm.valid) {
alert(`Number: ${this.editUserForm.value.favoriteNumber} Actor: ${this.editUserForm.value.favoriteActor}`);
}
}
i have a trouble.
here i let my code.
https://gist.github.com/anonymous/b651408a8419f13a949d719e6b87d8ea
in my app i connect to the firebase cloud message service, in the appComponent i listen the messages that firebise send and emit the data content whit the DataInterchage.service, in the chatComponent i suscribe to the event emited and i process the data.
the problem is the next. when I receved the data, i set the this.messeges variable the data content but the view dont update.
what do you believe that be?
when you set this.messages your code might be running outside the angular because that code is written in service callback. that is why when you assign values to variable it doesn't update the view.
try running code inside the angular NgZone. after that your view will be updated successfully.
for your code snippet will be
import {NgZone,ChangeDetectorRef} from "#angular/core";
export class ChatComponent implements OnInit{
constructor(
private zone: NgZone,
private cd: ChangeDetectorRef,
) {}
ngOnInit() {
this.user = JSON.parse(appStorage.getString("user_info"));
this.me = {
id: this.user.id,
name: this.user.full_name,
pictureUrl: this.user.icon
};
this.other = {
id: "",
name: "",
pictureUrl: "",
coverUrl: ""
};
this.emitter.msgRecived$
.subscribe(data => {
data = JSON.parse(data);
this.http.get(`${ env['api_route'] }/api/users/${ data.user }`)
.subscribe((res: Response) => {
let user = res.json().data;
this.other = {
id: user.id,
name: user.full_name,
pictureUrl: user.icon,
};
this.zone.run(()=>{
this.messages.push({
sender: this.other,
content: data.message,
date: data.date
});
});
console.dump(this.other);
console.dump(this.messages)
}, (err: Response) => {
this.oauth.isLogged(err);
});
});
}
}