I have an Angular form where a user fills in the form and selects multiple books via checkboxes and send the data to the server. The user can later edit the form again. When a user wants to edit the form, the form must be shown to the user pre-filled with the previous data the user has already submitted.
The problem I am having now is how to pre-filled the checkboxes with the values the user has already submitted. There are five checkboxes and the user can select two. When he wants to edit the data later, i want to pre-select the checkboxes he has selected before.
Current problem
When I run my code, the checkboxes appears and vanishes instantly within a second. Because of that am not able to see whether they are checked or not.
The following is a stripdown of my code so that you can understand the problem.
// Model Interface
export interface BooksModel {
books?: Array<any>;
}
export class AdviesHandicapComponent implements OnInit {
private sub: any;
service: DataService;
books = new FormArray([]);
booksModel: BooksModel;
booksForm: FormGroup;
booksOptions = [
{id: 1, book: 'Biology'},
{id: 2, book: 'Physics'},
{id: 3, book: 'Chemistry'},
{id: 4, book: 'Maths'},
{id: 6, book: 'None of the above'}
];
constructor(private fb: FormBuilder){}
ngOnInit(): void {
this.service = new DataService();
this.sub = this.route.queryParams.subscribe(params => {
const tok = params['tok'];
if (tok) {
const results = this.service.getBooks();
booksModel = results.reg;
patchBooksForm();
}
});
const booksFormControls = this.booksOptions.map(book => new FormControl(false));
this.books = new FormArray(booksFormControls);
this.booksForm = this.fb.group({
books: this.books,
});
}
patchBooks(ind: string[]) {
for (let i = 0; i < this.books.controls.length; i++) {
for (const d of ind) {
if (this.booksOptions[i].book === d) {
this.books.controls[i].patchValue(true);
}
}
}
}
patchBooksForm(): void {
patchBooks(this.booksModel.books); // I call this method here to patch the formarray values
this.booksForm.patchValue({
books: this.books.controls
});
}
}
Data from server
The user may have selected the following books. So when he comes back later I want these books to be selected before he starts editing the form.**
export class DataService {
getBooks() {
return {"reg":{"books":["Biology","Maths"]}};
}
}
Based on your code, your untouched form looks like:
{
"books": [
false,
false,
false,
false,
false
]
}
To achieve the same when patching, you are now having issues, that you are patching it twice, first time with correct values in your for loop in patchBooks(), second time patching it with the books controls in patchBooksForm(). You should remove those lines:
// remove
// this.booksForm.patchValue({
// books: this.books.controls
// });
STACKBLITZ
Just a remark, does it make sense to not having any identifier in the formarray, now there's just a bunch of falses and trues, but that is of course up to you :) Also another remark, not related, you should inject your service in the constructor, and not call: this.service = new DataService();.
Related
So I'm using https://github.com/bashleigh/typeorm-polymorphic as polymorphic relation in my model. There a few models
// Device Entity
#Entity()
#TableInheritance({ column: { type: 'varchar', name: 'type' } })
export class Device extends BaseModel { // Base model is just primary generated id and timestamp
// Device stuff
}
// lock entity
#ChildEntity()
export class Lock extends Device {
// Lock stuff
#PolymorphicChildren(()=>ConnectionData, {
eager: false
})
providers: ConnectionData[]
}
// connection data entity
#Entity()
#TableInheritance({ column: { type: 'varchar', name: 'type' } })
export class ConnectionData extends BaseModel {
// connection data basic stuff
}
// first user type entity
#ChildEntity()
export class FirstUserType extends ConnectionData implements PolymorphicChildInterface {
// other first user type stuff
#PolymorphicParent(()=>[Lock,Key]) // Key is also a parent like lock
connectable: Lock | Key
#Column({name: 'connectableId'})
entityId: string;
#Column({name: 'connectableType'})
entityType: string;
}
using these script
let repo = connection.getCustomRepository(FirstUserTypeRepository) // extends AbstractPolymorphicRepository
let result = repo.findOne(1) // find with some id
I'm able to get these data
{
id: // first user type id
prop: // first user type other properties
connectable : {
// Lock object
}
}
But I want the other way. I want the output to be
{
id: //some lock id
data: // some lock data
providers: [
// I want here to be list of ConnectionData
]
}
I tried to create these script that I thought will be able to do such thing
let repo = connection.getCustomRepository(LockRepository) // extends AbstractPolymorphicRepository
let result = repo.findOne(1) // find with lock id
but got these error
TypeORMError: Function parameter isn't supported in the parameters. Please check "orm_param_1" parameter.
I'm not sure how would I get the data. I've been spending a few days to do so but still no luck for me until now.
I found out that I can use TypeOrm InnerJoinAndMap to achieve what I want, I did
const locks = connection.getRepository(Lock)
const result = await locks
.createQueryBuilder("lock")
.innerJoinAndMapMany("lock.providers",ConnectionData,"pc", `pc."connectableId"::text = lock.id::text`)
.where({
id:'a725b986-71d7-4f65-bbbf-26f537c13026'
})
.getOne()
and got the result just like what I want
I have the following class model in my application Angular:
export class IItemsModel {
public description: string;
public itemDetail: IItemDetailModel;
public itemCategories: IItemCategoriesModel[]; // array of IItemCategoriesModel
}
export class IItemCategoriesModel {
public id: string | number;
public description: string;
}
And my Controller:
itemModel: IItemsModel;
selectedCategories: any[] = [];
ngOnInit() {
this.itemModel = new IItemsModel();
this.itemModel.itemCategories = [];
}
onSubmit(form: NgForm) {
// here I format the data
}
In the template I have a multiple select where I fill an array with the id's of the chosen categories.
[25, 38] // selectedCategories
Problem, I'm using ngModel to link the form with the controler, but to send the pre-filled data to the API, I have to format the id's to the model format, that is:
{
...,
itemDetail: 'something',
itemCategories: [
{ id: any Id },
{ id: other Id }
]
}
I try to format the data as follows in the onSubmit() method:
for(let i=0; i<this.selectedCategories.length; i++) {
this.itemModel.itemCategories[i].id = this.selectedCategories[i];
}
But I get the error:
TypeError: Cannot set property 'id' of undefined # undefined:undefined
How could you be formatting the itemCategories to be able to send the data correctly to the API?
Use forEach to iterate instead of for loop.
this.selectedCategories.forEach(f => {
this.itemModel.itemCategories.push({ id: f, description: '' })
});
Since your selectedCategories object is an array of numbers, it doesn't have id property in it. That's why you're getting errors.
Working demo at StackBlitz.
Click the button and check the console log.
I am dealing with an error which when I try to create new page Object, it send to backend but it is not updating the array, I need to reload the page to see the all the array.
I am using Observable within async in the frontend.
I tried to console.log the ngOnInit of the page.component.ts but when I add new page and navigate to pages then the ngOnInit it isn't calling.
On Create new page it happens this.
It sends me to the route of pages where there I show all the list of pages.
But when I create new Page it is returningback an error which says.
ERROR Error: Error trying to diff 'Here is the name of the object'. Only arrays and iterables are allowed.
Update: as Marco said this happens because I mix page as Object instead I am iterating through array
But I am unable to resolve it and i need your help.
In the page.service.ts at pageModel when I add new Object it is returning me only the added Object not the whole array and there is the problem I think, but I don't know how to fix.
But If I reload page then I see all my Array.
This is my updated code.
This is my code.
export class PagesService {
public baseUrl = environment.backend;
private data = new ReplaySubject<any>();
public userID = this.authService.userID;
public editDataDetails: any = [];
public subject = new Subject<any>();
private messageSource = new BehaviorSubject(this.editDataDetails);
getPageID = this.messageSource.asObservable();
constructor(private http: HttpClient, private authService: AuthService) { }
public getPages() {
return this.http.get<any>(`${this.baseUrl}/pages/${this.userID}`).subscribe(res => this.data.next(res));
}
public pageModel(): Observable<Page[]> {
return this.data.asObservable(); // Here it throws error
}
public getPage(id): Observable<any> {
return this.http.get(`${this.baseUrl}/page/${id}`);
}
public setPage(page: Page, id: string) {
const api = `${this.baseUrl}/page`;
const user_id = id;
this.http.post<any>(api, page, {
headers: { user_id }
}).subscribe(res => this.data.next(res));
}
changeMessage(message: string) {
this.messageSource.next(message)
}
public updateDate(id: string, page: Page) {
const api = `${this.baseUrl}/page/${id}`;
return this.http.put<any>(api, page).subscribe(res => this.data.next(res.data));
}
Updated Code from Answer.
public updateDate(id: string, page: Page) {
const api = `${this.baseUrl}/page/${id}`;
return this.http.put<any>(api, page).subscribe(res => {
this.lastSetOfData = res;
this.data.next(this.lastSetOfData);
});
}
}
export class Page {
_id = "";
name = "";
slogan = "";
description = "";
url = "";
telephone: number;
pageUrl: string;
website: string;
founded: number;
organization: number;
email: string;
coverImage: string;
profileImage: string;
specialty?: Specialty[];
branches: Branches[];
locations?: Location[];
phone?:Phone;
userRole?: string;
roles?: Roles[];
}
export class Roles {
role= "";
userID = "";
}
This is the HTML of page.component .
<div class="main" *ngIf="!showWeb">
<div *ngFor="let page of pages$ | async" class="card width-900">
<app-pages-list class="d-flex width-900" [page]="page" [details]="'details'"></app-pages-list>
</div>
<div>
</div>
</div>
This is the TS file.
public pages$: Observable<Page[]>;
ngOnInit(): void {
this.pageService.getPages();
this.pages$ = this.pageService.pageModel();
}
And this is the code when I create new Page.
export class CreatePageComponent implements OnInit {
public page = new Page();
search;
public branch = [];
constructor(public router: Router,
public branchesService: BranchesService,
public authService: AuthService,
public pageService: PagesService,
public shareData: SenderService) { }
ngOnInit(): void {
}
createPage() {
this.page.url = this.page.name;
this.page.branches = this.branch;
this.page.locations = [];
this.page.specialty = [];
this.page.roles = [];
this.page.phone = this.page.phone;
this.page.pageUrl = `${this.page.name.replace(/\s/g, "")}${"-Page"}${Math.floor(Math.random() * 1000000000)}`;
this.pageService.setPage(this.page, this.authService.userID);
}
addBranch(event) {
this.branch.push(event);
this.search = "";
}
removeBranch(index) {
this.branch.splice(index, 1);
}
}
From my understanding of your code, your error is thrown because the data variable hold 2 types of objects.
In the PagesServices:
In getPages you give data a list of Page.
In setPage and updatePage you give data an instance of Page.
private data = new ReplaySubject<any>();
When you create a new page, data hold the last page you created (not an array). Then you try to iterate this page.
<div *ngFor="let page of pages$ | async"
This error come from the fact that you can't iterate a Page object.
You should stop using any so that this type of error occurs at compilation time, not at runtime. Also you need to store an instance of the array of page, add the item in your array after a post, and then replay the whole array.
Code
public updateDate(id: string, page: Page) {
const api = `${this.baseUrl}/page/${id}`;
return this.http.put<any>(api, page).subscribe((res) => {
const index: number = lastSetOfData.findIndex((_page: Page) => _page._id === res._id);
lastSetOfData[index] = res;
lastSetOfData = [...lastSetOfData];
this.data.next(lastSetOfData);
});
}
Also the updateDate function should be named updatePage.
The issue is the one identified in the response from #Marco. I elaborate starting from there.
There are several ways of fixing this problem. Probably the fastest is to add an instance variable lastSetOfData to PagesService where you hold the last version of the array. Then you initiatlize lastSetOfData in the getPages method. Finally in the setPage method you update lastSetOfData appending the Page returned by the service at the end of lastSetOfData and notify it using the ReplaySubject.
So the code could look like this
export class PagesService {
public baseUrl = environment.backend;
// specify the type of data notified by the ReplaySubject
private data = new ReplaySubject<Array<Page>>();
// define lastSetOfData as an array of Pages
private lastSetOfData: Array<Page> = [];
....
public getPages() {
return this.http.get<any>(`${this.baseUrl}/page/${this.userID}`).subscribe(res => {
// res should be an array of Pages which we use to initialize lastSetOfData
lastSetOfData = res;
this.data.next(lastSetOfData)
});
}
....
public setPage(page: Page, id: string) {
const api = `${this.baseUrl}/page`;
const user_id = id;
this.http.post<any>(api, page, {
headers: { user_id }
}).subscribe(res => {
// update lastSetOfData appending resp, which should be a Page
// not the use of the spread operator ... to create a new Array
lastSetOfData = [...lastSetOfData, resp];
// now you notify lastSetOfData
this.data.next(lastSetOfData)
});
}
// probably you have to modify in a similar way also the method updateTable
public updateDate(id: string, page: Page) {
....
}
....
....
}
Consider that this may be the fastest way to fix the problem. Check if it works and then you may want to try to refactor the code to look for a more rx-idiomatic solution. But my suggestion is first to see if this fixes the problem.
Problem is that you put an object in your replaysubject although an array is expected in other places.
next(myarray)
next(myobject)
This does not magically append an object to the array.
To do so, you'd need something like this:
data.pipe(take(1)).subscribe(list => {
list.push(newvalue);
data.next(list);
});
Basically you take the last value, a the new item, and push the new list.
I am trying to limit users to vote only once on a `forumpost`. I have been struggling with correctly using arrays to handle this functionality in angular for a while now.
Right now my code is failing when loading all my forumposts. The error occurs when building the formgroups and when there are more than 1 userId in my upVoters: string[] so I assume my arrays are wrong. any help, tip or pointing me into the right direction is much appreciated!
my idea:
add upVoters: string[] to Forumpost class.push userId into string[] when voting
compare if userId is already in the voters string[]
true => remove userId from array
false => add userId to array
it is working great until I start loading up the array with multiple userIds.after investing many hrs of research on SO and other coding blogs and similiar I couldn't find an answer that was able to help me solve my problem so I decided to ask the community for help. I found a couple articles to nested FormArrays but none I could find were able to help me with my use case or maybe I do not understand how to implement correctly
Angular 2 Typescript: TypeError: this.validator is not a function
I am defining my entity and my mapping functions in my forum.service.ts file so I can use them anywhere in my application
export class ForumPost {
id: string;
title: string;
writerId: string;
upVoters: string[];
constructor() {
this.id = '';
this.title = '';
this.writerId = '';
this.upVoters = [];
}
}
mapFormToForumPost(form: FormGroup): ForumPost {
const forumPost = form.getRawValue();
return forumPost;
}
mapForumPostToForm(forumPost: ForumPost): FormGroup {
const form = this.formBuilder.group(forumPost);
this.handleVotesFromForumPostForForm(form, forumPost.upVoters);
return form;
}
handleVotesFromObjectToForm(form: FormGroup, arrayUpVoters: string[]) {
form.removeControl('upVoters');
if (arrayUpVoters && arrayUpVoters.length === 0) {
const upVotersForForm = [];
form.addControl('upVoters', this.formBuilder.array(upVotersForForm))
} else {
const upVotersForForm = [];
for (const item of arrayUpVoters) {
upVotersForForm.push(item);
}
form.addControl('upVoters', this.formBuilder.array(upVotersForForm))
}
in my application i have a page where i use an http.get call to getAll forumposts like this. the http request is called in the ngOnInit() of the forumList.component.ts file
forumPosts: FormGroup[] = [];
constructor(private forumService: ForumService, private formBuilder: FormBuilder, private formHelper: FormHelper ) {}
loadTopics() {
this.forumService.getForumPosts(this.postsPerPage, this.currentPage)
.subscribe(response => {
for (const forumPost of response.forumPosts) {
console.log(forumPost);
this.forumPosts.push(this.formBuilder.group(forumPost));
for (const post of this.forumPosts) {
this.formHelper.disableControls(post);
}
}
this.totalPosts = response.maxPosts;
});
}
my corresponding HTML looks like this forumList.component.html
<mat-card class="outerCard" *ngIf="this.forumPosts.length > 0">
<forum-list-post
*ngFor="let forumPostForm of forumPosts | sort: 'votes'"
[forumPostForm]="forumPostForm"
></forum-list-post>
</mat-card>
following my error stacktrace with corresponding locations in my code. you can see its failing when building a formgroup via formbuilder. i have added a console log of my forumPost just before getting mapped to a formGroup.
for anyone encountering same problem here is how i resolved my problem. i decided to refactor my forum.service file by changing the mapping functions a bit. instead of using the formbuilder to build a formgroup i defined the formgroup myself inside my mapping function like this.
mapForumPostToForm(forumPost: ForumPost): FormGroup {
const newform = new FormGroup({
id: new FormControl(forumPost.id),
title: new FormControl(forumPost.title),
writerId: new FormControl(forumPost.writerId),
upVoters: this.formBuilder.array(forumPost.upVoters),
});
return newform;
}
drawback of this is i need to add fields here when i add new variables to my forumPost entity. as it is located inside my forum.service.ts file i just need to make sure not to forget when adding new variables to my entities
I'm developping a single app and at the moment the only good behavior is that I'm getting an user from an API with HttpClient method.
The method is store in a service.
Getting the user is a success but now I want to get a specific array from that user to re-use it by my will.
Should I make another service since this value will be use in 2 components ?
How should I procced to get this array in a var ?
Exemple of user object :
{
firstName: '',
lastName: '',
arrayIWant: []
}
My user is in a subject and here is the way I use it in a component
user: User;
userSubscription: Subscription;
constructor(
public userService: UserService
) {
}
ngOnInit() {
this.userSubscription = this.userService.userSubject.subscribe(
(user: User) => {
this.user = user;
}
);
this.userService.getSingleUserFromServer();
this.userService.emitUser();
}
ngOnDestroy() {
this.userSubscription.unsubscribe();
}
Should I put this code in every component where I want to use the user or is there a way to definie globaly the user ?
You can use a BehaviourSubject which will hold the last value of whatever that service populates the userSubject with
public userSubject: BehaviourSubject<User> = new BehaviourSubject(null);
getSingleUserFromServer(): void {
//get your user from http
userSubject.next(result);
}
In you HTML you can use the async pipe to display the values of the inner array you want. Or just use it in your component by subscribing to the last emission of the behaviourSubject
//X.Component
public subscriptionKiller: Subject<void> = new Subject();
ngOnInit(): void {
this.userService.userSubject
.pipe(takeUntil(this.subscriptionKiller))
.subscribe((lastUser: User) => {
someMethod(this.userService.userSubject.value.arrayIWant);
}
}
ngOnDestroy(): void {
this.subscriptionKiller.next()
}