Object inside function not getting executed when calling function from another component - html

Apologies for not being able to title my question properly.
Let me explain my issue properly.
I have 2 Components say A and B.
In B I have a function saveIndCustData which emits and saves data.
export class CustomerformComponent implements OnInit {
#Output()
savedIndCustomer: EventEmitter<any> = new EventEmitter<any>();
saveIndCustData() {
const savedIndCustomer = {
prefix: this.prefix,
nameType: this.indCustNameType,
firstName: this.firstName,
middleNAme: this.middleName,
lastName: this.lastName,
gender: this.gender,
dateOfBirth: this.parseDate(this.dateOfBirth.toString()),
citizenship: this.citizenship
};
this.savedIndCustomer.emit(savedIndCustomer);
this.snackbar.open('Customer Info Saved,Click on Next', 'Close', {
duration: 5000
});
}
}
I am now calling the function from component A.
import { CustomerformComponent } from './forms/customerform/customerform.component';
constructor(private custComp: CustomerformComponent) {}
saveCustomerForm(): void {
this.custComp.saveIndCustData();
}
I emit the data into a service class
#Output()
savedIndCustomer: EventEmitter<any> = new EventEmitter<any>();
Service Class
public addDynamiIndCustomerComponent() {
const factory = this.factoryResolver.resolveComponentFactory(CustomerformComponent);
const component = factory.create(this.rootViewContainer.parentInjector);
component.instance.savedIndCustomer.subscribe(data => {
console.log(data);
// Insert Individual Customer Type
this.custFullDetails.customerType = 'individual';
this.custFullDetails.individualCustomer.dateOfBirth = data.dateOfBirth;
this.custFullDetails.individualCustomer.citizenship = data.citizenship;
this.custFullDetails.individualCustomer.gender = data.gender;
this.custFullDetails.individualCustomer.individualName.push({
prefix: data.prefix,
firstName: data.firstName,
middleName: data.middleName,
lastName: data.lastName,
agreementId: data.agreementId,
nameType: data.nameType
});
console.log(this.custFullDetails.individualCustomer);
});
this.rootViewContainer.insert(component.hostView);
}
My issue is if I invoke the saveIndCustData function from component B it pushes data into array const savedIndCustomer{ ... } and calls the service class.
However when I invoke the same function from component A it doesn't invoke the const savedIndCustomer{ ... } method inside saveIndCustData() function and service class method does not save data in array but it simply shows the snakbar.
What is the issue?

Suppose you put the component B inside the html of component A, so you should make a reference for the component B like this
A.component.html:
...
<B #bcmp></B>
...
and inject it in A.component.ts using #ViewChild like this
A.component.ts:
#Component({
selector: 'A',
templateUrl: './A.component.html',
styleUrls: ['./A.component.scss']
})
export class AComponent implements OnInit {
#ViewChild("bcmp") bcmp : B
ngOnInit(): void {
// by this way you can use any method existant in B component
this.bcmp.saveIndCustData();
}
}

Related

Angular - Dynamically load html that includes angular markups

In Angular 9+ I can successfully convert a string to a html and then load that that html using innerHtml and bypassSecurityTrustHtml().
My question is it possible to also dynamically load/render the converted html to include and recognise angular/javascript markup language eg *ngIf, handle bars and click events.
Below is the code and stackblitz at the attempt so far but as you can see it doesn't recognise the markup.
https://stackblitz.com/edit/dynamic-angular?file=app/app.component.ts
export class AppComponent implements OnInit {
text: string = "Hello world";
content: any;
constructor(private domSantizer: DomSanitizer) {}
ngOnInit() {
let body: any =
'<div>{{text}}<div><br><button (click)="test()">Test</button>';
this.content = this.domSantizer.bypassSecurityTrustHtml(body);
}
test() {
alert("It works");
}
}
Html
<div [innerHTML]="content"></div>
I have researched and tried many solutions.
My research and trial results are below.
html
<div #container></div>
typescript side as below
export class AppComponent implements OnInit {
#ViewChild("container", { read: ViewContainerRef })
container: ViewContainerRef;
constructor(private compiler: Compiler) {}
text: string = "asdasd";
ngOnInit() {
this.addComponent(
`<div>{{text}}<div><br><button (click)="test()">Test</button>
`,
{
text: "Hello word",
test: function() {
alert("It's work");
}
}
);
}
private addComponent(template: string, properties?: any = {}) {
#Component({ template })
class TemplateComponent {}
#NgModule({ declarations: [TemplateComponent] })
class TemplateModule {}
const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = mod.componentFactories.find(
comp => comp.componentType === TemplateComponent
);
const component = this.container.createComponent(factory);
Object.assign(component.instance, properties);
// If properties are changed at a later stage, the change detection
// may need to be triggered manually:
// component.changeDetectorRef.detectChanges();
}
demo
some posts I have reviewed
compile dynamic Component
angular-html-binding
I think it makes the most sense :)

Can't render data from api call from one component to another in Angular 8

I am new to Angular and I am facing issue in rendering data in UI from an api call. I want to show the data received as response in the parent and show it in a component called webex-uptime-chart.
The file with API call is as shown below:
public uptimeChartConfig: Array<{ [key: string]: string | any }>;
this.uptimeChartConfig = [
{
rpcMethod: 'getNodeStatus',
node: this.NodeId,
duration: '10 mins'
},
];
// API call to get the Uptime Chart data
this.uptimeChartConfig
.filter(config => config.rpcMethod)
.map(config => {
return this.rpcService
.invoke({
method: 'getNodeStatus',
args: ['2d945891-be9b-46a8-973e-3f343a8999ad'],
})
.then((data: any) => {
if (data && data.response) {
const labels: Array<string> = data.response.map(value =>
this.datePipe.transform(value.epochtime * 1000, 'shortTime')
);
const nodeList = {};
data.response.forEach(node => {
if (nodeList[node.nodeId]) {
nodeList[node.nodeId] = [...nodeList[node.nodeId], node.uptime];
} else {
nodeList[node.nodeId] = [node.uptime];
}
});
this.lineChartData[config.rpcMethod] = {
labels: labels,
dataSets: nodeList,
};
} else {
this.lineChartData[config.rpcMethod] = {
lables: [],
dataSets: [],
};
}
});
The response looks as shown below:
The parent component's html where the webex-uptime-chart is called looks as shown below:
<webex-uptime-chart
*ngFor="let config of uptimeChartConfig"
[config]="config"
[incomingData]="lineChartData[config.rpcMethod]">
</webex-uptime-chart>
The webex-uptime-chart.ts component file is:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'webex-uptime-chart',
templateUrl: './uptime-chart.component.html',
styleUrls: ['./uptime-chart.component.scss']
})
export class UptimeChartComponent implements OnInit {
#Input() chartData: any[];
#Input() public config;
#Input() public incomingData: any;
public labels: Array<string> = [];
public dataSets: any = {};
constructor() { }
ngOnInit() {
this.labels = this.incomingData.labels;
this.dataSets = this.incomingData.dataSets;
}
}
The webex-uptime-chart.html file is:
<div class="uptime-container">
<ul *ngFor="let data of chartData">
<li [ngClass]="data.status === 'down' ? 'my-class red-circle' : 'my-class green-circle '">
<span>{{ config.node }}</span>
</li>
<p class="right-text">{{ config.duration }}</p>
<hr />
</ul>
</div>
I get the below error while trying to run :
I don't know how to proceed.
incomingData is asynchronous. As a result it is initially provided as undefined to the child component until the promise then callback was executed. But this change is not registered within child component, since you only read incomingData within ngOnInit.
You could use ngOnChanges instead of ngOnInit.
ngOnChanges(changes: SimpleChanges) {
if (changes['incomingData'] && !!changes['incomingData'].previousValue) {
this.labels = changes['incomingData'].currentValue.labels;
this.dataSets = changes['incomingData'].currentValue.dataSets;
}
}

Using service.ts variables on multiple components

I've set up next.service.ts with 3 variables (user, action, rest) and made setters(updateNext()) and getters (getUser, getAction, getRest). I've got to use the setter to change the variables in one component (stock-management component) and retrieved these variables in another component (inventory-record component) but I can't seem to retrieve them from another component (inventory-record-filled component).
I've tried returning a string ("TEST") in the getter and it worked, but when I tried returning a variable, it just returned nothing/empty string.
export class NextService {
private action: string;
private user: string;
private restraunt: string;
constructor() { }
updateNext(actions, users, restraunts) {
this.action = actions;
this.user = users;
this.restraunt = restraunts;
}
getAction(): string {
return this.action;
}
getUser(): string {
return this.user;
}
getRest(): string {
return this.restraunt;
}
export class InventoryRecordComponent implements OnInit {
name = '';
rest = '';
action = '';
constructor(private next: NextService) {
this.name = this.next.getUser();
this.action = this.next.getAction();
this.rest = this.next.getRest();
}
ngOnInit() {
document.getElementById('dne').style.display = 'none';
}
onSubmit(f: NgForm) {
const x = document.getElementById('dne');
if (!this.next.updateCode(this.code)) {
x.style.display = 'block';
f.resetForm();
} else {
this.next.updateCode(this.code);
location.replace('inventory-record/qty');
}
}
}
export class InventoryRecordFilledComponent implements OnInit {
name: string;
action: string;
rest: string;
constructor(private next: NextService) {
this.name = this.next.getUser();
this.action = this.next.getAction();
this.rest = this.next.getRest();
}
ngOnInit() {
}
}
Each component have its respective html files with {{ name }} {{ action }} {{ rest }}
If you need your component to behave as a Simpleton (where it contains the same values regardless of where in the application it is used) you must set its providedIn value to "root", like so:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class NextService {
// The rest of the code stays the same
}
The description for that can be found here: https://angular.io/guide/singleton-services#providing-a-singleton-service
If you don't do that, each component that imports NextService will have it's own instance of NextService, with its own isolated values. If you want the values of a service to be available everywhere that the service is used in, then you want the service to be a Simpleton, so you must follow the steps.
Following the steps above is not the only way to make your component a Simpleton, but as the link mentions, it is the preferred way to do that.
Hope that helps!

Property 'filter' does not exist on type 'Object'. When trying to filter response

Im trying to get data from a json file that equal the name of the player in the url. For example: localhost:4200/players/Febiven should only return the information about Febiven. Im using Angular 6
So far I have this code:
player.service.ts
get(ingameName){
return <Observable<Player>> this.http.get(endpoint).map(response =>{
let data = response.filter(item=>{
if (item.ingameName == ingameName) {
return item
}
});
if (data.length == 1){
return data[0]
}
return {}
})
.catch(this.handleError)
}
private handleError(error:any, caught:any): any{
console.log(error, caught)
}
player-info.component.ts
export interface Player {
ingameName: string;
name: string;
intro: string;
image: string;
info: string;
team: string;
dob: string;
earnings: string;
role: string;
game: string;
favourite: string;
IDs: string;
}
export class PlayerInfoComponent implements OnInit {
players: Player[] = null;
private routeSub:any;
private req:any;
ingameName:string;
player : player;
constructor(private route: ActivatedRoute, private plService : PlayerService) { }
ngOnInit() {
this.routeSub = this.route.params.subscribe(params => {
this.ingameName = params['ingameName'];
this.req = this.plService.get(this.ingameName).subscribe(data=>{
this.player = data as player
})
});
Im getting the error 'Property 'filter' does not exist on type 'Object'. And I don't really have an idea how to fix this, I looked at multiple answers, but none seemed to work for me. If someone could help me with fixing this error thatd be great
Thanks
filter only exists on arrays. Your response is an object. You can do this instead:
get(ingameName){
return <Observable<Player>> this.http.get(endpoint).map(response =>{
let data = response.json();
if (data.ingameName == ingameName){
return data;
}
return {};
})
.catch(this.handleError)
}
Try this it will work:
define a parameter inside your class & use it in ngOnInit() function like this:
export class VideoDetailComponent implements OnInit, OnDestroy {
data_new:any;
ngOnInit() {
this.http.get("assets/json/videos.json").subscribe(data =>{
this.data_new = data;
this.data_new.filter(item=>{
console.log(item)
// do your work here
})
})
}
}

Angular2 functions in template and change detection

Im trying to build a method inside a service that checks whether a navigation button should be showed to the current user based on his permissions or not (this is just cosmetic "security" I know). Therefore this is the button placed inside the template
<button [routerLink]="['/some/where']"
*ngIf="AuthService.isAuthorized(['some', 'where'])">
Personen
</button>
The method AuthService.isAuthorized uses the provided array to run through all available routes and get the required permissions from the particular route's data object:
{
path: 'some',
component: SomeComponent,
data: {
permissions: [
"read:some",
"edit:some"
]
},
children: [
{
path: 'where',
component: SomeComponent,
data: {
permissions: [
"read:where"
]
}
},
]
}
so in this case the permissions ["read:some","edit:some","read:where"] are needed by the current signed in user so that the button would be displayed to him. Working so far!
But since the function is called inside the template it is called multiple times because of angular change detection. How could I change my code so that the function is called only once? Even better if it would only be called once after the authentication finished writing all permissions assigned to the authenticated user into AuthService.permissions
You can make AuthService.isAuthorized() method returns a promise:
#injectable()
export class AuthService {
...
isAuthorized(arr: string[]): Promise<boolean> {
return new Promise(resolve =>{
// your logic here
resolve(yourResult);
});
}
...
}
You can call this method on your ngOnInit of a component (Therefore it will be called once). You pass the return value to a new variable (e.g. isAuthorized) in the component and use this variable in the template instead.
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent implements OnInit {
isAuthorized: boolean;
constructor(private authService: AuthService) {}
ngOnInit() {
this.authService.isAuthorized(['some', 'where']).then(result => {
this.isAuthorized = result;
});
}
}
In the template you can just use isAuthorized variable.
<button [routerLink]="['/some/where']"
*ngIf="isAuthorized">
Personen
</button>
Edit:
If AuthService.isAuthorized() needed to be called only once but for more than one element, code like these may suits your need:
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent {
isObjectAuthorized = {} as {
isFirstAuthorized: boolean;
isSecondAuthorized: boolean;
};
constructor(private authService: AuthService) {}
checkForAuthorization(isElementAuthorized, arr: string[]) {
if (isElementAuthorized !== undefined) {
return;
}
this.authService.isAuthorized(arr).then(result => {
isElementAuthorized = result;
});
}
}
And in your template:
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isFirstAuthorized, ['some', 'where'])">
First
</button>
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isSecondAuthorized, ['some', 'where', 'else'])">
Second
</button>