angular4 viewchild not working on dom element - javascript

i want to know how to fetch the dom element from a components template :
Component
export class JokeListComponent implements OnInit, AfterViewInit {
jokes: Joke[];
constructor() { }
#ViewChild('.myclass') el: ElementRef;
ngOnInit() {
this.jokes = [
new Joke('joke1', 'content1'),
new Joke('Joke2', 'content2'),
new Joke(),
];
}
ngAfterViewInit(): void {
console.log(this.el);
}
}
View
<div class="card">
<div class="card-block">
<h4 class="card-title"> New Joke Form </h4>
<div class="myclass">
</div>
<div class="form-group">
<label for="jokeHeader">Joke Header</label>
<input type="text" id="jokeHeader" class="form-control" placeholder="Joke Head" #jokeHead>
</div>
<div class="form-group">
<label for="jokeContent">Joke Header</label>
<input type="text" id="jokeContent" class="form-control" placeholder="Joke Content" #jokeContent>
</div>
<button class="btn btn-primary" (click)="addJoke(jokeHead.value, jokeContent.value)"> Validate </button>
</div>
</div>
<hr>
<joke *ngFor="let joke of jokes" [joke]="joke" (deleteEvt)="deleteJoke($event)"></joke>
the problem is that this.el is always undefined, i dont know why.
PS: i'm using the last version of angular 4

You cannot use the class name for the #ViewChild, you will need a local variable:
#Component({
template: `
<div><span #myVar>xxx</span><div>`
})
class MyComponent {
#ViewChild('myVar') myVar:ElementRef;
ngAfterViewInit() {
console.log(this.myVar.nativeElement);
}
}

Related

I am working on exam portal using angular and spring boot, I got a problem here when i am using name as [name]

Component.html
<div class="bootstrap-wrapper" *ngIf="!isSubmit">
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!-- instructions -->
<h2>Instructions</h2>
</div>
<div class="col-md-8">
<!-- questions -->
<ng-container *ngIf="questions">
<h2>{{questions[0].quiz.title}}</h2>
</ng-container>
<mat-card *ngFor="let q of questions, let i= index" class="mt20">
<mat-card-content>
<p> Q {{i+1}}) <span [innerHTML]="q.content"></span> </p>
<mat-divider></mat-divider>
<div class="row mt20" >
<div class="col-md-6">
<input type="radio" [value]="q.option1"
[name]="i"
// this is where i am getting error
[(ngModel)] ="q.givenAnswer"
/>
{{q.option1}}
{{i}}
</div>
<div class="col-md-6">
<input type="radio" [value]="q.option2"
[name]="i"
// this is where i am getting error
[(ngModel)] ="q.givenAnswer"
/>
{{q.option2}}
{{i}}
</div>
</div>
<div class="row mt20">
<div class="col-md-6">
<input type="radio" [value]="q.option3"
// this is where i am getting error
[name]="i"
[(ngModel)] ="q.givenAnswer"
/>
{{q.option3}}
{{i}}
</div>
<div class="col-md-6">
<input
type="radio"
[value]="q.option4"
// this is where i am getting error
[name]="i"
[(ngModel)] ="q.givenAnswer"
/>
{{q.option4}}
{{i}}
</div>
</div>
</mat-card-content>
</mat-card>
<div class="container text-center mt20">
<button (click)="submitQuiz()" mat-raised-button color="accent">Submit
Quiz</button>
</div>
</div>
<div class="col-md-2">
</div>
</div>
</div>
</div>
<!-- Show Result -->
<div class="bootstrap-wrapper" *ngIf="isSubmit">
<div class="row">
<div class="col-md-6 offset-md-3">
<mat-card>
<mat-card-header>
<mat-card-title>
<h1 class="text-center mall">Quiz Result</h1>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<h1>Marks Obtained: {{marksGot}}</h1>
<h1>Correct Ansers: {{correctAnswers}}</h1>
<h1>Questions Attempted: {{attempted}}</h1>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button color="accent">Print</button>
<button mat-raised-button color="accent" [routerLink]="'/user-
dashboard/0'">Home</button>
</mat-card-actions>
</mat-card>
</div>
</div>
</div>
Component.ts
import { LocationStrategy } from '#angular/common';
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { QuestionService } from 'src/app/services/question.service';
import Swal from 'sweetalert2';
#Component({
selector: 'app-start-quiz',
templateUrl: './start-quiz.component.html',
styleUrls: ['./start-quiz.component.css']
})
export class StartQuizComponent implements OnInit {
qid;
questions;
marksGot = 0;
correctAnswers = 0;
attempted = 0;
isSubmit = false;
constructor(private locationSt: LocationStrategy, private _route: ActivatedRoute, private
_question: QuestionService) { }
ngOnInit(): void {
this.preventBackButton();
this.qid = this._route.snapshot.params['qid'];
this.loadQuestions();
}
loadQuestions() {
this._question.getQuestionsOfQuizForTest(this.qid).subscribe(
(data: any) => {
this.questions = data;
this.questions.forEach((q) => {
q['givenAnswer'] = '';
});
console.log(data);
},
(error) => {
Swal.fire('Error', 'Error in loading questions of quiz', 'error');
}
);
}
preventBackButton() {
history.pushState(null, null, location.href);
this.locationSt.onPopState(() => {
history.pushState(null, null, location.href);
})
}
submitQuiz() {
Swal.fire({
title: 'Do you want to Submit quiz?',
showCancelButton: true,
confirmButtonText: 'Submit Quiz',
icon: 'info',
}).then((e) => {
if (e.isConfirmed) {
//calculation
this.isSubmit=true;
this.questions.forEach((q) => {
if (q.givenAnswer == q.answer) {
this.correctAnswers++;
let marksSingle = this.questions[0].quiz.maxMarks / this.questions.length;
this.marksGot += marksSingle;
}
if (q.givenAnswer.trim() != '') {
this.attempted++;
}
});
console.log("Correct Answers " + this.correctAnswers);
}
})
}
}
enter image description here
When i am name as [name] it is showing number is not assignable to type String and when i am using name it is compiling successfully but i have three questions in a quiz and while selecting an option of a particular question other options of other questions are getting deselected. what to do?
Thanks in Advance
[(ngModel)]="q.givenAnswer" type="radio" [value]="q.option1" name={{i}}

How to show and hide a modal made with pure html and css in angular?

I have created a modal with css and pure html for my angular application, I try to show it and hide it with the ng-if property and with #input.
but the problem is that I can only open it once and close it once, after closing it it won't open again.
and I don't know what the problem is in my logic
This is my component modal HTML.
<div class="card mymodal z-depth-5" *ngIf="noDisplay">
<form class="form center" (ngSubmit)="enviar(f)" #f="ngForm">
<div>
<h3 class="center">Crear Usuario</h3>
</div>
<!-- nombre -->
<div class="row">
<div class="input-field col s6">
<input id="name" type="text" name="nombre" [(ngModel)]="usuario.nombre" >
<label for="first_name">Nombre</label>
</div>
<!-- nombre -->
<!-- Apellido -->
<div class="input-field col s6">
<input id="name" type="text" [(ngModel)]="usuario.apellido" name="apellido">
<label for="first_name">Apellido</label>
</div>
</div>
<!-- Apellido -->
<!-- Usuario -->
<div class="row">
<div class="input-field col s6">
<input id="name" type="text" [(ngModel)]="usuario.usuario" name="usuario">
<label for="first_name">Nombre de Usuario</label>
</div>
<!-- Usuario -->
<!-- Email -->
<div class="input-field col s6">
<input id="email" type="email" [(ngModel)]="usuario.email" name="email">
<label for="email">Email</label>
</div>
</div>
<!-- Email -->
<!-- Passwor -->
<div class="row">
<div class="input-field col s6">
<input id="password" type="password" [(ngModel)]="usuario.password" name="password">
<label for="password">Password</label>
</div>
<!-- Passwor -->
</div>
<div class="row">
<div class="row">
<div class="input-field col s12">
<button (click)="cerrar()" class="btn waves-effect waves-light right ml-1 mt-2 grey darken-1"
type="submit" name="action">Cancelar
</button>
<button class="btn waves-effect gradient-45deg-light-blue-cyan right mt-2" type="submit" name="action">Guardar
<i class="material-icons right">save</i>
</button>
</div>
</div>
</div>
</form>
</div>
CSS
.mymodal{
position: fixed;
z-index: 2000;
height: 500px;
width: 70%;
background-color: aliceblue;
}
.form{
padding-left: 10%;
padding-right: 10%;
}
TS file
import { Component, OnInit, Output, Input } from '#angular/core';
import { EventEmitter } from 'protractor';
import { AuthService } from 'src/app/services/services.index';
import { NgForm } from '#angular/forms';
import { NuevoUsuario } from 'src/app/models/nuevosUsuario';
#Component({
selector: 'app-modal-form',
templateUrl: './modal-form.component.html',
styleUrls: ['./modal-form.component.css']
})
export class ModalFormComponent implements OnInit {
usuario: NuevoUsuario;
errorMessage: boolean;
#Input() noDisplay: boolean;
constructor(private register: AuthService) { }
ngOnInit(): void {
this.usuario = new NuevoUsuario();
}
enviar(form: NgForm) {
if (form.invalid) {
console.log('Formulario Invalido');
return;
}
this.register.createUser(this.usuario).subscribe(resp => {
console.log('usuario registrado');
}, (err) => {
this.errorMessage = true;
console.log(err.message);
console.log('ocurrio un error');
}
);
}
// close the modal
cerrar() {
this.noDisplay = false;
}
}
and this is where I try to use it
<app-modal-form [noDisplay]="modal" ></app-modal-form>
<div class="row">
<button class="btn gradient-45deg-light-blue-cyan ml-5" (click)="activarModal()">Nuevo
<i class="material-icons right">person_add</i>
</button>
</div>
import { Component, OnInit } from '#angular/core';
import { UsuariosService } from '../../services/services.index';
import { Usuario } from 'src/app/models/usuario';
#Component({
selector: 'app-admin-panel',
templateUrl: './admin-panel.component.html',
styleUrls: ['./admin-panel.component.css']
})
export class AdminPanelComponent implements OnInit {
modal: boolean;
usuariosData: Usuario[] = [];
total: number;
// total_activos: Usuario[] = [];
constructor(private usuario: UsuariosService) { }
ngOnInit(): void {
this.getAllusers();
}
// optiene todos los usuarios
getAllusers() {
this.usuario.getAllUsers().subscribe((data: any) => {
this.usuariosData = data.usuarios;
this.total = this.usuariosData.length;
});
}
// Open the modal
activarModal() {
this.modal = true;
}
}
in your parent component
export class AdminPanelComponent implements OnInit {
// open modal
activarModal() {
this.noDisplay = true;
}
// close modal
onHidePopup() {
this.noDisplay = false;
}
in your popup component
export class ModalFormComponent implements OnInit {
#Input() noDisplay;
// use output to pass event
#Output() onHidePopup = new EventEmitter();
cerrar() {
this.onHidePopup.emit();
}
and in html admin you should pass noDisplay for showing popup and onHidePopup to back the parent
<app-modal-form [noDisplay]="noDisplay" (onHidePopup)="onHidePopup()"></app-modal-form>
After looking at your code again it appears the problem is that you're setting noDisplay to equal the value of modal. But modal is only false when the parent component first loads. You set it to true when you trigger this function:
activarModal() {
this.modal = true;
}
Now, that's going to be leave this.modal as true until the parent component re-mounts. So, logically, that's why you can only open the modal once with your current implementation.
To resolve this, use #Output() someEvent = new EventEmitter(); to pass details back to the parent from the child component, and re-set this.modal to false when that event fires. The event you want to trigger on for the #Output() is this one in your modal component:
// close the modal
cerrar() {
this.noDisplay = false;
}
See this answer for more details on #Output(): Pass Event from child component to parent component Angular 5

Why Parent does not listen to child on Angular?

I cannot make this eventemitter work. Can you please help? I am a beginner and it should be quite simple code for you.
I have a parent component, reading two different emitters from two different children:
<app-van [vans]="vans"></app-van>
<app-modal *ngIf="modalOpen" (closed)="onClick()" (openModal)="onClickTwo($event)"></app-modal>
import { Component, OnInit } from '#angular/core';
import { Van } from '../../interface';
#Component({
selector: 'app-fleet-home',
templateUrl: './fleet-home.component.html',
styleUrls: ['./fleet-home.component.css']
})
export class FleetHomeComponent implements OnInit {
modalOpen = true;
vans: Van [] = [
{ name: 'Ubeddu', description: 'Mercedes Sprinter', plate: 'NH55GKA' },
{ name: 'Abbestia', description: 'Ford Transit', plate: 'DK66HHR' },
{ name: 'Eumulu', description: 'Citroen Berlingo', plate: 'DR55MKL' }
];
constructor( ) { }
ngOnInit() {
}
onClick() {
this.modalOpen = !this.modalOpen;
console.log('modalOpen changed');
}
onClickTwo(event) {
this.modalOpen = event;
console.log('modalOpen changed');
}
}
the parent listened to this child:
<div (click)="onCloseClick()" class="ui dimmer visible active">
<div (click)="$event.stopPropagation()" class="ui modal visible active">
<div class="asuca">
<form class="ui form" >
<h4 class="ui dividing huge header">Van</h4>
<div class="required field">
<label class="ui header">Van Name</label>
<input type="text" placeholder="Van NickName">
</div>
<div class="field">
<label class="ui header">Description</label>
<input type="text"placeholder="Description">
</div>
<div class="field">
<label class="ui header">Plate</label>
<input type="text"placeholder="License Plate">
</div>
<button (click)="onCloseClick()" class="ui button" type="submit">Submit</button>
</form>
</div>
</div>
</div>
import { Component, OnInit, ElementRef, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css']
})
export class ModalComponent implements OnInit {
#Output() closed = new EventEmitter();
constructor(private el: ElementRef) { }
ngOnInit() {
document.body.appendChild(this.el.nativeElement);
}
// tslint:disable-next-line: use-lifecycle-interface
ngOnDestroy() {
this.el.nativeElement.remove();
}
onCloseClick() {
this.closed.emit();
}
}
and doesnt listen to the second child:
<div class="ui fluid four black cards">
<div *ngFor="let van of vans" class="card">
<div class="content">
<div class="header">
{{ van.name }}
</div>
<div class="meta">
{{ van.description }}
</div>
<div class="description">
{{ van.plate }}
</div>
</div>
<div class="extra content">
<div class="ui two buttons">
<div (click)="onEditClick(true)" class="ui basic black button">Edit</div>
<div class="ui basic red button">Delete</div>
</div>
</div>
</div>
</div>
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-van',
templateUrl: './van.component.html',
styleUrls: ['./van.component.css']
})
export class VanComponent implements OnInit {
#Input() vans = [];
#Output() openModal = new EventEmitter<boolean>();
constructor() { }
ngOnInit() {
}
onEditClick(event: boolean) {
this.openModal.emit(event);
}
}
the whole thing is to hide the modal clicking around the screen and show it again clicking a button.
On the console.log, the object emitter by the first child has got an observer, where the object emitter by the second child as none; no idea what means though.
thanks in advance for the help. I can provide the whole folder if needed. I am just trying to learn :)
Seems like you have missed binding the output event in parent template. Please correct like below:
<app-van [vans]="vans" (openModal)="onEditClick($event)"></app-van>

classList.toggle() not working IE11 Angular 7 (Invalid Calling Object)

I've just been testing my app on IE11 and I cant figure out why this isn't working,
I have this code it has three elements .hamburger-small, .hamburger-big and .menu
<div [class.shown]="!chatbarFullscreen">
<div [class.disabled]="router.url.includes('home')">
<img (click)="closeChatbar(true, router.url.includes('home') ? true : false)" *ngIf="chatbarFullscreen" src="../assets/images/whole-app/arrow-right.svg" alt="Arrow Right">
<img (click)="closeChatbar(false, router.url.includes('home') ? true : false)" *ngIf="!chatbarFullscreen" src="../assets/images/whole-app/arrow-left.svg" alt="Arrow Left">
</div>
<img (click)="goHome()" src="../assets/images/chatbar/header-logo.svg" alt="header logo">
<div id="small" (click)="hamburgerClick()" class="hamburger hamburger--slider hamburger-small">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
</div>
<div id="big" (click)="hamburgerClick()" class="hamburger hamburger--slider hamburger-big">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
<div class="menu">
<p (click)="closeChatbar(false); hamburgerClick();" [routerLink]="['/app/main/home']">Home</p>
</div>
</div>
and when you click it, it calls this function
hamburgerClick() {
const small = <HTMLElement>document.querySelector('.hamburger-small');
const big = <HTMLElement>document.querySelector('.hamburger-big');
const menu = <HTMLElement>document.querySelector('.menu');
small.classList.toggle('is-active');
big.classList.toggle('is-active');
menu.classList.toggle('show');
}
now It works on every other browser, Chrome, Firefox, Safari and Edge but not in IE I've seen similar questions but it seems as if it should work? I'm also getting this error in the console when I click the button for the first time, but it does not happen any other time
any help would be great..
EDIT
I have tried using #ViewChild() but it still isn't working, however the Invalid Calling Object error is no longer happening
#ViewChild('hamburgerBig') hamburgerBig: ElementRef;
#ViewChild('hamburgerSmall') hamburgerSmall: ElementRef;
#ViewChild('menu') menu: ElementRef;
hamburgerClick() {
this.hamburgerBig.nativeElement.classList.toggle('is-active');
this.hamburgerSmall.nativeElement.classList.toggle('is-active');
this.menu.nativeElement.classList.toggle('show');
}
Thanks!!
try using Renderer2 to manipulate dom elements along with ElementRef and ViewChild as other previously mentioned.
first import ViewChild, ElementRef and Renderer2
import { Renderer2, ElementRef, ViewChild } from '#angular/core';
get the Element using ViewChild of type ElementRef after you've made template references in your DOM, like
<div #hamburgerBig></div>
<div #hamburgerSmall></div>
<div #menu></div>
#ViewChild('hamburgerBig') hamburgerBig: ElementRef;
#ViewChild('hamburgerSmall') hamburgerSmall: ElementRef;
#ViewChild('menu') menu: ElementRef;
and do your stuff with your hamburgerClick function
hamburgerClick() {
const hamBigIsActive = this.hamburgerBig.nativeElement.classList.contains('is-active');
const hamSmallIsActive = this.hamburgerSmall.nativeElement.classList.contains('is-active');
const menuShow = this.menu.nativeElement.classList.contains('show');
if(hamBigIsActive) {
this.renderer.removeClass(this.hamburgerBig.nativeElement, 'is-active');
} else {
this.renderer.addClass(this.hamburgerBig.nativeElement, 'is-active');
}
if(hamSmallIsActive) {
this.renderer.removeClass(this.hamburgerSmall.nativeElement, 'is-active');
} else {
this.renderer.addClass(this.hamburgerSmall.nativeElement, 'is-active');
}
if(hamSmallIsActive) {
this.renderer.removeClass(this.menu.nativeElement, 'show');
} else {
this.renderer.addClass(this.menu.nativeElement, 'show');
}
}
or you could just simply use [ngClass](not sure why you aren't using this instead)
hope this helps
also dont forget to add render to your contructor
contructor(private renderer: Renderer2){}
Edit: here's the [ngClass] implementation
<div id="small"
(click)="hamburgerClick()"
[ngClass] = "{'is-active' : hamClick}"
class="hamburger hamburger--
slider hamburger-small">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
<div id="big"
(click)="hamburgerClick()"
[ngClass] = "{'is-active' : hamClick}"
class="hamburger hamburger--slider
hamburger-big">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
<div
[ngClass] = "{'show' : hamClick}"
class="menu">
<p (click)="closeChatbar(false); hamburgerClick();" [routerLink]="
['/app/main/home']">Home</p>
</div>
and then just use a function to switch
hamClick: boolean
hamburgerClick(){
this.hamClick = !this.hamClick;
}
there you go
Try to make a test with code below may help you to solve your issue.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public show:boolean = false;
public buttonName:any = 'Show';
ngOnInit () { }
toggle() {
this.show = !this.show;
// CHANGE THE NAME OF THE BUTTON.
if(this.show)
this.buttonName = "Hide";
else
this.buttonName = "Show";
}
}
.is-active{color:green;
}
<button (click)="toggle()" id="bt">
Hide
</button>
<ng-container *ngIf="show">
<div style="margin: 0 auto;text-align: left;">
<div>
<label>Name:</label>
<div><input id="tbname" name="yourname" /></div>
</div>
<div>
<label>Email Address:</label>
<div><input name="email" id="email" /></div></div>
<div>
<label>Additional Information (optional):</label>
<div><textarea rows="5" cols="46"></textarea></div>
</div>
</div>
</ng-container>
Further, You can try to modify the code based on your requirement.

Nested arrays in Angular 2 reactive forms?

I have use the following tutorial to create reactive forms in Angular 2 and it works well.
https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2
However, I am now trying to add an array within an array. Using the tutorial above, I have created an 'Organisation' form, which can contain an array of 'Contact' groups. But I am unable to successfully adapt the setup to allow each 'Contact' group to contain an array of 'Email' groups.
I have been unable to find a tutorial or example that covers this and would be grateful for any pointers.
Using the tutorial above, I have created an 'Organisation' form, which
can contain an array of 'Contact' groups. But I am unable to
successfully adapt the setup to allow each 'Contact' group to contain
an array of 'Email' groups.
The tutorial above gives you all what you need.
I suppose you want structure like this.
Firstly you need some component (AppComponent in my case) where you declare root FormGroup. I called it trustForm below.
app.component.ts
export class AppComponent {
trustForm: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.trustForm = this.fb.group({
name: '',
contracts: this.fb.array([])
});
this.addContract();
}
initContract() {
return this.fb.group({
name: '',
emails: this.fb.array([])
});
}
addContract() {
const contractArray = <FormArray>this.trustForm.controls['contracts'];
const newContract = this.initContract();
contractArray.push(newContract);
}
removeContract(idx: number) {
const contractsArray = <FormArray>this.trustForm.controls['contracts'];
contractsArray.removeAt(idx);
}
}
In this component you have also some methods that help you to manipulate the first level FormArray - contracts
app.component.html
<div class="container">
<form [formGroup]="trustForm">
<h3>Add trust</h3>
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" formControlName="name">
</div>
<!--contracts-->
<div formArrayName="contracts">
<div *ngFor="let contract of trustForm.controls.contracts.controls; let i=index" class="panel panel-default">
<div class="panel-heading">
<span>Contract {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right" *ngIf="trustForm.controls.contracts.controls.length > 1" (click)="removeContract(i)"></span>
</div>
<div class="panel-body" [formGroupName]="i">
<contract [group]="trustForm.controls.contracts.controls[i]"></contract>
</div>
</div>
</div>
<div class="margin-20">
<button (click)="addContract()" class="btn btn-primary">
Add another contract +
</button>
</div>
</form>
<h5>Details</h5>
<pre>{{ trustForm.value | json }}</pre>
</div>
There is no different from root html from the tutorial except different FormArray name.
Then you need to build contract component that will be similar to AppComponent
contract.component.ts
export class ContractComponent {
#Input('group') contractGroup: FormGroup;
constructor(private fb: FormBuilder) { }
addEmail() {
const emailArray = <FormArray>this.contractGroup.controls['emails'];
const newEmail = this.initEmail();
emailArray.push(newEmail);
}
removeEmail(idx: number) {
const emailArray = <FormArray>this.contractGroup.controls['emails'];
emailArray.removeAt(idx);
}
initEmail() {
return this.fb.group({
text: ''
});
}
}
contract.component.html
<div [formGroup]="contractGroup">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" formControlName="name">
</div>
<!--emails-->
<div formArrayName="emails">
<div *ngFor="let email of contractGroup.controls.emails.controls; let i=index" class="panel panel-default">
<div class="panel-heading">
<span>Email {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right" *ngIf="contractGroup.controls.emails.controls.length > 1" (click)="removeEmail(i)"></span>
</div>
<div class="panel-body" [formGroupName]="i">
<email [group]="contractGroup.controls.emails.controls[i]"></email>
</div>
</div>
</div>
<div class="margin-20">
<button (click)="addEmail()" class="btn btn-primary">
Add another email +
</button>
</div>
</div>
As you can see we just replace contracts to emails FormArray and we are also passing FormGroup to email component
And finally you will only need to fill EmailComponent with desired fields.
email.component.ts
export class EmailComponent {
#Input('group') emailGroup: FormGroup;
}
email.component.html
<div [formGroup]="emailGroup">
<div class="form-group">
<label>Text</label>
<input type="text" class="form-control" formControlName="text">
</div>
</div>
Completed version you can find at Plunker Example
If you think that this solution doesn't seems right because the parent component holds the description of the child component like initContract and initEmails you can take a look at more complex
Plunker Example
where each component is responsible for its functionality.
If you're looking for solution for template driven forms read this article:
Angular: Nested template driven form

Categories

Resources