I am trying to render data inside a tab body section based on clicking an icon on a tabset. Here is a screen shot of my initial, successfully rendered view:
But when I click a new tabset icon to load data into the tab body section, I get the following exception:
TypeError: Cannot read property 'category' of undefined
at CompiledTemplate.proxyViewClass.View_DnDMiniCardGroupComponent0.detectChangesInternal (/PaletteSchemaModule/DnDMiniCardGroupComponent/component.ngfactory.js:144)
at CompiledTemplate.proxyViewClass.AppView.detectChanges (view.js:425)
at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (view.js:620)
at CompiledTemplate.proxyViewClass.AppView.internalDetectChanges (view.js:410)
at CompiledTemplate.proxyViewClass.View_TabCommunityComponent0.detectChangesInternal (/PaletteSchemaModule/TabCommunityComponent/component.ngfactory.js:89)
at CompiledTemplate.proxyViewClass.AppView.detectChanges (view.js:425)
at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (view.js:620)
at CompiledTemplate.proxyViewClass.AppView.internalDetectChanges (view.js:410)
at View_TypesContentComponent2.detectChangesInternal (/PaletteSchemaModule/TypesContentComponent/component.ngfactory.js:190)
at View_TypesContentComponent2.AppView.detectChanges (view.js:425)
at View_TypesContentComponent2.DebugAppView.detectChanges (view.js:620)
at ViewContainer.detectChangesInNestedViews (view_container.js:67)
at CompiledTemplate.proxyViewClass.View_TypesContentComponent0.detectChangesInternal (/PaletteSchemaModule/TypesContentComponent/component.ngfactory.js:592)
at CompiledTemplate.proxyViewClass.AppView.detectChanges (view.js:425)
at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (view.js:620)
Here is a slice of my tabset template that shows tab content body based on ngSwitch and ngSwitchCase:
<div class="schema-palette" [ngSwitch]="tabId">
<myapp-base-palette *ngSwitchCase="'T'">
<myapp-tabset [tabType]="'T'" (onTabSelected)="tabChanged($event)" palette-tabset></myapp-tabset>
<myapp-tab-community palette-content-region></myapp-tab-community>
</myapp-base-palette>
</div>
Here is my myapp-tab-community component template:
<div class="community-buffer" ngFor="let miniCardGrp of miniCardGrps; let i = index">
<div style="height:5px" *ngIf="i === 0"></div>
<myapp-dnd-mini-card-group [miniCardGrp]="miniCardGrp"></myapp-dnd-mini-card-group>
</div>
Here is myapp-tab-community component typescript:
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { PalettesService } from '../../palettes.service';
import { MiniCardGroup } from '../../palettes.helpers';
import { DnDMiniCardGroupComponent } from '../../shared/dnd-mini-card-group/dnd-mini-card-group.component';
#Component({
selector: 'myapp-tab-community',
templateUrl: './tab-community.component.html',
styleUrls: ['./tab-community.component.scss'],
providers: [PalettesService]
})
export class TabCommunityComponent implements OnInit, AfterViewInit {
miniCardGrps: MiniCardGroup[];
constructor(private palettesService: PalettesService) {}
ngOnInit() {
this.miniCardGrps = [];
this.miniCardGrps = this.palettesService.getTemplateTypes();
// DATA IS SUCCESSFULLY RETRIEVED
console.log('TabCommunityComponent.ngOnInit = ' + JSON.stringify(this.miniCardGrps));
}
ngAfterViewInit() {
// ERROR BLOCKS EVALUATION
console.log('TabCommunityComponent.ngAfterViewInit = ' + JSON.stringify(this.miniCardGrps));
}
trackByMiniCardGroup(index: number, miniCardGrp: MiniCardGroup) {
return miniCardGrp.category;
}
}
An array of myapp-dnd-mini-card-group components should be rendered but the error is thrown from its template.
The template file:
<div class="mini-card-grp-collapse">
<div class="mini-card-grp-collapse-row">
<div class="mini-card-grp-title">
<a data-toggle="collapse" [href]="href" aria-expanded="true" attr.aria-controls="{{ ariaControls }}">
<!-- THIS LINE THROWS -->
<md-icon class="white-text md-16">play_arrow</md-icon>{{ miniCardGrp.category }}
</a>
</div>
<div class="mini-card-grp-title-helper">
<md-icon class="white-text md-24">help_outline</md-icon>
</div>
</div>
</div>
<div class="collapse in" [id]="id">
<div class="mini-card-wrapper" *ngFor="let miniCard of miniCardGrp.miniCards">
<myapp-dnd-mini-card [miniCard]="miniCard"></myapp-dnd-mini-card>
</div>
</div>
Now the typescript file:
import { Component, OnInit, AfterViewInit, Input } from '#angular/core';
import { MathHelper } from '../../../../utils/math';
import { MiniCardGroup } from '../../palettes.helpers';
import { DndMiniCardComponent } from '../dnd-mini-card/dnd-mini-card.component';
#Component({
selector: 'myapp-dnd-mini-card-group',
templateUrl: './dnd-mini-card-group.component.html',
styleUrls: ['./dnd-mini-card-group.component.scss']
})
export class DnDMiniCardGroupComponent implements OnInit, AfterViewInit
{
#Input() miniCardGrp: MiniCardGroup;
#Input() href: string;
#Input() id: string;
#Input() ariaControls: string;
constructor() {
let mathHelper = new MathHelper();
let random$ = mathHelper.getRandomNumberString();
this.id = random$;
this.href = '#' + random$;
this.ariaControls = random$;
}
ngOnInit() {
// IS UNDEFINED
console.log('DnDMiniCardGroupComponent.ngOnInit = ' + JSON.stringify(this.miniCardGrp));
}
ngAfterViewInit() {
// IS NEVER REACHED
console.log('DnDMiniCardGroupComponent.ngAfterViewInit = ' + JSON.stringify(this.miniCardGrp));
}
}
What am I doing wrong here?
As requested, here is what the mini-card-group component looks like:
<div class="btn-grp-collapse">
<div class="btn-grp-collapse-row">
<div class="btn-grp-title">
<a data-toggle="collapse" [href]="href" aria-expanded="true" attr.aria-controls="{{ ariaControls }}">
<md-icon class="white-text md-16">play_arrow</md-icon>{{ dndButtonGrp.title }}
</a>
</div>
<div class="btn-grp-title-helper">
<md-icon class="white-text md-24">help_outline</md-icon>
</div>
</div>
</div>
<div class="collapse in" [id]="id">
<div class="btn-wrapper" *ngFor="let dndButton of dndButtonGrp.dndButtons">
<myapp-dnd-button [dndButton]="dndButton"></myapp-dnd-button>
</div>
</div>
Its typescript file:
import { Component, Input, OnInit } from '#angular/core';
import { MathHelper } from '../../../../utils/math'
import { DnDButtonGroup } from './dnd-button-group.helpers';
import { DndButtonComponent } from '../dnd-button/dnd-button.component';
#Component({
selector: 'myapp-dnd-button-group',
templateUrl: './dnd-button-group.component.html',
styleUrls: ['./dnd-button-group.component.scss']
})
export class DnDButtonGroupComponent implements OnInit {
#Input() dndButtonGrp: DnDButtonGroup;
#Input() href: string;
#Input() id: string;
#Input() ariaControls: string;
constructor() {
let mathHelper = new MathHelper();
let random$ = mathHelper.getRandomNumberString();
this.id = random$;
this.href = '#' + random$;
this.ariaControls = random$;
}
ngOnInit() {}
}
When you call the service the data might not be arrived yet, you need to subscribe to the service to get the data
ngOnInit() {
this.palettesService
.getTemplateTypes()
.subscribe(data => this.miniCardGrps = data);
}
I needed to implement dynamic components to render content for each tab. Here is the relevant link for a full background:
https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
Related
I am making a list of students. The input field, add button, and update button are inside one child and in other child there is the list with the delete and edit buttons. Both are handled in the parent component.
When I click edit button, I would like the input filed to have a value from that list and to be able to update the list.
parent html
<ul>
<app-list-item
*ngFor="let studentName of students; let i = index"
[name]="studentName"
[index]="i"
(deleteNameByIndex)="onemitDeleteNameByIndex($event)"
(editNameById)="onemitEditNameById($event)"
>
</ul>
parent .ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
students = ['huzaifa', 'hunzila', 'waqar', 'sibte', 'shahzeen'];
student = '';
onemitAddNewName(newName: string) {
this.students.push(newName);
}
onemitDeleteNameByIndex(index: number) {
this.students.splice(index, 1);
}
onemitEditNameById(student: any) {
this.student = student;
console.log('app student :>> ', this.student);
}
}
child 1 html
<input
type="text"
placeholder="enter name"
[value]="name"
(input)="oninputSetName($event)"
/>
{{ student }}
{{ name }}
<button (click)="onclickEmitNewName()">Add</button>
<button>Update</button>
child 1 .ts
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'app-list-form',
templateUrl: './list-form.component.html',
styleUrls: ['./list-form.component.css'],
})
export class ListFormComponent {
name = '';
#Output() newName = new EventEmitter<string>();
#Input() student = '';
oninputSetName(event: any) {
this.name = event.target.value;
}
onclickEmitNewName() {
this.newName.emit(this.name);
}
updateInput() {
let obj = { name: this.student };
console.log('list-form student :>> ', this.student);
}
}
child 2 html
{{ name }} -- {{ index }}
<button (click)="onclickDeleteName()">delete</button>
<button (click)="onclickEditName()">edit</button>
child 2 .ts
#Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.css'],
})
export class ListItemComponent {
#Input() name = '';
#Input() index = 0;
#Output() deleteNameByIndex = new EventEmitter<number>();
#Output() editNameById = new EventEmitter<any>();
onclickDeleteName() {
this.deleteNameByIndex.emit(this.index);
}
onclickEditName() {
let obj = { index: this.index, name: this.name };
this.editNameById.emit(obj);
}
}
Or even in a more elegant way, you could use a helper service to solve your communication issue.
Below you could find a sample service:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class MessageService {
private messageSource = new Subject<string>();
currentMessage = this.messageSource.asObservable();
constructor() {}
changeMessage(message: string) {
this.messageSource.next(message);
}
}
import { Component } from '#angular/core';
import { MessageService } from './message.service';
#Component({
selector: 'app-sender',
template: `
<button (click)="sendMessage()">Send Message</button>
`
})
export class SenderComponent {
constructor(private messageService: MessageService) {}
sendMessage() {
this.messageService.changeMessage('Hello from Sender Component');
}
}
import { Component } from '#angular/core';
import { MessageService } from './message.service';
#Component({
selector: 'app-receiver',
template: `
<p>{{ message }}</p>
`
})
export class ReceiverComponent {
message: string;
constructor(private messageService: MessageService) {
this.messageService.currentMessage.subscribe(message => {
this.message = message;
});
}
}
Since angular is Pass By reference. You can take advantage of that. when you do that you don't even have to emit the changed value.
For Example, In your code at Child 1:
Rather than emitting a local variable. All you can do is assign the value to #Input.
Here is a example:
#Input existingStudentName: string;
localName: string = existingStudentName;
onUserUpdate(){
existingStudentName = localName;
//No need to emmit since existingStudentName is updated here
//It will update in parent or anyone who refer it
}
<input type="text" [(ng-Model)]="localName">
<input type="button" (click)="onUserUpdate()">
Parent Html
<section>
<!-- input field where i enter my data -->
<app-list-form
(newName)="onemitAddNewName($event)"
[student]="student"
(updatedName)="updateThisNameInList($event)"
></app-list-form>
<!-- rendering my list -->
<ul>
<app-list-item
*ngFor="let studentName of students; let i = index"
[name]="studentName"
[index]="i"
(deleteNameByIndex)="onemitDeleteNameByIndex($event)"
(editNameById)="onemitEditNameById($event)"
[
></app-list-item>
</ul>
</section>
Parent ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
students = ['huzaifa', 'hunzila', 'waqar', 'sibte', 'shahzeen'];
student = null;
onemitAddNewName(newName: string) {
this.students.push(newName);
}
onemitDeleteNameByIndex(index: number) {
this.students.splice(index, 1);
}
onemitEditNameById(student: any) {
this.student = student;
}
updateThisNameInList(student: any) {
let newName = student.name;
let index = student.index;
this.students.splice(index, 1, newName);
}
}
child 1 html
<input
type="text"
placeholder="enter name"
[(ngModel)]="name"
(input)="oninputSetName($event)"
/>
<!-- {{ student?.name ?? "" }}
{{ name }} -->
<button (click)="onclickEmitNewName()">Add</button>
<button (click)="onclickEmitUpdateName()">Update</button>
child 1 ts
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
} from '#angular/core';
#Component({
selector: 'app-list-form',
templateUrl: './list-form.component.html',
styleUrls: ['./list-form.component.css'],
})
export class ListFormComponent implements OnChanges {
name = '';
#Output() newName = new EventEmitter<string>();
#Input() student: any = null;
#Output() updatedName = new EventEmitter<any>();
oninputSetName(event: any) {
this.name = event.target.value;
}
ngOnChanges(changes: SimpleChanges): void {
console.log('list-form: changes happen ', changes);
this.name = changes['student']?.currentValue?.name ?? '';
}
change(event: any) {
this.name = event.target.value;
}
onclickEmitNewName() {
this.newName.emit(this.name);
this.name = '';
}
onclickEmitUpdateName() {
// if (this.name == '') return;
if (!this.name) return;
this.updatedName.emit({
name: this.name,
index: this.student.index,
});
}
}
child 2 html
<li>
{{ name }} -- {{ index }}
<button (click)="onclickDeleteName()">delete</button>
<button (click)="onclickEditName()">edit</button>
</li>
child 2 ts
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.css'],
})
export class ListItemComponent {
#Input() name = '';
#Input() index = 0;
#Output() deleteNameByIndex = new EventEmitter<number>();
#Output() editNameById = new EventEmitter<any>();
onclickDeleteName() {
this.deleteNameByIndex.emit(this.index);
}
onclickEditName() {
let obj = {
index: this.index,
name: this.name,
};
this.editNameById.emit(obj);
}
}
I have a template like the one below:
card.component.html
<mat-card class="mat-elevation-z4">
<img mat-card-image src="{{ item.media_url }}" />
<mat-card-content class="custom">
<p>{{ item.caption }}</p>
</mat-card-content>
</mat-card>
It is a Material Card used inside a parent View that creates a grid with cards, each having a specific item.media_url and item.caption
events.component.ts
<div class="content">
<div fxLayout="row wrap" fxLayoutGap="16px grid">
<div
fxFlex="25%"
fxFlex.md="33%"
fxFlex.sm="50%"
fxFlex.xs="100%"
*ngFor="let item of events"
>
<app-card [item]="item" #myCard></app-card>
<button mat-button (click)="openDialog(myCard)">SCOPRI DI PIU'</button>
</div>
</div>
</div>
As you can see, inside the ngFor loop, below each card, there's a button that is supposed to open a dialog showing only one card at a time (the one above the clicked button in the grid).
I would like to put very the same card with its item.media_url and item.caption in this dialog, so I thought to use the data property of MatDialog to do this.
card.component.ts
import { Component, OnInit, Input, Inject, Optional } from '#angular/core';
import { MAT_DIALOG_DATA } from '#angular/material/dialog';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
#Input() item : any;
constructor(#Optional() #Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit(): void {
}
}
events.component.ts
import { Component, OnInit } from '#angular/core';
import { EventsService } from '../../shared/services/events.service';
import { MatDialog } from '#angular/material/dialog';
import { CardComponent } from 'src/app/shared/card/card.component';
#Component({
selector: 'app-events',
templateUrl: './events.component.html',
styleUrls: ['./events.component.css'],
})
export class EventsComponent implements OnInit {
events: any[];
constructor(private eventsService: EventsService, public dialog: MatDialog) {}
ngOnInit(): void {
this.getEvents();
}
getEvents(): void {
this.eventsService.getEvents().subscribe((response: any) => {
this.events = response.data;
console.log(this.events);
});
}
openDialog(card: any) {
this.dialog.open(CardComponent, {
data: {
item: card,
},
});
}
}
However, when I do this, I need to pass data to this.dialog.open(), not item, nor card.
Right now I am getting the error ERROR TypeError: ctx.item is undefined (which I perfectly understand why I am getting).
Is there a way I can somehow "alias" data as item, or maybe a better way to do this?
To make it work we did the following:
openDialog(card: CardComponent) { <-- strongly type
console.log(card.item); <-- access item
this.dialog.open(CardComponent, {
data: {
item: card.item <-- pass it as param
}
});
}
next in card.component.ts set the 'item` property.
constructor(#Optional() #Inject(MAT_DIALOG_DATA) public data: any) {
if (data) { <-- only if data is injected
this.item = data.item; <-- set item
}
}
Working Stackblitz
Robert's coment under your question answers the template / component part.
As for the data alias question, the answer is no. data is a property of the MatDialogConfig class, as shown here and to the best of my knowledge, you can't change it.
I am facing a weird issue with the ElementRef in Angular 5.
I have a home component which contains two popups, which consists of different component ListPopUp and tile. I am iterating through a list and creating multiple tiles on the home page. When you click on a tile it will open a listPopup.
From the list popUp you can click a function that will trigger a event in Parent and thus trigger a function in tile component.
Everything works file except when we trigger the function second time the myBar ElementRef (in tile component class) returns undefined. I have tried accessing the bar directly using getElementById("myBar") but it also becomes undefined the second time. I am not sure why this is happening, I have tried to modify the code and look for any explanation in Angular documentation but nothing is helping.
I have provided the code for all the three components.
Please let me know if further info is required.
Here is the code:
TileComponent:
import { Component, Input, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'tile',
templateUrl: 'tile.html'
})
export class TileComponent {
#Input('tileinfo') tileinfo;
#ViewChild('myBar') myBar:ElementRef;
constructor() {
};
ngOnInit() {
};
move(value) {
let elem = this.myBar;
let id = setInterval(frame, this.tileinfo.progressSpeed * 1000);
this.tileinfo.active = value;
let val = this.tileinfo;
val.allowNext = false;
function frame() {
if (val.progress >= 100) {
clearInterval(id);
delete val.active;
val.allowNext = true;
val.progress = 0;
} else {
val.progress++;
elem.nativeElement.style.width = val.progress + '%';
}
}
}
}
HomeComponent Code:
import { Component, ViewChild } from '#angular/core';
import { GlobalProvider } from '../../providers/global/global';
import { CommonMethodsProvider } from '../../providers/common-methods/common-methods';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
#ViewChild('tileComponent') tileComponent: any;
tileInfoArr: Array<any> = [];
ageList: Array<any> = [];
gameObject: any;
modalInfo: any = {};
modalList: any = {};
constructor(public navCtrl: NavController, private globalService: GlobalProvider, private commonService: CommonMethodsProvider) {
this.globalService.getGameDummy().subscribe(data => this.gameSetupHelper(data), error => console.log(error));
}
ngOnInit() {
}
fillTiles() {
this.tileInfoArr = [
{
name: 'Research',
progress: 1,
description: "Description Here",
progressSpeed: this.gameObject.researchProgress,
allowNext: true,
},
];
}
tileClicked(tileInfo) {
switch (tileInfo.name) {
case 'Research':
if (this.tileInfoArr[0].allowNext)
this.modalList = this.commonService.generateModalList("Research List", this.globalService.getAgeDataObj().researchList);
break;
default:
break;
}
};
listItemClicked(stuff) {
if (stuff.data === "age") {
this.tileComponent.move(this.gameObject.nextAgeDisplayName);
} else {
this.tileComponent.move(stuff.data.name);
}
};
}
ListPopupComponent:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'list-popup',
templateUrl: 'list-popup.html'
})
export class ListPopupComponent {
#Input('modalList') modalList;
#Output() listItemClicked = new EventEmitter();
text: string;
constructor() {
};
startStuff(listItem): void {
this.listItemClicked.emit({
data: listItem
});
}
closePopup() {
this.modalList.showPopup = false;
};
}
HTML :
HomeComponent:
<div padding style="padding-top: 0px">
<div class="tile-container-container">
<div class="tile-container" *ngFor="let tileinfo of tileInfoArr;">
<tile [tileinfo]="tileinfo" (click)="tileClicked(tileinfo)" #tileComponent></tile>
</div>
</div>
</div>
<info-popup [modalInfo]="modalInfo"></info-popup>
<list-popup (listItemClicked) = "listItemClicked($event)" [modalList]="modalList"></list-popup>
TileHTML:
<div class="tile-box">
<div class="tile-inside">
<h6>
{{tileinfo.name}}
</h6>
<p>
{{tileinfo.description}}
</p>
</div>
<footer *ngIf="tileinfo.progress !== 0">
<div id="myProgress">
<div id="myBar" #myBar></div>
</div>
</footer>
</div>
ListPopupHTML
<div *ngIf="modalList.showPopup" id="myModal" class="modal">
<div class="modal-content card">
<span class="close" (click)="closePopup()">
x
</span>
<div>
<h6>
{{modalList.title}}
</h6>
</div>
<hr>
<div *ngIf="modalList.list && modalList.list.length > 0;else ageChange">
<ul class="list">
<li class="item" (click)="startStuff(listInfo)" *ngFor="let listInfo of modalList.list; index as i; even as isEven; odd as isOdd" class="">
<div>
<span class="">{{listInfo.name}}</span>
<br>
<span>{{listInfo.description}}</span>
</div>
</li>
</ul>
</div>
<ng-template #ageChange>
<div class="age-Change">
<h1>
Enter New Age
</h1>
<div (click)="startStuff('age')" class="new-age-icon">
</div>
</div>
</ng-template>
</div>
</div>
Answering my own question.
Silliest mistake that cost me a day of work.
Using *ngIf="tileinfo.progress !== 0" in the tile template. and then making val.progress = 0; in tileComponet. This was removing the div at the end thus the element was not coming because there was no element at all.
Sorry for wasting everyone's time, who took time to look into the issue...
I am actually trying to inject the array and the data inside the array to another component but is constantly getting errors.
My list.component.ts
Here i injected the itemList array from app.component and this component is working just fine. No errors here.
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import {List} from './list.model'
#Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
#Input() itemList: List[] = [];
#Output() onItemSelected: EventEmitter<List>;
private currentItem: List;
constructor(){
this.onItemSelected = new EventEmitter();
}
onClick(list: List): void {
this.currentItem = list;
this.onItemSelected.emit(list);
console.log(`clicking list title: ${list.title}`);
}
isSelected(list: List): boolean {
if (!list || !this.currentItem) {
return false;
}
return list.title === this.currentItem.title;
}
ngOnInit() {
}
}
list.component.html
Here i try to inject both the array and then using ngFor i try to inject the single list also.
<div class="ui grid posts">
<app-list-row
[lists]="itemList"
*ngFor="let list of itemList"
[list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)">
</app-list-row>
</div>
list-row.component.ts
I am mainly trying to input the array in this component so that i can use the splice method to delete my list. I tried the delete list;method but this says i cannot use delete in strict mode. Therefore i am trying to input the array and use the splice method.
import { Component, OnInit, Input} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
inputs: ['list: List'],
templateUrl: './list-row.component.html',
styleUrls: ['./list-row.component.css'],
host: {'class': 'row'},
})
export class ListRowComponent implements OnInit {
list: List;
#Input() lists: List[];
deletelist(list: List): void {
let index: number = this.lists.indexOf(list);
if (index !== -1) {
this.lists.splice(index, 1);
}
}
ngOnInit() {
}
}
list-row.component.html
In this write a div and use a lable of delete icon and give an event of click with the "deleteList(list)".
<div class="Eight wide column left aligned title">
<div class="value">
<div class = "hello">
<b>
{{ list.title | uppercase }}
</b>
<div style="float: right;" class="ui label">
<i class="delete icon"
(click)="deleteList(list)"
></i>
</div>
</div>
</div>
</div>
These are my codes and i dont know whether i can do the dependency injection of both the array and its single data in the array. If i can, what ways are there to do it. while running in server the console error is
Unhandled Promise rejection: Template parse errors:
Can't bind to 'list' since it isn't a known property of 'app-list-row'.
1. If 'app-list-row' is an Angular component and it has 'list' input, then verify that it is part of this module.
2. If 'app-list-row' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component to suppress this message.
("
[lists]="itemList"
*ngFor="let list of itemList"
[ERROR ->][list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)">
"): ListComponent#4:2 ; Zone: <root> ; Task: Promise.then ; Value: SyntaxError {__zone_symbol__error: Error: Template parse errors:
Can't bind to 'list' since it isn't a known property of 'app-list-row'…, _nativeError: ZoneAwareError, __zone_symbol__currentTask: ZoneTask, __zone_symbol__stack: "Error: Template parse errors:↵Can't bind to 'list'…ttp://localhost:4200/polyfills.bundle.js:6060:47)", __zone_symbol__message: "Template parse errors:↵Can't bind to 'list' since …lected]="isSelected(list)">↵"): ListComponent#4:2"} Error: Template parse errors:
Can't bind to 'list' since it isn't a known property of 'app-list-row'.
1. If 'app-list-row' is an Angular component and it has 'list' input, then verify that it is part of this module.
2. If 'app-list-row' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component to suppress this message.
("
[lists]="itemList"
*ngFor="let list of itemList"
[ERROR ->][list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)">
Thank you.
Add #Input() to list variable in ListRowComponent class and check if it is working or not and remove inputs from metadata.
import { Component, OnInit, Input} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
templateUrl: './list-row.component.html',
styleUrls: ['./list-row.component.css'],
host: {'class': 'row'},
})
export class ListRowComponent implements OnInit {
#Input() list: List;
#Input() lists: List[];
deletelist(list: List): void {
let index: number = this.lists.indexOf(list);
if (index !== -1) {
this.lists.splice(index, 1);
}
}
ngOnInit() {
}
}
or
Remove :List from inputs as
import { Component, OnInit, Input} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
templateUrl: './list-row.component.html',
inputs :['list']
styleUrls: ['./list-row.component.css'],
host: {'class': 'row'},
})
export class ListRowComponent implements OnInit {
list: List;
#Input() lists: List[];
deletelist(list: List): void {
let index: number = this.lists.indexOf(list);
if (index !== -1) {
this.lists.splice(index, 1);
}
}
ngOnInit() {
}
}
I got the answer. I did not do two input bindings but i created a custom event in the list-row.component and emmited the list to list.Component.
import { Component, Input, Output, EventEmitter} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
templateUrl: './list-row.component.html',
styleUrls: ['./list-row.component.css'],
host: {
'class': 'row'
}
})
export class ListRowComponent {
#Input() list: List;
#Output() deleted = new EventEmitter<List>();
deletedl() {
const listing: List = this.list;
this.deleted.emit(listing);
}
In the template I used the click event to call the deletel() method.
<div class="Eight wide column left aligned title">
<div class="value">
<div class = "hello">
<b>
{{ list.title | uppercase }}
</b>
<div style="float: right;" class="ui label">
<i class="delete icon"
(click)="deletedl()">
</i>
</div>
</div>
</div>
</div>
Then I just called the event in the list.component
list.component.html
<div class="ui grid posts">
<app-list-row
*ngFor="let list of itemList"
[list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)"
(deleted)="deletedl($event)">
</app-list-row>
</div>
Then i called a method in list.component to delete the list from the array using Splice method.
list.component
import { Component, Input} from '#angular/core';
import {List} from './list.model';
#Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent {
#Input() itemList: List[] = [];
private currentItem: List;
onClick(list: List): void {
this.currentItem = list;
console.log(`clicking list title: ${list.title}`);
}
isSelected(list: List): boolean {
if (!list || !this.currentItem) {
return false;
}
return list.title === this.currentItem.title;
}
deletedl(list: List) {
console.log(`deleting list title: ${list.title}`);
let index: number = this.itemList.indexOf(list);
if (index !== -1) {
this.itemList.splice(index, 1);
}
}
}
I have learned that if we want to get some input from the parent component than using property binding helps and when we want to run some output from our parent component then event binding is helpful.
I would like to pass the parent component's FormGroup to its child for the purpose of displaying an error-message using the child.
Given the following parent:
parent.component.ts
import { Component, OnInit } from '#angular/core'
import {
REACTIVE_FORM_DIRECTIVES, AbstractControl, FormBuilder, FormControl, FormGroup, Validators
} from '#angular/forms'
#Component({
moduleId: module.id,
selector: 'parent-cmp',
templateUrl: 'language.component.html',
styleUrls: ['language.component.css'],
directives: [ErrorMessagesComponent]
})
export class ParentCmp implements OnInit {
form: FormGroup;
first: AbstractControl;
second: AbstractControl;
constructor(private _fb: FormBuilder) {
this.first = new FormControl('');
this.second = new FormControl('')
}
ngOnInit() {
this.form = this._fb.group({
'first': this.first,
'second': this.second
});
}
}
I would now like to pass the form:FormGroup variable above to the child component below:
error-message.component.ts
import { Component, OnInit, Input } from '#angular/core'
import { NgIf } from '#angular/common'
import {REACTIVE_FORM_DIRECTIVES, FormGroup } from '#angular/forms'
#Component({
moduleId: module.id,
selector: 'epimss-error-messages',
template: `<span class="error" *ngIf="errorMessage !== null">{{errorMessage}}</span>`,
styles: [],
directives: [REACTIVE_FORM_DIRECTIVES, NgIf]
})
export class ErrorMessagesComponent implements OnInit {
#Input() ctrlName: string
constructor(private _form: FormGroup) { }
ngOnInit() { }
get errorMessage() {
// Find the control in the Host (Parent) form
let ctrl = this._form.find(this.ctrlName);
console.log('ctrl| ', ctrl);
// for (let propertyName of ctrl.errors) {
// // If control has a error
// if (ctrl.errors.hasOwnProperty(propertyName) && ctrl.touched) {
// // Return the appropriate error message from the Validation Service
// return CustomValidators.getValidatorErrorMessage(propertyName);
// }
// }
return null;
}
The constructor formGroup represents the FormGroup of the parent - in its present form it does not work.
I am trying to follow this obsolete example at http://iterity.io/2016/05/01/angular/angular-2-forms-and-advanced-custom-validation/
In the parent component do this:
<div [formGroup]="form">
<div>Your parent controls here</div>
<your-child-component [formGroup]="form"></your-child-component>
</div>
And then in your child component you can get hold of that reference like so:
export class YourChildComponent implements OnInit {
public form: FormGroup;
// Let Angular inject the control container
constructor(private controlContainer: ControlContainer) { }
ngOnInit() {
// Set our form property to the parent control
// (i.e. FormGroup) that was passed to us, so that our
// view can data bind to it
this.form = <FormGroup>this.controlContainer.control;
}
}
You can even ensure either formGroupName or [formGroup] is specified on your component by changing its selector like so:
selector: '[formGroup] epimss-error-messages,[formGroupName] epimss-error-messages'
This answer should be sufficient for your needs, but if you want to know more I've written a blog entry here:
https://mrpmorris.blogspot.co.uk/2017/08/angular-composite-controls-formgroup-formgroupname-reactiveforms.html
For Angular 11 I tried all the above answers, and in different combinations, but nothing quite worked for me. So I ended up with the following solution which worked for me just as I wanted.
TypeScript
#Component({
selector: 'fancy-input',
templateUrl: './fancy-input.component.html',
styleUrls: ['./fancy-input.component.scss']
})
export class FancyInputComponent implements OnInit {
valueFormGroup?: FormGroup;
valueFormControl?: FormControl;
constructor(
private formGroupDirective: FormGroupDirective,
private formControlNameDirective: FormControlName
) {}
ngOnInit() {
this.valueFormGroup = this.formGroupDirective.form;
this.valueFormControl = this.formGroupDirective.getControl(this.formControlNameDirective);
}
get controlName() {
return this.formControlNameDirective.name;
}
get enabled() {
return this.valueFormControl?.enabled
}
}
HTML
<div *ngIf="valueFormGroup && valueFormControl">
<!-- Edit -->
<div *ngIf="enabled; else notEnabled" [formGroup]="valueFormGroup">
<input class="input" type="text" [formControlName]="controlName">
</div>
<!-- View only -->
<ng-template #notEnabled>
<div>
{{valueFormControl?.value}}
</div>
</ng-template>
</div>
Usage
Note that I had to add ngDefaultControl otherwise it would give no default value accessor error in console (if somebody knows how to get rid of it without error - will be much appreciated).
<form [formGroup]="yourFormGroup" (ngSubmit)="save()">
<fancy-input formControlName="yourFormControlName" ngDefaultControl></fancy-input>
</form>
this is an example of child component used inside parent formGroup :
child component ts:
import { Component, OnInit, Input } from '#angular/core';
import { FormGroup, ControlContainer, FormControl } from '#angular/forms';
#Component({
selector: 'app-date-picker',
template: `
<mat-form-field [formGroup]="form" style="width:100%;">
<input matInput [matDatepicker]="picker" [placeholder]="placeHolder" [formControl]="control" readonly>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<mat-icon (click)="clearDate()">replay</mat-icon>`,
styleUrls: ['./date-picker.component.scss']
})
export class DatePickerComponent implements OnInit {
public form: FormGroup;
public control : FormControl;
#Input() controlName : string;
#Input() placeHolder : string;
constructor(private controlContainer: ControlContainer) {
}
clearDate(){
this.control.reset();
}
ngOnInit() {
this.form = <FormGroup>this.controlContainer.control;
this.control = <FormControl>this.form.get(this.controlName);
}
}
css date picker :
mat-icon{
position: absolute;
left: 83%;
top: 31%;
transform: scale(0.9);
cursor: pointer;
}
and used like this :
<app-date-picker class="col-md-4" [formGroup]="feuilleForm" controlName="dateCreation" placeHolder="Date de création"></app-date-picker>
Parent Component :
#Component({
selector: 'app-arent',
templateUrl: `<form [formGroup]="parentFormGroup" #formDir="ngForm">
<app-child [formGroup]="parentFormGroup"></app-child>
</form> `
})
export class ParentComponent implements {
parentFormGroup :formGroup
ngOnChanges() {
console.log(this.parentFormGroup.value['name'])
}
}
Child Component :
#Component({
selector: 'app-Child',
templateUrl: `<form [formGroup]="childFormGroup" #formDir="ngForm">
<input id="nameTxt" formControlName="name">
</form> `
})
export class ChildComponent implements OnInit {
#Input() formGroup: FormGroup
childFormGroup :FormGroup
ngOnInit() {
// Build your child from
this.childFormGroup.addControl('name', new FormControl(''))
/* Bind your child form control to parent form group
changes in 'nameTxt' directly reflect to your parent
component formGroup
*/
this.formGroup.addControl("name", this.childFormGroup.controls.name);
}
}
The ngOnInit was important - this did not work in the constructor.
And I prefer looking for the FormControlDirective - its the first one found in the child component's ancestor hierarchy
constructor(private formGroupDirective: FormGroupDirective) {}
ngOnInit() {
this.formGroupDirective.control.addControl('password', this.newPasswordControl);
this.formGroupDirective.control.addControl('confirmPassword', this.confirmPasswordControl);
this.formGroup = this.formGroupDirective.control;
}
I would do this in this way, i have passed child form data as group to parent so you can have separated form data in submit call.
Parent:
<form [formGroup]="registerStudentForm" (ngSubmit)="onSubmit()">
<app-basic-info [breakpoint]="breakpoint" [formGroup]="registerStudentForm"></app-basic-info>
<button mat-button>Submit</button>
</form>
Child:
<mat-card [formGroup]="basicInfo">
<mat-card-title>Basic Information</mat-card-title>
<mat-card-content>
<mat-grid-list
[gutterSize]="'20px'"
[cols]="breakpoint"
rowHeight="60px"
>
<mat-grid-tile>
<mat-form-field appearance="legacy" class="full-width-field">
<mat-label>Full name</mat-label>
<input matInput formControlName="full_name" />
</mat-form-field>
</mat-grid-tile>
</mat-grid-list>
</mat-card-content>
</mat-card>
Parent.ts:
export class RegisterComponent implements OnInit {
constructor() { }
registerForm = new FormGroup({});
onSubmit() {
console.warn(this.registerForm.value);
}
}
Child.ts
export class BasicInfoComponent implements OnInit {
#Input() breakpoint;
#Input() formGroup: FormGroup;
basicInfo: FormGroup;
constructor() { }
ngOnInit(): void {
this.basicInfo = new FormGroup({
full_name: new FormControl('Riki maru'),
dob: new FormControl(''),
});
this.formGroup.addControl('basicInfo', this.basicInfo);
}
}
Here in your child form components #Input() formGroup: FormGroup; part would be reference of parent component
I would pass the form as an input to the child component;
#Component(
{
moduleId: module.id,
selector: 'epimss-error-messages',
template: `
<span class="error" *ngIf="errorMessage !== null">{{errorMessage}}</span>`,
styles: [],
directives: [REACTIVE_FORM_DIRECTIVES, NgIf]
})
export class ErrorMessagesComponent implements OnInit {
#Input()
ctrlName: string
#Input('form') _form;
ngOnInit() {
this.errorMessage();
}
errorMessage() {
// Find the control in the Host (Parent) form
let ctrl = this._form.find(this.ctrlName);
console.log('ctrl| ', ctrl)
// for (let propertyName of ctrl.errors) {
// // If control has a error
// if (ctrl.errors.hasOwnProperty(propertyName) && ctrl.touched) {
// // Return the appropriate error message from the Validation Service
// return CustomValidators.getValidatorErrorMessage(propertyName);
// }
// }
return null;
}
And of course you'll need o pass the form from the parent component to the child, which you can do it in different ways , but the simplest is :
Somewhere in your parent ;
<epimss-error-messages [form]='form'></epimss-error-messages>
If you want to access the parent from the child component, you can access parent property of the FormControl instance, https://angular.io/api/forms/AbstractControl#parent
To get the parent error:
const parent = control.parent;
const errors = parent.errors;