My Employee-Management-Component is reloading when it links to itself with route parameters at interaction and this should not happen. Am I having any issues in my code? The <p-dropdown> and <p-datatable> are components from primeNG and do what their name says. EventHandlers and properties should also be easy to understand. If not just ask.
import ...
#Component({
selector: 'employee-management-table',
template: `
<div class="ui-g-12 ui-g-nopad" id="CONTENT">
<nav class="ui-g-12 ui-g-nopad">
<p-dropdown [options]="departments" [(ngModel)]="selectedDeparment" (onChange)="selectDepartment($event)"></p-dropdown>
</nav>
<p-dataTable [value]="employees" [(selection)]="selectedEmployees" (onRowClick)="routeToEmployee($event)">
<p-column [style]="{'width':'38px'}" selectionMode="multiple"></p-column>
<p-column *ngFor="let col of columns" [field]="col.field" [header] = "col.header"></p-column>
</p-dataTable>
<employee-form [employee]="employee"></employee-form>
</div>
`,
styleUrls: [],
directives: [ROUTER_DIRECTIVES, EmployeeFormComponent, Dropdown, DataTable, Column],
})
export class EmployeeManagementTableComponent implements OnInit, OnDestroy{
private employees: Employee[];
private employee: Employee;
private newEmployee: boolean = false;
private selectedEmployees: Employee[];
private departments: SelectItem[] = [];
private selectedDepartment: string;
private columns: any[];
private paramSub: any;
private employeesSub: any;
private departmentSub: any;
constructor(private employeeManagementService: EmployeeManagementService,
private route: ActivatedRoute,
private router: Router,
private ccs: ComponentCommunicatorService,
private logger: Logger) { }
ngOnInit(){
this.columns = [
....
];
this.selectedDepartment = this.ccs.getSelectedDepartment();
this.getDepartments();
this.paramSub = this.route.params.subscribe(
//Success
params => {
if(params['type']){
let type = params['type'];
this.logger.log(type);
if(type === "employee"){
if(params['option']){
let option = params['option'];
this.logger.log(option);
this.doEmployeeOption(option);
}else if(params['id']){
let id = params['id'];
this.logger.log(id);
this.editEmployee(id);
}
}else if(type === "department"){
if(params['option']){
let option = params['option'];
this.logger.log(option);
this.doDepartmentOption(option);
}
}
}
},
//Error
err => this.logger.error(err),
//Complete
() => { }
);
}
ngOnDestroy(){
this.paramSub.unsubscribe();
this.employeesSub.unsubscribe();
this.departmentDub.unsubscribe();
}
doEmployeeOption(option: String){
switch(option){
case 'new':
this.newEmployee = true;
this.employee = new Employee();
break;
case 'delete':
break;
default:
this.logger.log("Default");
break;
}
}
save(){
if(this.newEmployee){
this.employees.push(this.employee);
this.employeeManagementService.insertEmployee(this.employee);
this.newEmployee = false;
}else{
this.employees[this.findSelectedEmployeeIndex()] = this.employee;
}
this.employee = null;
window.history.back();
}
abort(){
this.employee = null;
window.history.back();
}
routeToEmployee(event){
this.logger.log(event.data);
this.employee = event.data;
let link = ['/employee-management/employee', this.findSelectedEmployeeIndex()];
this.router.navigate(link);
}
editEmployee(id: number){
this.logger.log('edit '+id);
for (let employee of this.employees) {
this.logger.log("Edit: "+employee);
}
this.employee = this.employees[id];
this.logger.log('check');
findSelectedEmployeeIndex(): number {
this.logger.log("Method: "+this.employee);
this.logger.log("Method: "+this.employees.indexOf(this.employee));
return this.employees.indexOf(this.employee);
}
selectDepartment(event: any){
this.ccs.setSelectedDepartment(event.value);
this.getEmployees(this.ccs.getSelectedDepartment());
}
getDepartments(){
this.departments.push({label: 'Alle', value: 'all'});
this.departmentSub = this.employeeManagementService.getDepartments().subscribe(
//Sucess
data => {data.forEach((item, index) => {
this.departments.push({label: item, value: index.toString()});
});
},
//Error
err => this.logger.error(err),
//Complete
() => {this.logger.log('done loading');
this.departmentSub.unsubscribe();
this.getEmployees(this.selectedDepartment);}
);
}
getEmployees(department: any){
this.employeesSub = this.employeeManagementService.getEmployees(department).subscribe(
//Sucess
data => {this.employees = data},
//Error
err => this.logger.error(err),
//Complete
() => {this.logger.log('done loading');
/*for (let employee of this.employees) {
this.logger.log("Observable "+employee);
}*/
this.employeesSub.unsubscribe()}
);
}
ROUTES
export const EmployeeManagementRoutes: RouterConfig = [
{
path: 'employee-management',
component: EmployeeManagementComponent,
children: [
{
path: '',
component: EmployeeManagementTableComponent
},
{
path: ':type/:id',
component: EmployeeManagementTableComponent
},
{
path: ':type/:option',
component: EmployeeManagementTableComponent
},
]
}];
Everything gets loaded as wanted but if I click on an employee routeToEmployee routes me to ./employee/:employeeArrayIndex, the page reloads (what it should not) and it crashes in editEmployee where I want to assign the selected employee from the employees array to the employee variable that I can display him.
Error message is 'TypeError: Cannot read property '0' of undefined'. The number is for the array index where the employee should be. So I assume the array is empty after the re-init. Only getDepartments() gets called again but does not call getEmployees() anymore.
Related
NgOnChanges update table when changes is detected causes infinite service call , which I dont have any idea why. Maybe someone can help. Thanks.
I have parent component TableMultiSortComponent and I passed output EventEmitter which is the table data .
The event or the output from EventEmitter is passed to child component which I will use the pagesize , pageindex etc to query as you can see on the dataServiceEvent on the child component.
And then the output of the request is dealsListData which I pass back from child to parent as data to be populated to the table.
It should update table when there is changes that is why I put it on ngOnchanges which is the initTableMultiSort but right now it is updating the table infinitely , it should only update once if there is changes
#the table data that I am passing from parent component to child component , this.dataServiceEvent.emit(this.table); , this is what I emit cause I will be needing the pageindex, size etc.
#HTML CODE - Parent Component
<mat-card *ngIf="table !== undefined">
<mat-table mat-table [dataSource]="table.dataSource" matMultiSort (matSortChange)="table.onSortEvent()">
<ng-container *ngFor="let column of table.columns" [matColumnDef]="column.id">
<mat-header-cell class="table-multi-sort-header" *matHeaderCellDef [mat-multi-sort-header]="column.id">
<div>{{column.name}}</div>
<div class="sub-text">{{getColumnSubtitle(column.id)}}</div>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<ng-container *ngIf="column.id !== 'action'; then col; else actionCol"></ng-container>
<ng-template #col>
<app-table-multi-sort-cell-default [cellData]="row" [id]="column.id" [subId]="getColumnSubId(column.id)"></app-table-multi-sort-cell-default>
</ng-template>
<ng-template #actionCol>
<app-table-multi-sort-cell-action [rowData]="row" [actions]="getActions(column.id)" (actionClickEvent)="clickTableAction($event,row)"></app-table-multi-sort-cell-action>
</ng-template>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="table.displayedColumns; sticky:true"></mat-header-row>
<mat-row *matRowDef="let item; columns: table.displayedColumns;"></mat-row>
</mat-table>
<mat-progress-bar *ngIf="isLoading" mode="indeterminate"></mat-progress-bar>
</mat-card>
#ts code- parent component
export class TableMultiSortComponent implements OnInit {
#Input() tableOptions:any;
#Input() tableData:any = [];
#Input() isClientSide:boolean = false;
#Input() isLoading: boolean = false;
#Output() tableActionsEvent = new EventEmitter<any>();
#Output() dataServiceEvent = new EventEmitter<any>() ;
#ViewChild(MatMultiSort, { static: false }) sort: MatMultiSort;
tableConfig: any = TABLE_MULTI_SORT_OPTIONS.DEFAULT;
table:TableData<any>;
displayedColumns: any;
constructor() { }
ngOnInit(): void {
this.initTableMultiSort();
}
initTableMultiSort(){
this.tableConfig = {
...this.tableConfig,
...this.tableOptions
}
this.table = new TableData<any>(this.tableConfig.columns,this.tableConfig.sortParams);
this.table.pageSize = this.tableConfig.pageSize;
this.table.pageIndex = this.tableConfig.pageIndex;
this.table.nextObservable.subscribe(() => { this.getData(); });
this.table.sortObservable.subscribe(() => { this.getData(); });
this.table.previousObservable.subscribe(() => { this.getData(); });
this.table.sizeObservable.subscribe(() => { this.getData(); });
setTimeout(()=>{
this.table.dataSource = new MatMultiSortTableDataSource(this.sort, this.isClientSide);
this.getData();
},0);
}
ngOnChanges(changes: SimpleChanges) {
if (changes.tableData && changes.tableData.currentValue){
console.log("changes" , changes)
this.initTableMultiSort()
}
}
getData(){
this.table.totalElements = 1;
this.table.pageIndex = 0;
this.table.pageSize = 10;
this.table.data = this.tableData;
if(this.dataServiceEvent) {
this.dataServiceEvent.emit(this.table);
}
}
#child component HTML code
<app-table-multi-sort (dataServiceEvent)="dataServiceEvent($event)" [tableOptions]="tableOptions" [tableData]="dealsListData" (tableActionsEvent)="tableActions($event)"></app-table-multi-sort>
#child component ts code
export class DealsTransactionComponent implements OnInit {
#Input() transactionId: any = 2;
#Input() transactionName: string = '-';
#ViewChild(TableMultiSortComponent, { static: true }) tableMultiSortComponent: TableMultiSortComponent;
private currentTableConfig: TableData<any>;
dealsListData: any;
tableOptions: any;
#Input() transaction: any;
isLoading: boolean;
private _destroyed$ = new Subject();
totalDeals : number;
accountId: any;
searchInput: string;
table: any;
constructor(
private dialog: MatDialog,
private dealService: DealService,
private notificationService: NotificationService,
private route: Router,
) {}
ngOnInit(): void {
const currentAccountDetails = localStorage.getItem('currAcct') as any;
if (currentAccountDetails) {
this.accountId = JSON.parse(currentAccountDetails).accountId;
}
this.tableOptions = {
columns:[
{id:'name',name:'Deal Name',subId:'type', subtitle:'Deal Type'},
{id:'annualRentProposed',name:'Annual Rent (Proposed)', subId: 'annualRentCurrent', subtitle:'Annual Rent (Proposed)'},
{id:'firmTermRemain',name:'Firm Term Remaining', subId: 'firmTermAdded', subtitle:'(Current)'},
{id:'maxTerm',name:'Max Available Term'},
{id:'cash',name:'Cash Contribution'},
{id:'action', name: 'Actions', actions:[
{icon:'file_copy', name:'Copy', class:'primary-color'},
{icon:'delete', name: 'Delete', class:'mat-error'},
{icon:'forward', name: 'For Approval', class:'primary-color'}
]}
]
}
}
dataServiceEvent(item) {
this.table = item;
if(this.table) {
this._pageEventMyList();
}
}
private _pageEventMyList() {
if (!this.shouldLoadData(this.currentTableConfig, this.table)) {
return;
}
this.currentTableConfig = this.table;
this.searchInput = '';
this.isLoading = true;
this.dealService
.getAllDeals(
this.accountId,
this.transaction.id,
this.table.pageIndex + 1,
this.table.pageSize,
this.searchInput,
this.table.sortParams,
this.table.sortDirs
)
.pipe(finalize(() => (this.isLoading = false)))
.subscribe({
error: (err) => this.notificationService.showError(err),
next: (res) => {
this.dealsListData = res.totalItemCount;
this.dealsListData = res.lastItemOnPage;
this.totalDeals = res.items.length;
this.dealsListData = res.items;
console.log("this.dealsListData" , this.dealsListData)
},
complete: noop,
});
}
private shouldLoadData(oldTableConfig: TableData<any>, tableConfig: TableData<any>): boolean {
if (!oldTableConfig) {
return true;
}
return oldTableConfig.pageIndex !== tableConfig.pageIndex
|| oldTableConfig.pageSize !== tableConfig.pageSize
|| oldTableConfig.sortParams !== tableConfig.sortParams
|| JSON.stringify(oldTableConfig.sortParams) !== JSON.stringify(tableConfig.sortParams)
|| JSON.stringify(oldTableConfig.sortDirs) !== JSON.stringify(tableConfig.sortDirs);
}
createDeal() {
this.route.navigateByUrl(`deals/detail/${this.transactionId}~create`, {
state: { data: this.transaction },
});
localStorage.removeItem('breadCrumbsPath');
const breadCrumbsPath = [
{
text: 'My transactions',
link: '/transactions',
},
{
text: this.transactionName,
link: `/transactions/overview/${this.transactionId}`,
},
];
localStorage.setItem('breadCrumbsPath', JSON.stringify(breadCrumbsPath));
}
copyDeal(id: number) {
const data = { id : id }
this.duplicateDeal(data)
}
deleteDeal(id: number) {
this.isLoading = true;
this.dealService.delete(id)
.pipe(finalize(() => { this.isLoading = false;}))
.subscribe({
next: (res) => {
this.dealsListData = this.dealsListData.filter((x) => x.id !== id);
this.notificationService.showSuccess('Deal been removed successfully.');
},
error: (err) => {
this.notificationService.showError('Something went wrong, Try again later.');
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
},
});
}
duplicateDeal(data: any) {
this.isLoading = true;
this.dealService.duplicate(data)
.pipe( finalize(() => { this.isLoading = false;}))
.subscribe({
next: (res) => {
let copyData = {
...this.dealsListData.filter((x) => x.id === data.id),
};
copyData.name = copyData.name + '(copy)';
copyData.id = this.dealsListData[this.dealsListData.length - 1].id + 1;
this.notificationService.showSuccess('Deal was successfully duplicated.' );
},
error: (err) => {
this.notificationService.showError('Something went wrong, Try again later.');
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
},
});
}
approveDeal(data: any) {
this.isLoading = true;
this.dealService
.approve(data)
.pipe(finalize(() => { this.isLoading = false; }))
.subscribe({
next: (res) => {
this.notificationService.showSuccess('Deal was approved');
},
error: (err) => {
this.notificationService.showError('Something went wrong, Try again later.');
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
},
});
}
You are emitting this.table whenever you call getData in the TableMultiSortComponent which happens in ngOnInit for the first time.
This event emission (I suppose) triggers the actual data fetch in the parent component, in the tableActions method (could not see the implementation of this method in the code you included). This method is probably doing something similar to _pageEventMyList that gets data from the API and sets the dealsListData property on the TableMultiSortComponent ([tableData]="dealsListData").
This in turn, triggers your onChange in the TableMultiSortComponent and also passes your if check (the tableData is actually changed). From onChange, you call initTableMultiSort that reinitializes this.table and emits it with this.dataServiceEvent.emit(this.table);. This makes all go into a cycle.
I recommend to implement some extra checks to make sure you are not triggering data reload if the table configuration is the same.
In short, it looks like you use the table for providing you data about the pageIndex, pageSize, sortParams and sortDirs, so I suggest to keep information about the value of these properties when you try to load data again. Compare them, and if something is changed, then fetch data
private currentTableConfig: TableData<any>;
private _pageEventMyList() {
if (!shouldLoadData(currentTableConfig, this.table)) {
return;
}
this.currentTableConfig = this.table;
this.searchInput = '';
this.isLoading = true;
this.dealService
.getAllDeals(
this.accountId,
this.transaction.id,
this.table.pageIndex + 1,
this.table.pageSize,
this.searchInput,
this.table.sortParams,
this.table.sortDirs
)
.pipe(finalize(() => (this.isLoading = false)))
.subscribe({
error: (err) => this.notificationService.showError(err),
next: (res) => {
this.dealsListData = res.totalItemCount;
this.dealsListData = res.lastItemOnPage;
this.totalDeals = res.items.length;
this.dealsListData = res.items;
},
complete: noop,
});
}
and the new shouldLoadData method performs the checks:
private shouldLoadData(oldTableConfig: TableData<any>, tableConfig: TableData<any>): boolean {
if (!oldTableConfig) {
return true;
}
return oldTableConfig.pageIndex !== tableConfig.pageIndex
|| oldTableConfig.pageSize !== tableConfig.pageSize
|| JSON.stringify(oldTableConfig.sortParams) !== JSON.stringify(tableConfig.sortParams)
|| JSON.stringify(oldTableConfig.sortDirs) !== JSON.stringify(tableConfig.sortDirs);
}
Disclaimer for using JSON.stringify:
This works, but it is not optimal. If you have another third party library installed, like lodash, you can go for _.isEqual instead (or something similar, that will compare the array contents for you).
When i'm trying to test using karma jasmin, i'm getting this error...
TypeError: Cannot read property '_id' of undefined
Component.ts
import { Component, OnInit } from '#angular/core';
import { ApiService } from '../../../services/api.service';
import { Router } from '#angular/router';
import { NgxSpinnerService } from "ngx-spinner";
import { ToasterConfig } from 'angular2-toaster';
import { NbComponentStatus, NbGlobalPhysicalPosition, NbToastrService } from '#nebular/theme';
import { ToastrService } from 'ngx-toastr';
#Component({
selector: 'ngx-edit-subscription',
templateUrl: './edit-subscription.component.html',
styleUrls: ['./edit-subscription.component.scss']
})
export class EditSubscriptionComponent implements OnInit {
title = [
{"name": "Name"},
{"name": "Description"},
{"name": "Price"},
{"name": "Quantity"},
{"name": "Total"}];
review = false;
recipientDetails:any;
subtotal: number = 0;
tax: number = 100.00;
total: number = 0;
orders: any = [];
menuitems: any = [];
selectedItems: any = [];
recipientId:any;
recipientAddressId:any;
cartId:any;
searchItem:string='';
searchitemNumber="";
searchDescription:string='';
searchPrice:string='';
imageShow: boolean = false;
imageUrl: any = '';
item: any = '';
itemNumber= '';
desc: any = '';
price: any = '';
vendorID: any;
response: any;
subscriptionItem : any;
subscriptionItemsId : any;
subscriptionItems_Id : any;
cartDetails:any;
constructor(private apiService: ApiService,
private router: Router,
private toastrService: ToastrService,
private spinner: NgxSpinnerService) {
this.recipientDetails = JSON.parse(localStorage.getItem('recipientDetails')) || {};
localStorage.setItem('subId', this.recipientDetails._id);
this.recipientId = this.recipientDetails.recipientId._id ? this.recipientDetails.recipientId._id : '';
this.recipientAddressId = this.recipientDetails._id ? this.recipientDetails._id : '';
this.vendorID = localStorage.getItem('vendorID');
let zipCode = this.recipientDetails.zipCode;
this.spinner.show();
this.venueId = this.recipientDetails.deliveryAreaId[0].venueId;
this.apiService.getMenubyCategory(this.vendorID).subscribe((res)=>{
this.spinner.hide();
this.orders = res.body.vendorTags;
for(let order of this.orders) {
for(let prod of order.menudetails) {
prod['quantity'] = 0;
}
}
this.spinner.show();
this.apiService.userSubscription(this.recipientDetails._id).subscribe((resp)=>{
this.spinner.hide();
this.response = resp;
this.subscriptionItem = this.response.subscriptionItem[0];
this.subscriptionItemsId = this.subscriptionItem.subscriptionItemsId[0];
this.subscriptionItems_Id = this.subscriptionItemsId._id;
for(let menuItems of this.subscriptionItemsId.menuItems) {
for(let order of this.orders) {
for(let prod of order.menudetails) {
if(prod._id === menuItems.menuItemId._id) {
prod['quantity'] = menuItems.quantity;
}
}
}
}
});
});
}
venueId:any;
ngOnInit() {
this.apiService.editSubscription = true;
}
imagePopupShow(url: any) {
var scrollElem= document.querySelector('#moveTop-row');
scrollElem.scrollIntoView();
this.imageShow = true;
this.imageUrl = url;
}
imagePopupHide() {
this.imageShow = false;
this.imageUrl = '';
}
search(query, tab) {
if(tab === 'item') {
this.item = query;
}
if(tab === 'itemNumber') {
this.itemNumber = query;
}
if(tab === 'description') {
this.desc = query;
}
if(tab === 'price') {
this.price = query;
}
var data2 = {
"item" : this.item,
"itemNumber" : this.itemNumber,
"description" : this.desc,
"price":this.price,
"vendorId":this.vendorID
};
this.spinner.show();
this.apiService.searchOrder(data2).subscribe((res)=>{
this.spinner.hide();
this.orders = res.body.vendorTags;
for(let order of this.orders) {
for(let prod of order.menudetails) {
prod['quantity'] = 0;
}
}
});
}
reviewOrder() {
this.menuitems = [];
this.spinner.show();
for(let order of this.orders) {
for(let product of order.menudetails) {
if(product.quantity > 0){
this.menuitems.push({
deliverRestOfOrder: true,
menuItemId: {
_id: product._id,
name: product.name,
price: product.price
},
quantity: product.quantity,
});
}
}
}
if(this.menuitems.length == 0){
this.toastrService.error('', 'Please Select Atleast One menu');
this.spinner.hide();
return;
}
var data2 = {
"subscriptionItemId": this.subscriptionItems_Id,
"menuItems": this.menuitems
};
this.apiService.reviewSubscription(data2).subscribe((res)=>{
this.spinner.hide();
this.toastrService.success('', 'Succesfully Added');
this.cartDetails = res;
localStorage.setItem('totalAmount',this.cartDetails.subscriptionItem[0].totalAmount);
localStorage.setItem('typeOfSubscription', this.cartDetails.subscriptionItem[0].subscriptionId[0].typeOfSubscription);
this.review = true;
});
}
addToCart(item) {
if(item.quantity<0){
item.quantity = 0;
}
return;
if(this.menuitems.length>0){
var arr = this.menuitems;
function userExists(menuItemId) {
return arr.some(function(el) {
return el.menuItemId === menuItemId;
});
}
var exists = userExists(item._id);
if(exists == true){
for(var i in this.menuitems){
if(this.menuitems[i].menuItemId == item._id){
this.menuitems[i].quantity = this.menuitems[i].quantity+1;
break;
}
}
} else {
this.menuitems.push({menuItemId: item._id, quantity: 1, name: item.name, price: item.price});
}
var data = {
"cartId" : this.cartId,
"vendorId" : this.vendorID,
"venueId" : this.venueId,
"recipientId":this.recipientId,
"recipientAddressId":this.recipientAddressId,
"menuItems": this.menuitems
};
this.apiService.updateCart(data).subscribe((res)=>{
});
} else {
this.menuitems.push({menuItemId: item._id, quantity: 1, name: item.name, price: item.price});
var data2 = {
"vendorId" : this.vendorID,
"venueId" : this.venueId,
"recipientId":this.recipientId,
"recipientAddressId":this.recipientAddressId,
"menuItems": this.menuitems
};
this.apiService.addToCart(data2).subscribe((res)=>{
this.cartId = res.cartId;
});
}
}
}
Spec.ts
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { EditSubscriptionComponent } from './edit-subscription.component';
import { NbCardModule } from '#nebular/theme';
import { FormsModule } from '#angular/forms';
import { DummyService } from '../../../services/dummy.service';
import { RouterTestingModule } from '#angular/router/testing';
import { ToastrService } from 'ngx-toastr';
import { HttpClientTestingModule } from '#angular/common/http/testing';
class MockDummyService extends DummyService {
// mock everything used by the component
};
describe('EditSubscriptionComponent', () => {
let component: EditSubscriptionComponent;
let fixture: ComponentFixture<EditSubscriptionComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports:
[
RouterTestingModule,
NbCardModule,
FormsModule,
HttpClientTestingModule
],
declarations: [ EditSubscriptionComponent ],
providers:
[
{
provide: ToastrService,
useClass: MockDummyService
},
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditSubscriptionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Thanks.......................................................................................................................................................................................................................
You are checking if recipientDetails exists in localStorage:
this.recipientDetails = JSON.parse(localStorage.getItem('recipientDetails')) || {};
and here if _id of given recipientDetails exists:
this.recipientId = this.recipientDetails.recipientId._id ? this.recipientDetails.recipientId._id : '';
but than you are subscribing on userDetails instead of this.recipientId anyway:
this.apiService.userSubscription(this.recipientDetails._id).subscribe((resp)=>{
If this.recipientDetails will be {} as pointed in first line, using this.recipientDetails._id will throw an error "Cannot read property '_id' of undefined"
Try sending this.recipientId to the subscription ,and handle possibility of lacking _id in your backend
I really need help, because I don't know why this is happening.
I have an Angular webapp with a Firebase backend. My webapp shows 7 random products from a products collection in Firebase. This 7 random products should be stored so that, the users can see it on different devices.
But if i call my savemethod, something weird is happening. The save method, calls a the method, who generates the 7 random products and i don't know why, because i don't call it expecially.
Heres my code:
export class ProductComponent implements OnInit {
schedulenumbers: ISchedule = { numbers: [] }; //Just an Object with an array of numbers in it
length: number = 0; //lenght is needed to show or hite the list in the template
filteredproducts: IProduct[] = [];
numbers: number[] = []; // the numbers to filter the products
constructor(private productservice: ProductService) {
console.log("constructor wurde aufgerufen");
}
ngOnInit(): void {
this.filteredproducts = [];
console.log("ngOnInit was called")
//get the numbers from the database
this.productservice.getnumbers().subscribe(
numbers => {
this.numbers = numbers.numbers;
}
);
//get all products from the database collection
this.productservice.getallproducts().pipe(
map((products) => {
this.length = products.length;
for (let i = 0; i < 7; i++) {
this.filteredproducts.push(product[this.numbers[i]]);
}
return this.filteredproducts;
})
).subscribe();
}
getRandomproducts(): void {
this.filteredproducts = [];
this.numbers = [];
console.log("getRandomproducts was called");
this.productservice.getallproducts().pipe(
map(products => {
for (let i = 0; i < 7; i++) {
this.numbers[i] = Math.floor(Math.random() * products.length + 1)
if (products[this.numbers[i]] == undefined) {
i -= 1;
}
else {
this.filteredproducts.push(products[this.numbers[i]]);
}
}
console.log(this.numbers);
return this.filteredproducts;
})
).subscribe();
}
saveNumbers(): void {
console.log("savenumbers was called")
console.log(this.numbers);
this.schedulenumbers.numbers = this.numbers;
console.log(this.filteredproducts.length);
this.productservice.updatenumbers(this.schedulenumbers);
}
}
Here is the code for the productservice:
#Injectable({
providedIn: 'root'
})
export class ProductService {
Productcollection: AngularFirestoreCollection<IProduct>;
schedulenumbers: AngularFirestoreDocument<ISchedule>;
numbers: Observable<ISchedule>
Products$: Observable<IProduct[]>;
constructor(private firestore: AngularFirestore, private auth: AngularFireAuth) {
this.auth.currentUser.then(user => {
this.productcollection = this.firestore.collection(`${user.uid}`);
this.schedulenumbers = this.firestore.collection(`${user.uid}`).doc("numbers")
})
}
getallproducts() {
return this.Products$ = this.Productcollection.valueChanges({ idField: 'ProductId' }).pipe(
map(products =>
products.filter(product => product.ProductId != 'numbers')
)
);
}
getnumbers() {
return this.numbers = this.schedulenumbers.valueChanges();
}
updatenumbers(numbers: ISchedule) {
this.schedulenumbers.update(numbers)
}
addrecipe(product: IProduct) {
this.Productcollection.add(product);
}
}
I have attached a picture from the Firefox console that shows that if I press the button for the getRandomproducts method twice and then I press the button for the saveNumbers method, strangely white from the saveNumbers method, the for loop of getrandomproduct number method is called and in the filteredproducts array are not longer only 7, but much more products. But why?
Firefox console
I am trying to build a dynamic component based on a Config. The component would read the config recursively and create the component. It is found that the method ngAfterViewInit() would only be called twice.
#Component({
selector: "dynamic-container-component",
template: `
<div #container
draggable="true"
(dragstart)="dragstart($event)"
(drop)="drop($event)"
(dragover)="dragover($event)"
style="border: 1px solid; min-height: 30px"></div>
`
})
export default class DynamicContainerComponent {
#Input()
dynamicConfig: DynamicConfig;
#ViewChild("container", {read: ElementRef})
private elementRef: ElementRef;
private isContainer: boolean;
private componentRef: ComponentRef<any>;
private componentRefs: ComponentRef<any>[] = [];
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
private viewContainer: ViewContainerRef,
private render: Renderer2
){
console.log("running");
}
ngAfterViewInit(){
if (this.dynamicConfig){
console.log(this.dynamicConfig)
if (this.dynamicConfig.getType() == ComponentType.INPUT){
this.isContainer = false;
let componetFactory: ComponentFactory<InputComponent> =
this.componentFactoryResolver.resolveComponentFactory(InputComponent);
this.componentRef = this.viewContainer.createComponent(componetFactory);
this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement);
}else {
this.isContainer = true;
let items: DynamicConfig[] = this.dynamicConfig.getItems();
if (items){
for (var i=0; i<items.length; i++){
let item: DynamicConfig = items[i];
let componetFactory: ComponentFactory<DynamicContainerComponent> =
this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent);
let componentRef: ComponentRef<DynamicContainerComponent> =
this.viewContainer.createComponent(componetFactory);
componentRef.instance.dynamicConfig = item;
this.componentRefs.push(componentRef);
this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement);
}
}
}
}else {
console.log("config does not exist");
}
}
dragstart(event){
debugger;
}
drop(event){
debugger;
}
dragover(event){
debugger;
event.preventDefault();
}
}
The Component would be created by other component by the following code. If The Dynamic Component would create another Dynamic Component by componentFactoryResolver.
var configJson = {
type: ComponentType.CONTAINER,
items: [
{
type: ComponentType.CONTAINER,
items: [{
type: ComponentType.CONTAINER,
items: [{
type: ComponentType.CONTAINER,
items: [{
type: ComponentType.INPUT
}]
}]
}]
}
]
}
this.config = new DynamicConfig();
this.config.assign(configJson);
console.log(this.config);
Update
I found a similar issue in github: https://github.com/angular/angular/issues/10762
I have done something suggested by other people. but I think it is just a dirty fix.
ngAfterViewInit(){
setTimeout(function(){
if (this.dynamicConfig){
console.log(this.dynamicConfig)
if (this.dynamicConfig.getType() == ComponentType.INPUT){
this.isContainer = false;
let componetFactory: ComponentFactory<InputComponent> =
this.componentFactoryResolver.resolveComponentFactory(InputComponent);
this.componentRef = this.viewContainer.createComponent(componetFactory);
this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement);
}else {
this.isContainer = true;
let items: DynamicConfig[] = this.dynamicConfig.getItems();
if (items){
for (var i=0; i<items.length; i++){
let item: DynamicConfig = items[i];
let componetFactory: ComponentFactory<DynamicContainerComponent> =
this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent);
let componentRef: ComponentRef<DynamicContainerComponent> =
this.viewContainer.createComponent(componetFactory);
componentRef.instance.dynamicConfig = item;
this.componentRefs.push(componentRef);
this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement);
}
}
}
}else {
console.log("config does not exist");
}
}.bind(this))
}
By the time you create your dynamic component angular has almost finished change detection cycle.
This way you can either run:
componentRef.changeDetectorRef.detectChanges()
Note: setTimeout has similar effect but fires change detection cycle on the whole app
or rename lifecycle hook to ngOnInit
Also you're passing wrong input to dynamic component:
let item: DynamicConfig = items[i];
^^^^^^^^^^^^^
but it is not DynamicConfig instance but rather plain object
...
componentRef.instance.dynamicConfig = item;
it should be:
let item: any = items[i];
const config = new DynamicConfig();
config.assign(item);
componentRef.instance.dynamicConfig = config;
Ng-run Example
Actually I need to implement FB like in my cordova ios app. So I have included Cordova FB connect plugin in my cordova app. I have used the following code.
<div class="fb-like" data-href="https://www.facebook.com/testentertainer" data-layout="button" data-action="like" data-show-faces="false" data-share="false"></div>
It is creating a like button and its nice. When I click on like button first time, It is popping up fb login page. After entering the credentials request is not coming back to my page. Its displaying white empty page and app is hanging there it self. If i reopen my app then i can like because I am already logged in. Now like and unlike functionalities are working but i am not able to get callback of those events.
I have tried following
FB.Event.subscribe('edge.create', function(href, widget) {
console.log("in edge create");
alert('You just liked the page!');
});
FB.Event.subscribe('edge.remove', function(href, widget) {
alert('You just unliked the page!');
});
But Its not working.
FIDDLE
Is the Facebook login page is a popup or it opens the facebook app? if it is a popup you have to install InAppBrowser plugin first.
the same was happening with me and it is very annoying,seems like the fb snippet doesn't play well with the inappbroswer,however now without wasting any more time i am leveraging graph api, and since i am making an angular (and typescript also) app i had made a directive for Facebook like and handling events from there
<facebook-like url="{{vm.currentUrl}}"></facebook-like>
directive template
<div ng-switch="vm.likeStatus">
<div ng-switch-when="like" ng-click="vm.setLike(true)">
<span class="fbLike"><img src="content/images/like.png" class="img-responsive"/></span>
</div>
<div ng-switch-when="unlike" >
<span ng-click="vm.setLike(false)">
<span class="fbLiked"><img src="content/images/liked.png" class="img-responsive" /></span>
</span>
</div>
<div ng-switch-when="loading"><img src="content/images/loader.gif" class="img-responsive" /></div>
<div ng-switch-when="login" ng-click="vm.login()">login</div>
<div ng-switch-default></div>
</div>
directive definition
namespace portal.directives {
export class facebookLike {
constructor() {
let directive: ng.IDirective = <ng.IDirective>{};
directive.restrict = 'E';
directive.scope = {
url:"#"
};
directive.templateUrl = "views/directives/facebookLike.html";
directive.controller = 'facebookLikeController';
return directive;
}
}
}
directive controller
namespace portal.controllers {
export class facebookLikeController {
public likeStatus: string;
public postId: string;
public setLike = (value: boolean) => {
this.likeStatus = "loading";
if (value) {
this.facebookService.setLike(this.$scope.url).then((postId) => {
this.likeStatus = 'unlike';
this.postId = postId;
});
}
else {
this.facebookService.setUnlike(this.postId).then(() => {
this.likeStatus = 'like';
this.postId = void 0;
});
}
};
public login = () => {
this.userService.socialLogin(socialProviders.facebook).then(() => {
this.$state.forceReload();
});
};
static $inject = ['$element', '$scope', 'facebookService', 'userService', '$state','localStorageHandler'];
constructor(public $element: JQuery, public $scope, public facebookService: services.facebookService, public userService: services.userService, public $state, localStorageHandler) {
facebookService.isObjectLiked($scope.url).then((res) => {
this.likeStatus = res ? 'unlike' : 'like'; // if object is like then show unlike button and vice versa
this.postId = res;
}, () => {
//user is not logged in via facebook,may be via other carrier
let tokenDetails: IUser = localStorageHandler.get(storageNames.socialLoginDetails);
if (!tokenDetails)
this.likeStatus = 'login';
});
$scope.vm = this;
}
}
}
facebook service
export class facebookService {
public fireGraphApi = (endPoint, verb): ng.IPromise<any>=> {
let config: IRequestConfig = {
method: verb,
url: "https://graph.facebook.com/v2.4/" + endPoint,
showLoader: false
}
return this.$http(config).then((res: any) => {
return res.data;
});
};
get isUserLoggedIn(): ng.IPromise<any> {
let tokenDetails: IUser = this.localStorageHandler.get(storageNames.socialLoginDetails);
if (tokenDetails && tokenDetails.social_provider == socialProviders.facebook) {
let url = "me?access_token=" + tokenDetails.access_token;
return this.fireGraphApi(url, 'GET');
}
else
return this.$q.reject();
}
public isObjectLiked = (objectUrl: string): ng.IPromise<any> => {
let def = this.$q.defer();
let tokenDetails: IUser = this.localStorageHandler.get(storageNames.socialLoginDetails);
this.isUserLoggedIn.then(() => {
//user is logged in
let url = "me/og.likes?access_token=" + tokenDetails.access_token + '&object=' + objectUrl;
this.fireGraphApi(url, 'GET').then((res) => {
if (res && res.data.length == 0) {
// not yet liked
def.resolve(void 0);
}
else {
//liked and show unlike button
def.resolve(res.data[0].id);
}
});
}, () => {
//rejected when user not logged in
def.reject();
});
return def.promise;
};
public setLike = (objectUrl: string): ng.IPromise<string> => {
return this.isUserLoggedIn.then(() => {
let url: string;
let tokenDetails: IUser = this.localStorageHandler.get(storageNames.socialLoginDetails);
url = "me/og.likes?access_token=" + tokenDetails.access_token + '&object=' + objectUrl;
return this.fireGraphApi(url, 'POST').then((res) => {
return res.id;
});
});
};
public setUnlike = (postId: string): ng.IPromise<any> => {
return this.isUserLoggedIn.then(() => {
let url: string;
let tokenDetails: IUser = this.localStorageHandler.get(storageNames.socialLoginDetails);
url = postId + "?access_token=" + tokenDetails.access_token;
return this.apiService.fireGraphApi(url, "DELETE").then((res) => {
return res;
});
});
}
static $inject = ['$http', '$q', 'localStorageHandler', 'apiService'];
constructor(public $http, public $q, public localStorageHandler: services.localStorageHandler, public apiService: services.apiService) {
}
}