Angular 8: Send Form data as a Service to Another Component - html

How do I sent all the Formgroup data as a Service in Angular to Another Component? I am looking for alternative to ControlValueAccessor. So anytime someone writes something on a form, the receiver will get the value data.
Trying to edit the code below to work.
Address Sender Form:
export class AddressFormComponent implements OnInit {
editAddressForm: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.editAddressForm = this.formBuilder.group({
'streetName' : [null, Validators.required, Validators.maxLength(64)],
'city' : [null, Validators.required, Validators.maxLength(32)],
'state' : [null, Validators.required, Validators.maxLength(16)],
'postalCode' : [null, Validators.required, Validators.maxLength(16)]
});
}
ngOnInit() {
}
// What should I write as Event Code??
}
Service:
export class AddressService {
private messageSource = new Subject();
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(currentMessage) {
this.messageSource.next(currentMessage);
}
}
Receiver:
export class AddressCombinedReceiverComponent implements OnInit {
message: any;
constructor(private readonly addressService: AddressService) {
this.addressService.currentMessage.subscribe(currentMessage => this.message = currentMessage);
}
ngOnInit() {
}
}
Related question: Angular 2 - formControlName inside component

This doesnt look far away from what you need, in your AddressFormComponent try the following. Ensure to clean up the subscription when needed, added below.
public subs = new Subscription();
public editAddressForm: FormGroup;
constructor(addressService: AddressService){}
public ngOnInit(): void {
this.editAddressForm = this.formBuilder.group({
'streetName' : [null, Validators.required, Validators.maxLength(64)],
'city' : [null, Validators.required, Validators.maxLength(32)],
'state' : [null, Validators.required, Validators.maxLength(16)],
'postalCode' : [null, Validators.required, Validators.maxLength(16)]
});
this.subs.add(this.editAddressForm.valueChanges.subscribe(data =>
{
this.addressService.changeMessage(this.editAddressForm);
}));
}
public ngOnDestroy(): void
{
this.subs.unsubscribe();
}
That should in turn push the data to the subject you have an in turn cause the subscribers of that subject to receive the data. To confirm try the following
export class AddressCombinedReceiverComponent implements OnInit {
public message: any;
constructor(private readonly addressService: AddressService) {}
public ngOnInit() {
this.addressService.currentMessage.subscribe(currentMessage => {
this.message = currentMessage;
console.log(this.message); // should be the form coming through
});
}
}

You don't need to respond to an event. FormGroups already have an observable that streams the changes.
Example
// address-form.component.ts
export class AppComponent implements OnDestroy {
editAddressForm: FormGroup;
ngOnDestroy() {
// cleanup
this.addressService.formValues$ = null;
}
constructor(private addressService: AddressService) {
this.editAddressForm = new FormGroup({
streetName: new FormControl(null, { validators: [Validators.required, Validators.maxLength(64)]}),
city: new FormControl(null, { validators: [Validators.required, Validators.maxLength(32)]}),
state: new FormControl(null, { validators: [Validators.required, Validators.maxLength(16)]}),
postalCode: new FormControl(null, { validators: [Validators.required, Validators.maxLength(16)]}),
});
this.addressService.formValues$ = this.editAddressForm.valueChanges;
}
}
// address.service.ts
export class AddressService {
formValues$: Observable<any>;
constructor() { }
}
In regards to the receiver, if the intention is to show the form changes in the template, then I recommend using the async pipe for that, so that you only subscribe when it's defined, like:
// address-combined-receiver.component.ts
export class SimpleComponent implements OnInit {
addressValues$: Observable<any>;
constructor(private addressService: AddressService) {
this.addressValues$ = this.addressService.formValues$;
}
ngOnInit() {
}
}
<!-- address-combined-receiver.component.html -->
<ng-container *ngIf="addressValues$ | async as addressValues">
<pre>{{ addressValues | json }}</pre>
</ng-container>

Related

Type error Cannot find properties of undefined (reading id)

I'm at a lost here. I have a couple of services, one has been working for month and the newer one for some weeks, then all of a sudden a couple of days ago I start to get two console errors "Cannot read properties of undefined(reading id). I have not changed anything.
If I can fix one then I can understand the other because they both have the same error. When I did my research it talks about initializing.
This is an e-commerce site, when the user clicks on the heart it gets added to wishlistitems in my json file. The user should have the ability to go to favorites and it shows all of the wishlist items. As I stated it was working now I get errors. I have a two models(class) I have two components wishlist-list and wishlistitems the html will display the url for the images and I have a serive.
Here is the wish Model
import { Product } from './product';
export class Wish {
id:number;
productId: number;
productName: string;
description: string;
qty:number;
price: number;
imageUrl: string;
constructor(id:number, product:Product, qty=1, ){
this.id = id;
this.productId = product.id;
this.price = product.price;
this.productName = product.name;
this.qty = qty;
this.imageUrl = product.imageUrl;
this.description = product.description;
}
}
Here is the Product Model
export class Product {
id:number;
name: string;
description: string;
price: number;
imageUrl: string;
constructor(id:number, name, description="", price=0, imageUrl="" ){
this.id=id
this.name = name
this.description = description
this.price= price
this.imageUrl = imageUrl
}
}
Here is my Wishlist component
import { Component, OnInit, Input } from '#angular/core';
import {ProductService} from 'src/app/services/product.service'
import { MessengerService } from 'src/app/services/messenger.service';
import { WishlistService } from 'src/app/services/wishlist.service';
import { WishlistItemService } from '#app/services/wishlist-item.service';
import { Wish} from 'src/app/models/wish';
import {Product} from 'src/app/models/product';
#Component({
selector: 'app-wishlist-list',
templateUrl: './wishlist-list.component.html',
styleUrls: ['./wishlist-list.component.scss']
})
export class WishlistListComponent implements OnInit {
#Input() product: any;
productList: Product[]= [];
wishitemList: Wish[]= [];
wishItems = [];
constructor( private msg: MessengerService, private productService: ProductService,
private wishlistService: WishlistService,
private _wishlistitemService: WishlistItemService ) { }
ngOnInit(): void {
this.loadwishlistList()
}
loadwishlistList(){
alert("Loading wish list item");
this._wishlistitemService.getWishlistitem().subscribe((items: Wish[]) => {
console.log("A wish List Item" + items);
this.wishItems=items;
})
}
}
Here is my service
#Injectable({
providedIn: 'root'
})
export class WishlistItemService {
product:any;
id:number;
wishlistitemUrl = 'http://localhost:3000/wishlistitem'; //I have this in a api file
constructor(private http: HttpClient) { }
getWishlistitem(): Observable<Wish[]>{
return this.http.get<Wish[]>(wishlistitemUrl).pipe(
map((result: any[]) => {
let wishItems: Wish[] =[];
for(let item of result) {
let productExists = false
for(let i in wishItems){
if(wishItems[i].productId === item.product.id){ //this is line I get error on
wishItems[i].qty++
productExists = true
break;
}
}
if (!productExists){
wishItems.push(new Wish(item.id,item.product, item.name));
}
}
return wishItems;
})
);
}
}
I am at a lost here for it to work then stop working. I'm pulling my hair out, I tried making adjustments but nothing work. I read something about initializing, I'm in need of help.
Thanking You Advance
PH

Update method passes the old value instead of the new value in Angular

I am trying to make an Update method and everything works fine except for a value that is selected from a drop-down-list. When the PUT method is called, the API always receives the old value instead of the newly selected one.
This is the list I am talking about:
edituser.component.html
<mat-form-field>
<mat-label>Choose a role </mat-label>
<mat-select *ngIf="roles" [(ngModel)]="userRoleID" name="role">
<mat-option *ngFor="let role of roles" [value]="role.id" [(ngModel)]="el.userRoleID">{{role.role}}</mat-option>
</mat-select>
</mat-form-field>
edituser.component.ts
export class EdituserComponent implements OnInit {
id: string;
sub: any;
user: User[];
roles: Role[];
userRoleID: string;
constructor(private route: ActivatedRoute, private userService: UserService, private router:Router, private roleService:RoleService) {
this.roleService.getRoles().subscribe((result) => {
this.roles = result
this.useRoles(this.roles);
});
}
ngOnInit(): void {
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
console.log(this.id);
this.userService.getUserById(this.id).subscribe((result) => this.user = result);
});
}
updateUser(user: User){
this.userService.updateUser(user).subscribe(
(result: User) => {
this.userService.getUsers();
});
this.router.navigateByUrl("");
}
useRoles(roles:any)
{
console.log(roles);
}
}
Why does it keep passing the wrong, outdated value to the API when trying to update?

How to get a directive to react to an EventEmitter in a component

I have a CustomComponent which emits a value (let's just call it "error") if a http request to the back end api returns an error. How can I get a directive (call it Form Directive), applied to this form, to recognize when the "error" value is emitted by CustomComponent?
Code for CustomComponent:
export class CustomComponent extends FormComponent<Custom> {
constructor(
protected fb: FormBuilder,
private httpService: HttpService) {
super(fb);
}
currentVal: string = '';
inputType: string = 'password';
showPasswordTitle: string = 'Show Password';
showPasswordStatus: boolean = false;
form: FormGroup;
#Output() invalidOnError = new EventEmitter<string>();
protected buildForm(): FormGroup {
return this.form = this.fb.group({
fieldA: ['', Validators.required],
fieldB: ['', Validators.required],
fieldC: [''],
fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
}
protected doSubmit(): Observable<Custom> {
return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
};
protected get value(): Registration {
return {
fieldA: this.fieldA.value,
fieldB: this.fieldB.value,
fieldC: this.fieldC.value,
fieldD: this.fieldD.value
};
}
get fieldA() { return this.form.get('fieldA'); }
get fieldB() { return this.form.get('fieldB'); }
get fieldC() { return this.form.get('fieldC'); }
get fieldD() { return this.form.get('fieldD'); }
protected onError() {
if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
Object.keys(this.error).forEach(element => {
let formControl = this.form.get(this.error[element])
this.currentVal = formControl.value;
formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
formControl.updateValueAndValidity()
this.invalidOnError.emit('error');
})
}
}
Code for FormComponent:
export abstract class FormComponent<T> implements OnInit {
protected form: FormGroup = null;
submitted = false;
completed = false;
error: string = null;
constructor(protected fb: FormBuilder) {}
ngOnInit() {
this.form = this.buildForm();
}
onSubmit() {
this.submitted = true;
if (this.form.valid) {
this.doSubmit().subscribe(
() => {
this.error = null;
this.onSuccess();
},
err => {
this.error = err
this.onError();
},
() => {
this.submitted = false;
this.completed = true;
}
)
}
}
protected abstract get value(): T;
protected abstract buildForm(): FormGroup;
protected abstract doSubmit(): Observable<T>;
protected onSuccess() {}
protected onError() {}
}
Code for Form Directive (works well when user clicks Submit button, which triggers onSubmit event in CustomComponent):
#Directive({
selector: 'form'
})
export class FormSubmitDirective {
submit$ = fromEvent(this.element, 'submit').pipe(shareReplay(1));
constructor(private host: ElementRef<HTMLFormElement>) {}
get element() {
return this.host.nativeElement;
}
}
I was hoping something like this could be the solution to my question, but this for sure doesn't work.
invalidOnError$ = fromEvent(this.element, 'error').pipe(shareReplay(1));
The idea is to use submit$ or invalidOnError$ from the directive to focus on the first invalid field in the form. Works fine for submit$, but not invalidOnError$. Appreciate some help - still fairly new to Angular.
I got this to work in a round about manner, by using the #Input decorator in another form directive which also imports submit$ from Form Directive.
No changes to code for FormComponent and Form Directive vs. what's shown in the question.
Relevant code from Custom component:
export class CustomComponent extends FormComponent<Custom> {
invalidOnError: string = '';
form: FormGroup;
protected buildForm(): FormGroup {
return this.form = this.fb.group({
fieldA: ['', Validators.required],
fieldB: ['', Validators.required],
fieldC: [''],
fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
}
protected doSubmit(): Observable<Custom> {
invalidOnError = '';
return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
};
protected get value(): Registration {
return {
fieldA: this.fieldA.value,
fieldB: this.fieldB.value,
fieldC: this.fieldC.value,
fieldD: this.fieldD.value
};
}
get fieldA() { return this.form.get('fieldA'); }
get fieldB() { return this.form.get('fieldB'); }
get fieldC() { return this.form.get('fieldC'); }
get fieldD() { return this.form.get('fieldD'); }
protected onError() {
if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
invalidOnError = 'invalid'
Object.keys(this.error).forEach(element => {
let formControl = this.form.get(this.error[element])
this.currentVal = formControl.value;
formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
formControl.updateValueAndValidity()
this.invalidOnError.emit('error');
})
}
}
Relevant code from CustomComponentTemplate:
<form class="bg-light border" appFocus="FieldA" [formGroup]="CustomForm"
[invalidOnError]="invalidOnError" (ngSubmit)="onSubmit()">
Relevant code from invalidFormControlDirective (imports submit$ from Form Directive):
#Directive({
selector: 'form[formGroup]'
})
export class FormInvalidControlDirective {
private form: FormGroup;
private submit$: Observable<Event>;
#Input() invalidOnError: string = ''; //this is the #Input variable invalidOnError
constructor(
#Host() private formSubmit: FormDirective,
#Host() private formGroup: FormGroupDirective,
#Self() private el: ElementRef<HTMLFormElement>
) {
this.submit$ = this.formSubmit.submit$;
}
ngOnInit() {
this.form = this.formGroup.form;
this.submit$.pipe(untilDestroyed(this)).subscribe(() => {
if (this.form.invalid) {
const invalidName = this.findInvalidControlsRecursive(this.form)[0];
this.getFormElementByControlName(invalidName).focus();
}
});
}
ngOnChanges(){
of(this.invalidOnError).pipe(filter(val => val == 'invalid')).subscribe(() => {
if (this.form.invalid) {
const invalidName = this.findInvalidControlsRecursive(this.form)[0];
this.getFormElementByControlName(invalidName).focus();
}
});
}
ngOnDestroy() { }
// findInvalidControlsRecursive and getFormElementByControlName defined functions to get invalid controls references
}
That said, I'd be interested in 1) somehow bringing code under onChanges lifecyle into ngOnInit lifecyle in invalidFormControlDirective (couldn't get that to work), and 2) find out if there is some way to emitting an event and processing it with Rxjs fromEventPattern as opposed to passing the #Input variable invalidOnError into invalidFormControlDirective.

Angular error "Cannot read property 'firstName' of undefined"

Hi i am working on a search service to look for usernames in a elasticsearch database but i got the following error when i want to display the f.e. firstName of the user: Cannot read property 'firstName' of undefined.
I am working with Angular and Elasticsearch
service:
export class SearchService {
getElastic = 'http://34.62.28.281:9200/users/_doc/_search?q=';
private handleError: HandleError;
constructor(
private http: HttpClient,
httpErrorHandler: HttpErrorHandler) {
this.handleError = httpErrorHandler.createHandleError('TimelineService');
}
/** GET elasticsearch result */
getElasticResult( text: string ): Observable<User> {
this.http.get<User>(this.getElastic + text).subscribe(res => console.log(res));
return this.http.get<User>(this.getElastic + text, {responseType: 'json'});
}
EDIT:
The new HTML form:
<form [formGroup]="angForm2" *ngIf="user != null" (ngSubmit)="getUser()" class="form-inline my-5 my-lg-0">
<input id="searchText" name="searchText" class="form-control" type="string" placeholder="Search for user" aria-label="Post"
formControlName="searchText" required>
<p>{{user?.firstName}}</p>
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
the new component:
export class SearchServiceComponent implements OnInit {
user: User;
angForm2 = this.fb.group({
searchText: ['', Validators.required]
});
ngOnInit() {
this.getUser();
this.getObject();
}
constructor(private searchservice: SearchService, private fb: FormBuilder) { }
getUser() {
const userName = this.angForm2.value.searchText;
console.log(this.angForm2.value, userName);
this.searchservice.getElasticResult(userName).subscribe(user => (this.user = user));
}
getObject() {
return this.user;
}
}
Output of user and this.user:
User interface:
export interface User {
$oid: string;
firstName: string;
lastName: string;
username: string;
password: string;
follows: User[];
}
I guess getObject() is getting called when you load the form, by the time user is undefined so you are getting that error. why dont you just use user?.firstName since you already have the variable defined ?
<p>{{user?.firstName}}</p>
In your html you need to do something like this
<p>{{usersFirstName}}</p>
In your ts code, once you get the response from the server set this usersFirstName.
usersFirstName : string;
getUser() {
const userName = this.angForm2.value.searchText;
console.log(this.angForm2.value, userName);
this.searchservice.getElasticResult(userName).subscribe(user => (this.user = user)
this.usersFirstName = user.name;
);
}
1.SearchServiceComponent should implement OnInit class and implement its ngOnInit method
2. Call both the methods inside ngOnInit method sequentially
3. Check if this.user is not equal to null or undefined and handle it using ngIf condition
Answered by Sajitharan
Example for OnInit
TS
#Component({selector: 'my-cmp', template: `...`})
class MyComponent implements OnInit {
ngOnInit() {
this.getUser();
this.getObject()
}
getUser() {
const userName = this.angForm2.value.searchText;
console.log(this.angForm2.value, userName);
this.searchservice.getElasticResult(userName).subscribe(user => (this.user = user.hits.hits[0]._source));
}
getObject(){}
}

How to get values from JSON webservice with 2 objects in Angular 2

I'm new in Angular 2 and I'm quite lost. I have a JSON web service responding to /rest/alertsDashboard. It returns something like:
{
"total": {
"totalOperations": 2573,
"totalOperationsAlert": 254,
"totalOperationsRisk": 34
},
"alerts": [
{
codAlert: "L1",
description: "Alert 1",
value: 1
},
{
codAlert: "L2",
description: "Alert 2",
value: 2
},
...
]
}
So I defined a DashboardComponent component and a AlertDashboardService service. I would like, for example, to display totalOperations and totalOperationsAlert. I don't know if I'm doing it in a correct way.
In dashboard.component.ts I have:
...
#Component({
selector: 'app-dashboard',
template: `
<p>{{totalAlertsDashboard.totalOperations}}</p>
<p>{{totalAlertsDashboard.totalOperationsAlert}}</p>
...
`
})
export class DashboardComponent implements OnInit {
totalAlertsDashboard: TotalAlertsDashboard;
alertsDashboard: AlertDashboard[];
constructor(private alertsDashboardService: AlertsDashboardService) { }
ngOnInit() {
this.alertsDashboardService.get().then(
response => {
this.totalAlertsDashboard = response.totalAlertsDashboard;
this.alertsDashboard = response.alertsDashboard;
}
);
}
}
In alerts-dashboard.service.ts I have:
...
export class AlertsDashboard {
totalAlertsDashboard: TotalAlertsDashboard;
alertsDashboard: AlertDashboard[];
}
export class TotalAlertsDashboard {
totalOperations: number;
totalOperationsAlert: number;
totalOperationsRisk: number;
}
export class AlertDashboard {
codAlert: string;
description: string;
value: number;
}
#Injectable()
export class AlertsDashboardService {
private headers = new Headers({ 'Content-Type': 'application/json' });
private url = environment.urlAPI + '/rest/alertsDashboard';
constructor(private http: Http) { }
get(): Promise<AlertsDashboard> {
var vm = this;
let params = new URLSearchParams();
return vm.http.get(vm.url, { search: params })
.toPromise()
.then(response => {
var responseJson: AlertsDashboard = response.json() ;
console.log(responseJson); // it prints the JSON correctly
return responseJson;
});
}
}
I hope you can help me with that.
try this :
ngOnInit() {
this.alertsDashboardService.get().then(
response => {
this.totalAlertsDashboard = response.total;
this.alertsDashboard = response.alerts;
}
);
}
In alerts-dashboard.service.ts
export class AlertsDashboard {
total: TotalAlertsDashboard;
alerts: AlertDashboard[];
}
template :
<p>{{totalAlertsDashboard?.totalOperations}}</p>