Component communication in Angular fails from child to parent component - javascript

I am trying to apply #Output in my sample project following this tutorial. I am unable to get it working. There are no compilation errors or any specific error for this in console. I am unable to figure out how to proceed with debugging #Output using developer tools. Below are my files.
app-component.ts
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
serverElements = [{type: 'server', name: 'testServer', content: 'Just a Server'}];
constructor(){
console.log(this.serverElements);
}
onServerAdded(serverData: {serverName: string, serverContent: string}) {
console.log("Server Added");
this.serverElements.push({
type: 'server',
name: serverData.serverName,
content: serverData.serverContent
});
console.log("Server Added");
}
onBlueprintAdded(blueprintData: {serverName: string, serverContent: string}) {
this.serverElements.push({
type: 'blueprint',
name: blueprintData.serverName,
content: blueprintData.serverContent
});
}
}
app-component.html
<app-cockpit (serverAdded)="onServerAdded($event)"
(blueprintAdded)="onBlueprintAdded($event)"></app-cockpit>
<hr>
<div class="row">
<div class="col-xs-12">
<app-server-element *ngFor="let serverElement of serverElements" [element] = "serverElement"></app-server-element>
</div>
</div>
</div>
cockpit-component.ts
#Component({
selector: 'app-cockpit',
templateUrl: './cockpit.component.html',
styleUrls: ['./cockpit.component.css']
})
export class CockpitComponent implements OnInit {
#Output() serverAdded = new EventEmitter<{serverName: string, serverContent: string}>();
#Output() blueprintAdded = new EventEmitter<{blueprintName: string, blueprintContent: string}>();
newServerName = '';
newServerContent = '';
constructor() { }
ngOnInit(): void {
}
onAddServer() {
console.log(this.newServerContent);
console.log(this.newServerName);
this.serverAdded.emit({
serverName:this.newServerName,
serverContent:this.newServerContent
});
}
onAddBlueprint() {
this.blueprintAdded.emit({
blueprintName:this.newServerName,
blueprintContent:this.newServerContent
});
}
}
cockpit-component.html
<div class="col-xs-12">
<p>Add new Servers or blueprints!</p>
<label>Server Name</label>
<input type="text" class="form-control" [(ngModel)]="newServerName">
<label>Server Content</label>
<input type="text" class="form-control" [(ngModel)]="newServerContent">
<br>
<button
class="btn btn-primary"
(click)="onAddServer()">Add Server</button>
<button
class="btn btn-primary"
(click)="onAddBlueprint()">Add Server Blueprint</button>
</div>
</div>
I can see the console prints in the browser for cockpit-component.ts file but this does not lead to a call to onServerAdded() method in app component.
It works now after starting angular and vscode again

Related

How to pass variables/member variables from PARENT TO CHILD using ng-content

I have some a component structured like this:
detail-component.ts
<app-base-modal>
<app-body></app-body>
</app-base-modal>
On my base modal component.ts I have this:
base-modal.component.ts
#Component({
selector: 'app-base-modal',
template: `
<div class="modal fade" bsModal #staticModal="bs-modal" [config]="{backdrop: 'static'}" tabindex="-1" role="dialog" aria-labelledby="dialog-static-name">
<button type="button" class="close pull-right" aria-label="Close" (click)="staticModal.hide()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<ng-content></ng-content>
</div>
</div>
`
})
export class BaseModalComponent implements OnInit {
#ViewChild('staticModal', { static: true })
staticModal: ModalDirective;
#Input()
toShowModal: BehaviorSubject<boolean> = new BehaviorSubject(false);
modalIsHidden = false;
constructor(private modalService: BsModalService) { }
ngOnInit() {
this.toShowModal.subscribe(t => {
if (t) {
this.staticModal.show();
}
});
this.staticModal.onHidden.subscribe(() => {
this.modalIsHidden = true;
})
}
closeModal() {
this.staticModal.hide();
}
}
I need to pass the closeModal method or modalIsHidden to ng-content so I can do a cleanup on the projected/child html.
The modal body component or child looks like this:
body.component.ts
#Component({
selector: 'app-body',
template: `
<section *ngIf="hasRespondedWithError || isReasonSubmitSuccessful">
<p class="alert alert-danger" *ngIf="hasRespondedWithError">Something went wrong. Please try again later.</p>
<p class="alert alert-success" *ngIf="isReasonSubmitSuccessful">Thank you! Please allow us to review your return and we will contact you soon</p>
</section>
<form (ngSubmit)="onSubmit()" [formGroup]="sForm">
<div class="form-group">
<textarea type="text" formControlName="reasonTextArea" name="reasonTextArea" placeholder="Return Reason" class="form-control"></textarea>
</div>
<div class="d-flex flex-row justify-content-end">
<button type="submit" class="btn btn-primary ml-2">Submit</button>
</div>
</form>
`
})
export class BodyComponent {
#Output()
returnValueOrder = new EventEmitter<Order>();
toShowLoading = false;
hasRespondedWithError = false;
isReasonSubmitSuccessful = false;
constructor(private fooService: FooService) { }
sForm = new FormGroup({
reasonTextArea: new FormControl('', Validators.required)
});
onSubmit(): void {
this.toShowLoading = true;
this.fooService
.createRequest(this.orderNumber, {
order_id: this.orderId,
reason: this.sForm.value.reasonTextArea
})
.subscribe((order: Order) => {
this.returnValueOrder.emit(order);
}, (err) => {
this.toShowLoading = false;
this.hasRespondedWithError = true;
}, () => {
this.toShowLoading = false;
this.isReasonSubmitSuccessful = true;
});
}
}
So right here I need to check/listen if the modal is closed so can set the isReasonSubmitSuccessful, and hasRespondedWithError to false.
I have read this but it's so confusing. Confusing because templateRef needs to be in the same file so I can reference it like <template #temp></template> using the pound sign?
One of possible solutions might be using dependency injection mechanism:
First determine what you want to pass:
export abstract class ModalContext {
abstract closeModal();
}
Then decouple your parent component from DI:
base-modal.component.ts
#Component({
selector: "app-base-modal",
template: `...`,
providers: [
{
provide: ModalContext,
useExisting: BaseModalComponent
}
]
})
export class BaseModalComponent implements ModalContext, OnInit {
...
closeModal() {
...
}
}
Finally, you can use this ModalContext in your projected component:
body.component.ts
#Component({...})
export class BodyComponent {
...
constructor(... private modalContext: ModalContext) { }
closeModal() {
this.modalContext.closeModal();
}
}
Forked Stackblitz

How to display object data in child component from parent in angular

I am a beginner in Angular.
I have created a demo app.
below is my code
servers.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { Server } from './server.model';
#Component({
selector: 'app-servers',
templateUrl: './servers.component.html',
styleUrls: ['./servers.component.css'],
})
export class ServersComponent implements OnInit {
allowNewServer = false;
serverCreationStatus = 'no server created';
serverName = '';
ipAddress = '';
owner = '';
server: Server[] = [];
serverCreated = false;
constructor() {
}
ngOnInit(): void {}
onCreateServer() {
this.serverCreated = true;
this.serverCreationStatus = 'server created';
this.server_list.push(server;
}
}
I have one model file where i have declared one class and constructor to create server
server.model.ts
export class Server {
public serverName: string;
public ipAddress: string;
public owner: string;
constructor(ame: string, ip: string, owner: string) {
this.serverName = name;
this.ipAddress = ip;
this.owner = owner;
}
}
in servers.component.html i am using ngModel to bind object property with forms field
servers.component.html
<form action="">
<div class="row">
<label for="serverName" class="col-sm-2">Server Name</label>
<input
type="text"
class="form-control col-sm-10"
[(ngModel)]="server.serverName"
[ngModelOptions]="{ standalone: true }"
/>
</div>
<div class="row">
<label for="ipAddress" class="col-sm-2">IP Adress</label>
<input
type="text"
class="form-control col-sm-10"
[(ngModel)]="server.ipAddress"
[ngModelOptions]="{ standalone: true }"
/>
</div>
<div class="row">
<label for="owner" class="col-sm-2">Owner</label>
<input
type="text"
class="form-control col-sm-10"
[(ngModel)]="server.owner"
[ngModelOptions]="{ standalone: true }"
/>
</div>
<button
class="btn btn-primary"
[disabled]="!allowNewServer"
(click)="onCreateServer()"
>
Add server
</button>
</form>
<app-server *ngFor="let s of server" [data]="s"> </app-server>
and my child component `server.component.ts`
import { Component, Input } from '#angular/core';
import { Server } from '../servers/server.model';
#Component({
selector: 'app-server',
templateUrl: './server.component.html',
styles: [
`
.online {
color: white;
}
`,
],
})
export class ServerComponent {
serverStatus = 'offline';
#Input() data: Server;
constructor() {
this.serverStatus = Math.random() > 0.5 ? 'online' : 'offline;';
}
getColor() {
return this.serverStatus === 'online' ? 'green' : 'red';
}
}
my server.component.html
<p
[ngStyle]="{ backgroundColor: getColor() }"
[ngClass]="{ online: serverStatus === 'online' }"
>
Server created with name {{ data }}
</p>
i am gettig two error in above code
when using ngModel in servers.component.html
error TS2339: Property 'ipAddress' does not exist on type 'Server[]'.
34 [(ngModel)]="server.ipAddress"
getting this error for all field
2.not able to display created server data (not able to pass object data to child component)
calling child component in servers.component.html
Please help.
Thanks in advance
You have too many typos and errors, Here is the updated servers and server component classes, Compare this with your question and you can figure out the differences and errors. Hope this works !!
servers.component .ts & .html:
.html:
<form action="">
<div class="row">
<label for="serverName" class="col-sm-6">Server Name</label>
<input
id="serverName"
type="text"
class="form-control col-sm-10"
[(ngModel)]="serverName"
[ngModelOptions]="{ standalone: true }"
/>
</div>
<div class="row">
<label for="ipAddress" class="col-sm-6">IP Adress</label>
<input
id="ipAddress"
type="text"
class="form-control col-sm-10"
[(ngModel)]="ipAddress"
[ngModelOptions]="{ standalone: true }"
/>
</div>
<div class="row">
<label for="owner" class="col-sm-6">Owner</label>
<input
id="owner"
type="text"
class="form-control col-sm-10"
[(ngModel)]="owner"
[ngModelOptions]="{ standalone: true }"
/>
</div>
<button
class="btn btn-primary"
(click)="onCreateServer()">
Add server
</button>
</form>
<app-server *ngFor="let s of server" [data]="s"> </app-server>
.ts:
import { Component, OnInit } from '#angular/core';
import {Server} from '../../models/Server.model';
#Component({
selector: 'app-servers',
templateUrl: './servers.component.html',
styleUrls: ['./servers.component.css']
})
export class ServersComponent implements OnInit {
allowNewServer = false;
serverCreationStatus = 'no server created';
serverName = '';
ipAddress = '';
owner = '';
server: Server[] = [];
serverCreated = false;
constructor() {
}
ngOnInit(): void {}
onCreateServer() {
this.serverCreated = true;
this.serverCreationStatus = 'server created';
this.server.push(new Server(this.serverName, this.ipAddress, this.owner));
}
}
server.component .ts & .html:
.html:
<p
[ngStyle]="{ backgroundColor: getColor() }"
[ngClass]="{ online: serverStatus === 'online' }">
Server created with name {{ data.serverName }}
</p>
.ts:
import {Component, Input} from '#angular/core';
import {Server} from '../../models/Server.model';
#Component({
selector: 'app-server',
templateUrl: './server.component.html',
styleUrls: ['./server.component.css']
})
export class ServerComponent {
#Input() data: Server;
serverStatus = 'offline';
constructor() {
this.serverStatus = Math.random() > 0.5 ? 'online' : 'offline;';
}
getColor() {
return this.serverStatus === 'online' ? 'green' : 'red';
}
}
finally Server model class:
export class Server {
public serverName: string;
public ipAddress: string;
public owner: string;
constructor(name: string, ipAddress: string, owner: string) {
this.serverName = name;
this.ipAddress = ipAddress;
this.owner = owner;
}
}

Angular 2 simple directive file upload

I have some simple directive for styling file upload in Angular CLI, only problem i have is that i use ID, and my directive works when is single on page, but when i have several upload fields of course it will not work.
Here is my code
upload-field.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
#Component({
selector: 'app-upload-field',
templateUrl: './upload-field.component.html',
styleUrls: ['./upload-field.component.scss']
})
export class UploadFieldComponent implements OnInit {
#Input() labelName = 'Blader';
#Input() placeHolderValue = 'Kies bestand';
constructor() { }
ngOnInit() {
}
uploadButton() {
const inputValue = (<HTMLInputElement>document.getElementById('upload-button')).value;
const filename = inputValue.replace(/^.*\\/, '');
(<HTMLInputElement>document.getElementById('upload-file')).value = filename;
}
}
upload-field.component.html
<input id="upload-file" placeholder="{{placeHolderValue}}" disabled="disabled" class="form-control" />
<div class="file-upload">
<span class="btn btn-default btn-lg">{{labelName}}</span>
<input id="upload-button" type="file" class="form-control upload-button" name="upload_file" (change)="uploadButton()" />
</div>

how to hide a form in angular

I have been trying to hide my form and making it appear only when user click on the button. but for some reason, the function I have created never work is there something wrong with my code? and I saw quite a few example that uses angular.module and I tried it but I always get an error message.
transfer.component.html
<form [formGroup]="AddRiciverForm" id="AddRiciverForm" (ngSubmit)="addRiciverFunc()" *ngIf="show">
<div class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<p><input type="email" id="emailTo" formControlName="emailTo" placeholder="Reciver Email" [(ngModel)]="this.AddRiciverForm.value.emailTo" required></p>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<p><button class="btn btn-primary" type="submit" [disabled]="!transferForm.form.valid">Add</button> </p>
</div>
</div>
</div>
</form>
<button ng-click="AddEmailFunc()">add user</button>
transfer.component.ts
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../auth.service';
import { FormBuilder, FormGroup } from '#angular/forms';
import { Router } from '#angular/router';
import { Http } from '#angular/http';
import { PostsService } from '../posts.service';
#Component({
selector: 'app-transfer',
templateUrl: './transfer.component.html',
styleUrls: ['./transfer.component.css']
})
export class TransferComponent implements OnInit {
constructor(private fb: FormBuilder, private http: Http, private router: Router, private auth: AuthService,private postsService: PostsService) { }
transfer = {};
addRiciver = {};
recivers: any;
AddRiciverForm: FormGroup;
transferForm: FormGroup;
public userEmail: string;
show=false;
ngOnInit() {
this.transferForm = this.fb.group({
emailTo: '',
amount: ''
});
this.AddRiciverForm = this.fb.group({
emailTo: ''
});
this.auth.currentEmail.subscribe(email => this.userEmail = email);
this.postsService.getAllReciver().subscribe(reciver => {
this.recivers = reciver;
});
}
transFunds(){
}
addRiciverFunc(){
this.addRiciver={
emailFrom: this.userEmail,
emailTo: this.AddRiciverForm.value.emailTo
}
this.http.post('/transfer', this.addRiciver)
.subscribe(res => {
let id = res['_id'];
}, (err) => {
console.log(err);
}
);
}
AddEmailFunc(){
if(!this.show){
this.show = true;
}
}
}
You must try adding [hidden]="AddEmailFunc()" on your form.
please see my sample code
import { Component } from '#angular/core';
#Component({
selector: 'Sandbox',
template: `<form [hidden]="isDisplayed">
<label>Sample: </label>
<input type="text">
</form>
<button (click)="showMe()">Click</button>`
})
export class SandboxComponent{
isDisplayed = true;
showMe()
{
if(this.isDisplayed)
{
this.isDisplayed = false;
}else{
this.isDisplayed = true;
}
}
}
Hope it helps. :) cheers!
You are mixing angularjs (1.x) and angular (>2)
the ng-click in Angular should be:
<button (click)="AddEmailFunc()">add user</button>
If this does not solve your issue please post the full component.
you can use *ngIf in and (model) binded to event click of button.
<button (click)="changeView()">add user</button>
and clickEvent
changeView(){
this.showForm = !this.showForm;
}

Angular 2 Inline template cannot read property of undefined

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

Categories