I'm working with Reactive Forms and I'm trying to pass my form down to child components, but I'm running into the error above. Initially at the top level of the form I was using a FormArray to hold my form and that was working fine before I tried passing it down to the child components. Thanks to this post I now know that the top level of a form should be a FormGroup and the FormArray should be a child of the FormGroup.
So now I am nesting my FormArray inside of a FormGroup and I'm getting the error above. I'm not sure what I'm doing wrong? Below in the relevant code.
// Parent component.ts
ngOnInit() {
if (!!this.rows) {
this.tableForm = new FormArray([]);
this.rows.forEach((row) => {
this.rowGroup = new FormGroup({})
this.columns.forEach(column => {
this.rowGroup.addControl(column.key, new FormControl(row[column.key]));
});
this.tableForm.push(this.rowGroup);
})
this.tableGroup = new FormGroup({
rows: new FormControl(this.tableForm)
})
}
}
// Parent HTML
<section
*ngIf="!!modal"
class="modal__mask">
<section
class="modal__container"
#carousel
[ngStyle]="{'left': start + 'px'}"
(window:resize)="onResize($event)"
[formGroup]="tableGroup">
<div
*ngFor='let row of selectedRows; let i = index'
class="modal modal__large"
[formArrayName]="rows">
<div
[formGroupName]="i"
[ngClass]="{'opacity': modalPage !== i}">
<div class="modal__header modal__header--large">
<h6>Edit Employee Details</h6>
</div>
<div class="u-flex u-wrap">
<div
class="u-flex modal__body"
style="width: 50%"
*ngFor="let column of columns">
<div
*ngIf="column.label"
class="input__wrapper"
[ngClass]="{'input__wrapper--inline': layout === 'horizontal'}">
<z-input
*ngIf="column.label"
class="u-maxX"
[group]="tableGroup"
[config]="column">
</z-input>
<!-- <div>
<label
class="input__label">
<p class="t-bold t-data">{{column.label}}</p>
</label>
<div class="z-input__default">
<input
class="input u-maxX"
[formControlName]="column.key"
[value]="row[column.key]">
</div>
</div> -->
</div>
</div>
<section class="modal__footer u-fillRemaining">
<div class="u-flex">
<button
class="button button--medium"
(click)="nextSelectedRow()">
<div class="button__content">
<i
class="icon icon--medium"
*ngIf="!!icon">
{{icon}}
</i>
<span>Skip</span>
</div>
</button>
</div>
<div class="u-flex">
<button
class="button button--low"
(click)="reset(row, i)">
<div class="button__content">
<i
class="icon icon--medium"
*ngIf="!!icon">
{{icon}}
</i>
<span>Reset</span>
</div>
</button>
<button
class="button button--low"
(click)="saveChanges(row, i)">
<div class="button__content">
<i
class="icon icon--medium"
*ngIf="!!icon">
{{icon}}
</i>
<span>Save Changes</span>
</div>
</button>
</div>
</section>
</div>
</div>
</div>
// Child component.ts
#Input() config;
#Input() group: FormGroup;
#Input() view: string;
#Input() layout: string;
// Child HTML
<div
class="input__wrapper"
[ngClass]="{'input__wrapper--inline': layout === 'horizontal'}"
[formGroup]="group"
[ngSwitch]="config.type">
<label
class="input__label"
*ngIf="!!config.label">
<p class="t-bold t-data">{{config.label}}</p>
</label>
<z-select
*ngSwitchCase="'select'"
[config]="config"
[group]="group"
[view]="view"
gridColumn="1 / 5">
</z-select>
<div class="z-input__default">
<input
*ngSwitchDefault
class="input u-maxX"
[formControlName]="config.key"
[attr.type]="config.type"
[attr.placeholder]="config.placeholder">
</div>
Related
I am using the ngx-pagination library for my angular app.
Besides the previous and next button I want to add 2 more buttons to go directly on the last page or on the first page.
How can I achieve this ?
Template logic
import { Component, OnInit } from '#angular/core';
import { TestimonialsDataService } from '../../services/testimonials-data.service';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss']
})
export class CardComponent implements OnInit {
public authors: Object = {};
public pageList: number = 1;
constructor(private _testimonialsService: TestimonialsDataService) { }
ngOnInit(): void {
this._testimonialsService.getData().subscribe(data => this.authors = data);
}
}
<div class="container">
<div class="content">
<div class="card" *ngFor="let author of authors['user'] | paginate: {id: 'list-pagination', itemsPerPage: 9, currentPage: pageList}" >
<div class="card-content">
<img class="image" src="{{author.image}}"/>
<p class="author">{{author.name}}</p>
<p class="job">{{author.job}}</p>
<p class="company">{{author.company}}</p>
<p class="country"><span class="flag flag-IT"></span><span class="country">{{author.country}}</span></p>
</div>
</div>
</div>
<pagination-controls id="list-pagination" lastLabel="Next" class="list-pagination" directionLinks="true" (pageChange)="pageList = $event"></pagination-controls>
</div>
You can customize the pagination-template and try adding these buttons
<pagination-template #p="paginationApi" [id]="config.id
(pageChange)="config.currentPage = $event">
<div class="custom-pagination">
<div class="pagination-first-page">
<span (click)="p.setCurrent(1)">
First Page
</span>
</div>
<div class="pagination-previous" [class.disabled]="p.isFirstPage()">
<span *ngIf="!p.isFirstPage()" (click)="p.previous()">
<
</span>
</div>
<div class="page-number" *ngFor="let page of p.pages" [class.current]="p.getCurrent() === page.value">
<span (click)="p.setCurrent(page.value)" *ngIf="p.getCurrent() !== page.value">{{ page.label }}</span>
<div *ngIf="p.getCurrent() === page.value">
<span>{{ page.label }}</span>
</div>
</div>
<div class="pagination-next" [class.disabled]="p.isLastPage()">
<span *ngIf="!p.isLastPage()" (click)="p.next()"> > </span>
</div>
<div class="pagination-last-page">
<span (click)="p.setCurrent(p.getLastPage())">
Last Page
</span>
</div>
</div>
</pagination-template>
I'm so new in VueJS and still don't control much of its funcionalities,
I'm trying when I update or delete something in my window not need to refresh to see the changes...how can I do it, please?
My functions work perfectly in my controller, just need to solve the question of seeing changes.
Here I post my code if it helps...thank you very much!!
UpdateProfile.vue:
<div class="field">
<label class="label">Schedules</label>
<!--Edit and Delete Schedules-->
<div v-for="schedule in sortedDays(data.schedulesDisplayed)" class="control">
<div class="container">
<div class="field">
<label class="label">Week Day: {{schedule.week_day}}</label>
</div>
<div class="row">
<div class="col">
<div class="row">
Opening Time: <input class="input" type="text" placeholder="Pub schedules" v-model="schedule.opening_time">
</div>
</div>
<div class="col">
<div class="row">
Closing Time: <input class="input" type="text" placeholder="Pub schedules" v-model="schedule.closing_time">
</div>
</div>
</div>
<div class="field">
<div class="buttons is-left">
<div class="button is-info" #click="updatePubSchedule(schedule)">
<span class="icon"><i class="fas fa-save fa-lg"></i></span>
<span>Save</span>
</div>
<div class="button is-danger" #click="deletePubSchedule(schedule)">
<span class="icon"><i class="far fa-trash-alt"></i></span>
<span>Delete Schedule</span>
</div>
</div>
</div>
</div>
</div>
</div>
updatePubSchedule(schedule){
var instance = this;
this.api.put('/pubschedules/update/' + this.data.id, schedule).then(response => {
console.log(schedule);
//data = response.data;
instance = response.data;
});
},
deletePubSchedule(schedule){
var instance = this;
this.api.delete('/pubschedules/delete/' + this.data.id, schedule).then(response => {
//data = response.data;
instance = response.data;
});
},
And in my controller:
/**
* #param Pub $pub
*/
public function updatePubSchedule(Pub $pub)
{
//json_die(request()->all());
Schedule::where([
['pub_id','=', $pub->id],
['week_day' ,'=', request()->get('week_day')]
])->update(request()->all());
}
/**
* #param Pub $pub
*/
public function deletePubSchedule(Pub $pub)
{
Schedule::where([
['pub_id','=', $pub->id],
['week_day' ,'=', request()->get('week_day')]
])->delete();
}
I don't know how you get your initial data but you can have a GET request that gets fresh data from the laravel after you update/delete initial data.
With the response from that request you can update your data.schedulesDisplayed prop.
Important! You need to set the :key attribute in the div that uses v-for rendering like this
<div v-for="(schedule, index) in sortedDays(data.schedulesDisplayed)" :key="index" class="control">
I used the index for the sake of this example but you should use a unique property of sortedDays return.
there's probably a simple fix to this. I have tried two prospective solutions to satisfy my goal of scrolling along the x-axis to a child component upon initiation of the view. For, I put both of these solutions in the AfterViewInit lifecycle hook of my child component so that when it initiates the function will fire. Both of these solutions have failed so far despite feeding them perfectly good elements in the AfterViewInit lifecycle hook. There are no compile time or runtime errors.
In the console I see my console log (refer to code below):
new-user-req.component.ts:27 ElementRef {nativeElement: div.blade-header}
Solution 1:
Element.scrollIntoView()
Solution 2:
window.scrollTo(4000, 0)
this didnt work either...
window.scrollBy(window.innerWidth, 0);
Note:
I am actually activating this cascade from the parent of the parent since new-user-info is default. So when I open new-user I am opening new-user-req from the parent/parent user-management.... I feel this shouldn't matter considering the functionality actually resides at the child and the element is being referenced properly; however, let me know if you want the parent/parent code.
Also Note:
Solution 3: asks me whether I meant scrollTo
window.scrollTo + 20;
Thank you!
Child Component(new-user-req.component):
pertinent:
#ViewChild('next') scroll;
ngAfterViewInit() {
console.log(this.scroll)
// this.scroll.scrollIntoView({ behavior: 'smooth' });
// window.scrollTo(4000, 0);
// window.scrollLeft = 20
window.scrollBy(4000, 0);
}
_
import { EventEmitter, ViewChild, AfterViewInit } from '#angular/core';
import { Component, OnInit, Output } from '#angular/core';
import { slideToRight } from '../../../../router.animations';
import { Router, ActivatedRoute, UrlSegment, NavigationEnd } from '#angular/router';
#Component({
selector: 'new-user-req',
templateUrl: './new-user-req.component.html',
styleUrls: ['./new-user-req.component.css'],
animations: [slideToRight()]
})
export class NewUserReqComponent implements OnInit, AfterViewInit {
#ViewChild('next') scroll;
constructor() {
}
ngOnInit(): void {
}
ngAfterViewInit() {
console.log(this.scroll)
// this.scroll.scrollIntoView({ behavior: 'smooth' });
// window.scrollTo(4000, 0);
// window.scrollLeft = 20
window.scrollBy(4000, 0);
}
}
Child template:
pertinent:
<div #next class="blade-header">
_
<div class="blade" [#routerTransition]>
<div #next class="blade-header">
<h3>USER INFORMATION</h3>
<div class="window-functions">
<i class="fa fa-window-minimize"></i>
<i class="fa fa-window-restore"></i>
<i class="fa fa-window-maximize"></i>
<a>
<i class="fa fa-window-close"></i>
</a>
</div>
</div>
<form action="submit">
User type:
<br>
<select required>
<option value="" hidden disabled selected data-default></option>
<option value="Customer">Customer</option>
<option value="Organization Administrator">Organization Administrator</option>
<option value="Customer Service Representative">Customer Service Representative</option>
<option value="Customer Service Administrator">Customer Service Administrator</option>
</select>
<br>
<br> First name:
<br>
<input required type="text" name="firstname" value="Richard">
<br>
<br> Last name:
<br>
<input required type="text" name="lastname" value="Dawkins">
<br>
<br> Cell phone:
<br>
<input required type="tel" name="cellphone" value="(585) 271-8888">
<br>
<br> Office phone:
<br>
<input required type="tel" name="officephone" value="(585) 271-8887">
<br>
<br> Fax:
<br>
<input type="tel" name="fax" value="(585) 271-8886">
<br>
<br> City:
<br>
<input required type="text" name="city" value="City">
<br>
<br> State:
<br>
<input required type="text" name="state" value="New York">
<br>
<br> Requester title:
<br>
<input required type="text" name="requester" value="Requester title">
<br>
<br>
<!-- <div *ngIf="newUserInfoValidState; else allowOrgInput">
<a [routerLink]="['../',{ outlets: { newuserorginfo: ['newuserorginfo'] } } ]" routerLinkActive='router-link-active'>
<button autofocus class="next-button">Next</button>
</a>
</div> -->
<!-- <ng-template #allowOrgInput> -->
<a>
<button autofocus class="next-button" disabled>Next</button>
</a>
<!-- </ng-template> -->
</form>
</div>
<router-outlet></router-outlet>
<router-outlet name="newuserorginfo"></router-outlet>
Parent Template
<!-- <app-page-header [icon]="'fa fa-users'"></app-page-header> -->
<!-- <app-page-header [heading]="'User Management'" [icon]="'fa fa-users'"></app-page-header> -->
<!-- BLADE LAYER 1 -->
<div class="blade" [#routerTransition]>
<div class="blade-header">
<!-- <div class="blade-header" [ngClass]="{'is-minimized-header':minimizeVar}"> -->
<h3>NEW USER REQUEST</h3>
</div>
<!-- [ngClass]="{'feature-nav-button': portalState =='portal-a', 'click-portal-style': portalState =='portal-a'}" -->
<!-- BLADE Contents -->
<div class="blade-contents">
<div (click)="stateUserReq = 'userInfo';" [ngClass]="{'userManSelect': stateUserReq =='userInfo'}">
<h4 class="font-weight-light">
<span class="blade-2-number" [ngClass]="{'blade-2-activated-number' : stateUserReq =='userInfo'}">1 </span> User Information
<i class="fa fa-chevron-right"></i>
</h4>
</div>
<div (click)="stateUserReq = 'orgInfo';" [ngClass]="{'userManSelect': stateUserReq =='orgInfo'}">
<h4 class="font-weight-light">
<span class="blade-2-number" [ngClass]="{'blade-2-activated-number' : stateUserReq =='orgInfo'}">2 </span> Organization
<i class="fa fa-chevron-right"></i>
</h4>
</div>
<div (click)="stateUserReq = 'supInfo';" [ngClass]="{'userManSelect': stateUserReq =='supInfo'}">
<h4 class="font-weight-light">
<span class="blade-2-number" [ngClass]="{'blade-2-activated-number' : stateUserReq =='supInfo'}">3 </span> Supervisor
<i class="fa fa-chevron-right"></i>
</h4>
</div>
<div (click)="stateUserReq = 'secInfo';" [ngClass]="{'userManSelect': stateUserReq =='secInfo'}">
<h4 class="font-weight-light">
<span class="blade-2-number" [ngClass]="{'blade-2-activated-number' : stateUserReq =='secInfo'}">4 </span> Security Profiles
<i class="fa fa-chevron-right"></i>
</h4>
</div>
</div>
</div>
<div *ngIf="stateUserReq === 'userInfo'">
<new-user-req></new-user-req>
</div>
<div *ngIf="stateUserReq === 'orgInfo'">
<new-user-org-info></new-user-org-info>
</div>
<div *ngIf="stateUserReq === 'supInfo'">
<new-user-supervisor-info></new-user-supervisor-info>
</div>
<div *ngIf="stateUserReq === 'secInfo'">
<new-user-security-info></new-user-security-info>
</div>
just add .nativeElement per #Ryan s suggestion
this.scroll.nativeElement.scrollIntoView({ behavior: 'smooth' });
Had similar problem, I've update with the below code. Now its working fine
element.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
I use {{> ArticleForm}} for create and update Articles in arabic-russian dictionary. I use blaze and ReactiveDict in my templates, after some problems with Autoform plugin.
First I was using it for update Article and everything works good. Now I added same form for create Article. I placed {{ > ArticleForm}} to {{> Header}} component, inside bootstrap modal, and call it by "+Add" button in top menu. It opens my form. First insert of Article works good. In DB there is new article, and even I redirected to new article. But at second call, template.data is empty and form is not interactive, it fires errors, because after update any field, ArticleForm should write ReactiveDict from template.data, but it doesn't exist. I can't understand, where goes template.data after firs successful insertion.... Please answer if someone knows...
Here is how it looks:
Here is data context of my form :
Template.ArticleForm.helpers({
article() {
console.log("helper-article");
if (!this.words) this.words = [{ note: "", word: "" }];
if (!this.translations) this.translations = [{ translation: "" }];
if (!this.synonyms) this.synonyms = [];
if (!this.roots) this.roots = [];
if (!this.subjects) this.subjects = [];
//newWords, newTranslations - это добавление имен к элементам формы,
//по которым можно будет отслеживать все изменения в форме
let newWords = this.words.map((elem, index) => {
return { note: elem.note, word: elem.word, wordId: `words.${index}` };
});
let newTranslations = this.translations
? this.translations.map((elem, index) => {
return {
translation: elem.translation,
translationId: `translations.${index}.translation`,
examples: elem.examples
? elem.examples.map((elem2, index2) => {
return {
example: elem2.example,
translation: elem2.translation,
exampleId: `translations.${index}.examples.${index2}`
};
})
: []
};
})
: [];
this.words = newWords;
this.translations = newTranslations;
this.picture = Session.get("picture");
Template.instance().reactiveForm.set("article", this);
const article = Template.instance().reactiveForm.get("article");
return article;
},
deleted() {
return this.deleted ? "checked" : "";
},
showMiddleHarakat(speachPart, index) {
return speachPart == "глагол, I порода" && index == 0;
},
picture() {
return Session.get("picture");
}
});
Here is my template {{> ArticleForm}}
<template name="ArticleForm">
<form id="articleForm-{{_id}}" class="panel panel-default article {{#if notPublished}}-opacity-06{{/if}}">
<div class="panel-heading">
<div class="panel-title words">
<div class="label label-info speach-part">{{speachPart}}</div><br />
<!-- Глагол 1й породы имеет дополнительную информацию для вывода, поэтому
особый шаблон его вывода, например, среднекорневую глассную и масдары -->
{{#each article.words}}
<div class="wordEdit editField" id="{{wordId}}">
<i class="glyphicon glyphicon-remove remove-word -remove" id="remove.{{wordId}}"></i>
<input type="text" placeholder="прим." value="{{note}}" name="{{wordId}}.note" class="form-control note">
<input type="text" placeholder="слово" value="{{word}}" name="{{wordId}}.word" class="form-control word -arabic-text-mid">
</div>
{{#if showMiddleHarakat ../speachPart #index}}
<div class="note middleHarakat" title="среднекорневая гласная настоящего времени">
<input type="text" placeholder="скгнв" value="{{../middleHarakat}}" name="middleHarakat" class="form-control note">
</div>
{{/if}}
{{/each}}
<div class="add-word">
<i class="glyphicon glyphicon-plus"></i>
</div>
</div>
</div>
<div class="panel-body">
<div class="translations">
{{#each article.translations}}
<div class="translation">
<div class="editField editTranslation" id="{{translationId}}">
<input type="text" name="{{translationId}}" value="{{translation}}" class="form-control" placeholder="перевод">
<i class="glyphicon glyphicon-remove remove-translation -remove" id="remove.{{translationId}}"></i>
</div>
<div class="examples examples-form-{{../_id}}-{{#index}}">
{{#each examples}}
<div class="exampleEdit editField" id="{{exampleId}}">
<input type="text" placeholder="пример" value="{{example}}" name="{{exampleId}}.example" class="form-control example -arabic-text-mid">
<input type="text" placeholder="перевод примера" value="{{translation}}" name="{{exampleId}}.translation" class="form-control translation">
<i class="glyphicon glyphicon-remove remove-example -remove" id="remove.{{exampleId}}"></i>
</div>
{{/each}}
<button class="btn btn-default btn-xs add-example" id="addExampleFor.{{translationId}}">
<i class="glyphicon glyphicon-plus"></i>Пример
</button>
</div>
</div>
{{/each}}
<button class="btn btn-default btn-sm add-translation">
<i class="glyphicon glyphicon-plus"></i>Перевод
</button>
</div>
{{> TagsSubjects}}
{{> TagsSynonyms}}
{{> TagsRoots}}
<!--
<div class="uploadImage">
{{> uploadForm}}
picture: {{picture}}
</div>
-->
</div>
<div class="panel-footer">
<button class="btn btn-primary article-save">Сохранить</button>
<button class="btn btn-default article-edit-cancel">Отмена</button>
</div>
</form>
</template>
This could be because you are not clearing the form template properly when the modal closes? Remember the modal is just hidden, the template is not removed.
I came across a similar issue when adding AutoForm support to my boostrap modal package.
I have parent component with all teams and child component for displaying each of team in box using *ngFor and #Input. All child components are displayed fine but when I want to change each of them opening modal I always get the data from first object in array of teams.
team.component.html
<app-team-box *ngFor="let team of teams" [team]="team"></app-team-box>
team-box.component.html
<div class="ibox">
<div class="ibox-title">
<h5>{{this.team.name}}</h5>
<a class="btn btn-xs" (click)="openEditTeamModal(this.team)">Edit</a>
</div>
</div>
<div class="modal fade" id="edit_team_modal">
<div class="modal-dialog">
<div class="modal-content">
<form role="form" (ngSubmit)="editTeam()" novalidate>
<div class="modal-body">
<div class="row" *ngIf="this.team">
<label>Team name:</label>
<input type="text" id="name" name="name" [(ngModel)]="this.team.name" #name="ngModel">
</div>
</div>
<div class="modal-footer">
<button type="submit">Save</button>
</div>
</form>
</div>
</div>
team-box.component.ts
export class TeamBoxComponent implements OnInit {
#Input() team;
constructor(private teamService: TeamService) {}
openEditTeamModal(selectedTeam): void {
this.team = selectedTeam;
$("#edit_team_modal").modal();
$(".modal").appendTo("html");
}
editTeam(): void {
this.teamService.update(this.team).subscribe((team: Response) => {
$("#edit_team_modal").modal("hide");
});
}
}
The problem is when I change first one everything is fine, modal is populated with name and after changing it's get saved. But when I click on edit button for example second team, in modal I get the data for first team. Why this.team is always referencing to first object in array of teams?