Reputation: 127
I'm currently learning how to use Angular (version 5+) correctly by making a small project. I've set up some basic routing to load the ContactList component by selecting a navigation tab. The other tab allows one to add a contact using a form which then sends the form data to the Contact Data Service where the contacts are managed in an array. The ContactList Component is as follows:
import { Component, OnInit, Input, Output,
OnChanges, DoCheck, SimpleChanges} from '@angular/core';
import { ContactDataService } from '../ContactData.service';
// import { FilterPipe } from '../../../src/pipes';
import { Contact } from '../contact.model';
@Component({
selector: 'app-contact-list-component',
templateUrl: './contact-list-component.component.html',
styleUrls: ['./contact-list-component.component.css']
})
export class ContactListComponent implements OnInit, OnChanges, DoCheck {
contactList: Contact[] = [];
searchQuery: string; // alternate subscribed data from contactDataService
contactServObj: ContactDataService;
constructor(contactServObj: ContactDataService) {
this.contactServObj = contactServObj;
}
ngOnChanges(changes: SimpleChanges) {
console.log('ngOnChanges called!');
console.log(changes);
for (const key of Object.keys(changes)) {
console.log(`${key} changed.Current: ${changes[key].currentValue}.Previous: ${changes[key].previousValue}`);
}
}
ngOnInit() {
this.contactList = this.contactServObj.getContacts();
// console.log('ngOnInit called!');
this.contactServObj.queryString.subscribe((query: string) => {
this.searchQuery = query;
});
}
ngDoCheck() {
console.log('ngDoCheck called!');
}
}
The Contact Data Service (where contact array is managed) is as follows:
import { EventEmitter } from '@angular/core';
import { Contact } from './contact.model';
export class ContactDataService {
private contactList: Contact[] = [];
getContacts() {
return this.contactList;
}
sortContactHandler = (value) => {
console.log(value);
const field = value === 'default' ? null : value;
if (this.contactList.length > 1 && field !== null) {
this.contactList.sort(this.compareValues(field, 'asc'));
console.log(this.contactList);
}
}
compareValues = (key, order= 'asc') => {
return function(a, b) {
if (!a.hasOwnProperty(key) ||
!b.hasOwnProperty(key)) {
return 0;
}
const varA = (typeof a[key] === 'string') ?
a[key].toUpperCase() : a[key];
const varB = (typeof b[key] === 'string') ?
b[key].toUpperCase() : b[key];
let comparison = 0;
if (varA > varB) {
comparison = 1;
} else if (varA < varB) {
comparison = -1;
}
return (
(order === 'desc') ?
(comparison * -1) : comparison
);
};
}
addContactHandler(sentContact: Contact) {
let field = '';
const findExistingContact = this.contactList.findIndex((el, i) => {
// return (name === el.name) || (phone === el.phone) || (email === el.email);
if (sentContact.name === el.name) {
field = 'name';
return true;
} else if (sentContact.phone === el.phone) {
field = 'phone';
return true;
} else if (sentContact.email === el.email) {
field = 'email';
return true;
}
return false;
});
console.log(findExistingContact);
if (findExistingContact === -1) {
const newContact: Contact = sentContact;
this.contactList.push(newContact);
} else {
alert(`Contact with field ${field} already exists!`);
}
}
}
The Contact Data Service is injected at the root level - in app.module.ts as shown below:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { ContactComponent } from './contact-component/contact-component.component';
import { ContactListComponent } from './contact-list-component/contact-list-component.component';
import { AddContactComponent } from './add-contact-component/add-contact-component.component';
import { SearchContactComponent } from './search-contact-component/search-contact-component.component';
import { BackdropComponent } from './backdrop/backdrop.component';
import { ModalComponent } from './modal/modal.component';
import { EditContactComponent } from './edit-contact/edit-contact.component';
import { ContactDataService } from './ContactData.service';
import { ModalShowService } from './modal-show.service';
import { CockpitComponentComponent } from './cockpit-component/cockpit-component.component';
import { FilterContactPipe } from './filter-contact.pipe';
import { SortComponent } from './sort/sort.component';
import { ContactDetailComponent } from './contact-detail/contact-detail.component';
const appRoutes: Routes = [
{ path: '' , component: AddContactComponent },
{ path: 'contactList', component: ContactListComponent },
{ path: 'contactList/:name', component: ContactDetailComponent }
];
@NgModule({
declarations: [
AppComponent,
ContactComponent,
ContactListComponent,
AddContactComponent,
SearchContactComponent,
BackdropComponent,
ModalComponent,
EditContactComponent,
CockpitComponentComponent,
FilterContactPipe,
SortComponent,
ContactDetailComponent
],
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot(appRoutes)
],
providers: [ContactDataService, ModalShowService],
bootstrap: [AppComponent]
})
export class AppModule { }
Lastly, I have a Sort Component which is just a simple drop-down select box containing the 3 fields to sort contacts on. All it does is send this field to the sortContactHandler function in the Contact Data Service and I see the result of this in the console.log(this.contactList) statement. But this change is not reflected in the ContactList component which renders the array.
Question: What is the best way to get this sorted contactList array to my ContactList component (it only retrieves this array in the ngOnInit() function)?
I was thinking this could be accomplished using life-cycle hooks and so, I have tried to understand them, but maybe I'm doing something wrong. The ngOnChanges() never gets called (not once have I seen a console.log() from within that function) and I'm not sure how to use ngDoCheck(). Also from what I have read about the above life-cycle methods is that they react to changes in input properties of the component, so I'm not sure they can be used in my case.
Here is the issue on StackBlitz
Upvotes: 1
Views: 1717
Reputation: 173
NgOnChanges - reacts to changes in values of properties declared with @Input decorators but you have a normal property in your component class.It wont detect the change.
As far as i understand your scenario, you have both the siblings component on UI, you are changing service class's property value from one of the component and expecting that change in another component but there is no way you are reading that change in component except in OnInit (which gtes executed once when you load the component) , so if your component stays there in the DOM and you are expecting changes to be reflected.
You can go with Observable data stream , so basic idea is to have Observable data in your service class and you are supposed to subscribe to that data at other places (in your component classes) so that whenever you emit any changes on Observable stream , subscribers will get notified.
you can try to have observable 'contactList` in your service as below,
private contactList = new Subject<Contact[]>();
contactList$ = this.contactList.asObservable();
and then you can subscribe to it in your contact List component where you are expecting the changes to get reflected as below.
serviceObj.contactList$.subscribe(
contactList => {
this.contactList= contactList;
})
and to execute the code that you write inside of your subscribe function you need to have this code in your service class
this.contactList.next(contactList)
above line is execution of next method of an observable object,definition of which you have written as a first parameter to subscribe function.
If you can upload your code somewhere , i can have a better look and suggest.
P.S. - even i am new to Angular/RxJs , there could be better technical term that one can use, please feel free to modify.
AFTER LOOKING INTO YOUR CODE
I came up with this easiest fix for you.
This is all what you need to add.
contact-list.component.html
<app-sort (contactListUpdate)="fetchUpdatedList()"></app-sort>
contact-list.component.ts
fetchUpdatedList(){
this.contactList = this.contactServObj.getContacts();
}
sort-contact.component.ts
@Output() contactListUpdate:EventEmitter<any>=new EventEmitter<any>();
constructor(private contactServObj: ContactDataService) { }
onSelectChange(event) {
this.contactServObj.sortContactHandler(event.target.value);
this.contactListUpdate.emit();
}
I think its quite self explanatory, you want to execute some code in parent component based on some condition in child component, which is when we can utilize event emitter to emit event from child component, and listen to that event in parent component.
Please try this and let me know if you want any explanation or code to be uploaded.
Upvotes: 2