I've got a service, which determines the location, it's written as Observable
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
const GEOLOCATION_ERRORS = {
'errors.location.unsupportedBrowser': 'Browser does not support location services',
'errors.location.permissionDenied': 'You have rejected access to your location',
'errors.location.positionUnavailable': 'Unable to determine your location',
'errors.location.timeout': 'Service timeout has been reached'
};
#Injectable()
export class GeolocationService {
public getLocation(opts): Observable<any> {
return Observable.create(observer => {
if (window.navigator && window.navigator.geolocation) {
window.navigator.geolocation.getCurrentPosition(
(position) => {
observer.next(position);
observer.complete();
},
(error) => {
switch (error.code) {
case 1:
observer.error(GEOLOCATION_ERRORS['errors.location.permissionDenied']);
break;
case 2:
observer.error(GEOLOCATION_ERRORS['errors.location.positionUnavailable']);
break;
case 3:
observer.error(GEOLOCATION_ERRORS['errors.location.timeout']);
break;
}
}, opts);
} else {
observer.error(GEOLOCATION_ERRORS['errors.location.unsupportedBrowser']);
}
});
}
}
export var GeolocationServiceInjectables: Array<any> = [
{ provide: GeolocationService, useClass: GeolocationService }
];
Then in my HttpService I want to construct the query URL with the output from location service
import { Observable } from 'rxjs/Observable';
import { Injectable, Inject } from '#angular/core';
import { Http, Response } from '#angular/http';
import { GeolocationService } from './location.service';
import { WeatherItem } from '../weather-item/weather-item.model';
export const OpenWeatherMap_API_KEY: string = 'SOME_API_KEY';
export const OpenWeatherMap_API_URL: string = 'http://api.openweathermap.org/data/2.5/forecast';
#Injectable()
export class HttpService {
constructor(private http: Http,
private geolocation: GeolocationService,
#Inject(OpenWeatherMap_API_KEY) private apiKey: string,
#Inject(OpenWeatherMap_API_URL) private apiUrl: string) {
}
prepaireQuery(): void {
this.geolocation.getLocation({ enableHighAccuracy: false, maximumAge: 3 }).subscribe(
(position) => {
let params: string = [
`lat=${position.latitude}`,
`lon=${position.longitude}`,
`APPID=${this.apiKey}`,
].join('&');
// return `${this.apiUrl}?${params}`;
}
);
}
getWeather(): Observable<WeatherItem[]> {
return this.http.get(/*there should be the url*/)
.map((response: Response) => {
return (<any>response.json()).items.map(item => {
const city = {
city: item.city.name,
country: item.city.country,
}
return item.list.map(entity => {
return new WeatherItem({
temp: entity.main.temp,
temMin: entity.main.temp_min,
temMax: entity.main.temp_max,
weatherCond: entity.weather.main,
description: entity.weather.description,
windSpeed: entity.wind.speed,
icon: entity.weather.icon,
city,
})
})
})
})
}
}
export var HttpServiceInjectables: Array<any> = [
{ provide: HttpService, useClass: HttpService },
{ provide: OpenWeatherMap_API_KEY, useValue: OpenWeatherMap_API_KEY },
{ provide: OpenWeatherMap_API_URL, useValue: OpenWeatherMap_API_KEY }
];
The question is how to get the URL before doing request. I've seen solutions with unsubscribe(), but I think thay are not so good. I've thought about merge() but I'm not sure that it's what I really want.
You are probably looking for the mergeMap operator of RxJs.
What mergeMap does is automatically subscribes to the source observable, then lets you work with its result in your inner observable, and then finally flattens your output.
In this example, you call the firstUrl and use the result you get from that request in your second call to secondUrl:
this.http.get(`{firstUrl}`)
.mergeMap(res => this.http.get(`{secondUrl}/{res.json()}`))
.subscribe(...)
I have not made it specific for your code, as I'm not sure of exactly what you want to do. But I hope this will help you on the way!
This can be done using map/flatMap combination:
getWeather(): Observable<WeatherItem[]> {
return this.geolocation.getLocation({ enableHighAccuracy: false, maximumAge: 3 })
.map((position) => {
let params: string = [
`lat=${position.latitude}`,
`lon=${position.longitude}`,
`APPID=${this.apiKey}`,
].join('&');
return `${this.apiUrl}?${params}`;
})
.flatMap(url => this.http.get(url)
.map((response: Response) => {
return (<any>response.json()).items.map(item => {
const city = {
city: item.city.name,
country: item.city.country,
}
return item.list.map(entity => {
return new WeatherItem({
temp: entity.main.temp,
temMin: entity.main.temp_min,
temMax: entity.main.temp_max,
weatherCond: entity.weather.main,
description: entity.weather.description,
windSpeed: entity.wind.speed,
icon: entity.weather.icon,
city,
})
})
})
})
}
Related
i have a messaging room application that create a discussion chat foreach room between users signed in with same room ,
i m facing this error :
Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
i used 3 functions :
getChatMessages() : to get all chat messages from firestore for users
with same room
getCurrentRoom():to get the room of the connected user
getUsers(): return all users with same room
chat.services.ts
import { Injectable } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/compat/auth';
import { AngularFirestore } from '#angular/fire/compat/firestore';
import { Observable } from 'rxjs';
import { Timestamp } from 'rxjs/internal/operators/timestamp';
import { switchMap,map, timestamp, filter } from 'rxjs/operators';
import { query, orderBy, limit, where } from "firebase/firestore";
import firebase from 'firebase/compat/app';
export interface User {
uid: string;
email: string;
displayName:string;
username?:string;
room?:string
}
export interface Message {
createdAt: firebase.firestore.FieldValue;
id: string;
from: string;
msg: string;
fromName: string;
myMsg: boolean;
}
#Injectable({
providedIn: 'root'
})
export class ChatService {
currentUser: User ;
currentRoom:string="";
updatedRoom:string="";
constructor(private afAuth: AngularFireAuth, private afs: AngularFirestore) {
this.afAuth.onAuthStateChanged((user) => {
this.currentUser=user;
console.log("current email"+this.currentUser.email);
});
}
async signup({ username,email, password,room }): Promise<any> {
const credential = await this.afAuth.createUserWithEmailAndPassword(
email,
password
);
const uid = credential.user.uid;
return this.afs.doc(
`users/${uid}`
).set({
uid,
email: credential.user.email,
username:username,
room:room,
})
}
signIn({ email, password }) {
return this.afAuth.signInWithEmailAndPassword(email, password);
}
signOut(): Promise<void> {
return this.afAuth.signOut();
}
addChatMessage(msg) {
return this.afs.collection('messages').add({
createdAt:firebase.firestore.FieldValue.serverTimestamp(),//firebase.default.firestore.Timestamp,
msg: msg,
from: this.currentUser.uid
});
}
async getChatMessages() {
let users = [];
return (await this.getUsers()).pipe(
switchMap(res => {
users = res;
console.log("resssssss"+res);
return this.afs.collection('messages', ref => ref.orderBy('createdAt','asc')).valueChanges({ idField: 'id' }) as Observable<Message[]>;
}),
map(messages => {
console.log("messages"+messages);
// Get the real name for each user
for (let m of messages) {
m.fromName = this.getUserForMsg(m.from, users);
m.myMsg = this.currentUser.uid === m.from;
}
return messages
})
)
}
public async getCurrentRoom() {
await this.afs.collection('users', ref => ref.where("email", "==", this.currentUser.email)).get().toPromise()
.then(snapshot => {
snapshot.forEach(doc => {
this.currentRoom=JSON.parse(JSON.stringify(doc.data())).room;
console.log("current room" + this.currentRoom);
});
});
}
public async getUsers() {
console.log("this room" + this.currentRoom);
return this.afs.collection('users', ref => ref.where("room", "==", this.currentRoom)).valueChanges({
idField: 'uid'
}) as Observable < User[] > ;
}
private getUserForMsg(msgFromId, users: User[]): string {
for (let usr of users) {
if (usr.uid == msgFromId) {
return usr.username ?? 'undefind';
}
}
return 'Deleted';
}
}
my chat.page.ts :
import { Component, OnInit, ViewChild } from '#angular/core';
import { IonContent } from '#ionic/angular';
import { Observable } from 'rxjs';
import { ChatService } from '../chat.service';
import { Router } from '#angular/router';
import { AngularFireStorage, AngularFireUploadTask } from '#angular/fire/compat/storage';
import { AngularFirestore, AngularFirestoreCollection } from '#angular/fire/compat/firestore';
import { finalize, tap } from 'rxjs/operators';
export interface FILE {
name: string;
filepath: string;
size: number;
}
#Component({
selector: 'app-chat',
templateUrl: './chat.page.html',
styleUrls: ['./chat.page.scss'],
})
export class ChatPage implements OnInit {
ngFireUploadTask: AngularFireUploadTask;
progressNum: Observable<number>;
progressSnapshot: Observable<any>;
fileUploadedPath: Observable<string>;
room:any;
files: Observable<FILE[]>;
ImgtoSend:any;
FileName: string;
FileSize: number;
isImgUploading: boolean;
isImgUploaded: boolean;
private ngFirestoreCollection: AngularFirestoreCollection<FILE>;
#ViewChild(IonContent) content: IonContent;
messages:any=[];
newMsg = '';
constructor(private angularFirestore: AngularFirestore,
private angularFireStorage: AngularFireStorage,private chatService: ChatService, private router: Router) {
this.isImgUploading = false;
this.isImgUploaded = false;
this.ngFirestoreCollection = angularFirestore.collection<FILE>('filesCollection');
this.files = this.ngFirestoreCollection.valueChanges();
}
ngOnInit() {
this.messages= this.chatService.getChatMessages();
}
sendMessage() {
this.chatService.addChatMessage(this.newMsg).then(() => {
this.newMsg = '';
this.content.scrollToBottom();
});
}
signOut() {
this.chatService.signOut().then(() => {
this.router.navigateByUrl('/login', { replaceUrl: true });
});
}
fileUpload(event: FileList) {
const file = event.item(0)
if (file.type.split('/')[0] !== 'image') {
console.log('File type is not supported!')
return;
}
this.isImgUploading = true;
this.isImgUploaded = false;
this.FileName = file.name;
const fileStoragePath = `filesStorage/${new Date().getTime()}_${file.name}`;
console.log("filestoragepath"+fileStoragePath);
const imageRef = this.angularFireStorage.ref(fileStoragePath);
console.log("image ref"+imageRef);
this.ngFireUploadTask = this.angularFireStorage.upload(fileStoragePath, file);
this.ImgtoSend=this.FileName;
console.log("image to Send"+this.ImgtoSend);
this.progressNum = this.ngFireUploadTask.percentageChanges();
this.progressSnapshot = this.ngFireUploadTask.snapshotChanges().pipe(
finalize(() => {
this.fileUploadedPath = imageRef.getDownloadURL();
console.log("uploaded path"+this.fileUploadedPath);
this.fileUploadedPath.subscribe(resp=>{
this.fileStorage({
name: file.name,
filepath: resp,
size: this.FileSize
});
this.isImgUploading = false;
this.isImgUploaded = true;
},error => {
console.log(error);
})
}),
tap(snap => {
this.FileSize = snap.totalBytes;
})
)
}
fileStorage(image: FILE) {
const ImgId = this.angularFirestore.createId();
this.ngFirestoreCollection.doc(ImgId).set(image).then(data => {
console.log("data"+data);
}).catch(error => {
console.log(error);
});
}
}
enter code here
First, you have to eleminate all the code that is not relevant to the question to help people help you.
Second, the issue is simple:
What the compiler is telling you here is, Hey Mohammed Amir, *ngFor="" is used to loop through an Array of objects while you are passing to it an object literal.
Check the value of the property you bind to *ngFor="let msg of [YourAssumedArrayFromEndpointResponse]" in your template you will find that YourAssumedArrayFromEndpointResponse is not an array. That's why the compiler is complaining
I have started learning how to test angular projects. So far basic unit testing is working fine for me but for the dependency testing especially when API services are injected into the component I am facing issue for providing HttpClient. I have tried different solutions but none is working for me.
Service
// Present in HttpClientService file
getDisposition() {
return this.http.get<{ message: string, data: { dispositionList: Disposition[] } }>(`${this.URL}/disposition/get`);
}
// Present in FileProcessService file
deleteMedia(media: string) {
return this.http.delete<{ message: string }>(`${this.URL}/certificate/delete?certificate=${media}`);
}
add-edit-activity.component.ts
import { HttpEventType } from '#angular/common/http';
import { Component, ElementRef, Inject, OnInit, ViewChild } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { DomSanitizer, SafeUrl } from '#angular/platform-browser';
import { AssignedPerson } from '#model/assigned-person.model';
import { Disposition } from '#model/disposition.model';
import { mimeTypes } from '#model/mime-type';
import { FileProcessService } from '#service/file-process.service';
import { HttpClientService } from '#service/http-client.service';
import { DeleteComponent } from '#shared/delete/delete.component';
import { CustomErrorStateMatcher } from '#validator/error-state-matcher';
import { ToastrService } from 'ngx-toastr';
#Component({
selector: 'app-add-edit-activity',
templateUrl: './add-edit-activity.component.html',
styleUrls: ['./add-edit-activity.component.css']
})
export class AddEditActivityComponent implements OnInit {
constructor(private fb: FormBuilder, private sanitizer: DomSanitizer, private dialogRef: MatDialogRef<AddEditActivityComponent>, #Inject(MAT_DIALOG_DATA) public data: any,
private _http: HttpClientService, private _fileService: FileProcessService, private toastr: ToastrService, private dialog: MatDialog,) { }
re = new RegExp(/^[a-zA-Z-]*/, 'g');
ISOstamp = { T: ' ', Z: '000' };
matcher = new CustomErrorStateMatcher();
dispositionList: Disposition[] = [];
assignedPersonList: AssignedPerson[] = [];
filterAssignedPersonList: AssignedPerson[] = [];
uploaded = false;
uploadProgress = false;
fileURL: SafeUrl;
activityForm = this.fb.group({
gaugeId: [this.data.gaugeId, Validators.maxLength(9)], createTimeStamp: [{ value: new Date().toISOString().replace(/[TZ]/g, m => this.ISOstamp[m]), disabled: true }],
user: [{ value: sessionStorage.getItem('username'), disabled: true }], disposition: ['', [Validators.required, Validators.maxLength(30)]],
assignedPersonName: ['', Validators.maxLength(30)], department: ['', Validators.maxLength(20)],
shift: ['', Validators.maxLength(1)], remark: ['', Validators.maxLength(50)],
calibrationDate: ['', Validators.maxLength(10)], attachment: ['', Validators.maxLength(255)]
});
#ViewChild('file') certificate: ElementRef;
ngOnInit(): void {
this.data.type.match(this.re)[0] === 'Update' && this.setFormValues();
this._http.getDisposition().subscribe(response => this.dispositionList = response.data.dispositionList);
this._http.getAssignedPerson().subscribe(response => this.assignedPersonList = this.filterAssignedPersonList = response.data.assignedToList);
}
get GaugeId() {
return this.activityForm.get('gaugeId');
}
get TimeStamp() {
return this.activityForm.get('createTimeStamp');
}
get Attachment() {
return this.activityForm.get('attachment');
}
get Disposition() {
return this.activityForm.get('disposition');
}
get DispositionValue() {
return this.dispositionList.map(e => e.dispositionType).indexOf(this.Disposition.value) < 0;
}
get AssignedTo() {
return this.activityForm.get('assignedPersonName');
}
get AssignedToValue() {
return this.assignedPersonList.map(e => `${e.firstName} ${e.lastName}`).indexOf(this.Disposition.value) < 0;
}
private async setFormValues() {
this.activityForm.patchValue({ ...this.data });
if (this.data.attachment) {
this.uploadProgress = true;
this.uploaded = true;
await this.fetchUploadedFile(this.data.attachment, mimeTypes[this.data.attachment.split('.')[1]]);
this.activityForm.markAsPristine();
}
}
searchAssignedPerson(event) {
if (event.target.value) {
this.filterAssignedPersonList = [];
for (let person of this.assignedPersonList) {
if (person.firstName.toLowerCase().startsWith(event.target.value.toLowerCase())) {
this.filterAssignedPersonList.push(person);
}
}
} else { this.filterAssignedPersonList = this.assignedPersonList }
}
upload(event) {
const file: File = event.target.files[0];
this.certificate.nativeElement.value = '';
if (file.size > (20 * 1000 * 1000)) { // Checking if File size is above 20MB
this.toastr.error('Size of ' + file.name + ' is above 20MB');
return;
}
const fd = new FormData();
fd.append('certificate', file, file.name);
this.processAttachment(fd);
}
private processAttachment(file: FormData) {
this._fileService.uploadMedia(file).subscribe(event => {
if (event.type === HttpEventType.UploadProgress) { this.uploadProgress = true }
if (event.type === HttpEventType.Response) {
let media = event.body.data.Certificate;
this.fetchUploadedFile(media.fileName, media.fileType);
this.toastr.info(event.body.message);
}
}, error => {
this.toastr.error(error.error.message);
this.uploadProgress = false;
});
}
private async fetchUploadedFile(file: string, mimeType: string) {
try {
let response = await this._fileService.getMedia(file).toPromise();
this.fileURL = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(new Blob([response], { type: mimeType })));
this.uploaded = true;
this.uploadProgress = false;
this.activityForm.patchValue({ attachment: file });
this.Attachment.markAsDirty();
} catch (error) {
this.uploadProgress = false;
this._fileService.processError(error.error)
}
}
deleteFile() {
this.dialog.open(DeleteComponent, {
width: '350px', disableClose: true
}).afterClosed().subscribe(response => {
if (response) {
this._fileService.deleteMedia(this.Attachment.value).subscribe(response => {
this.toastr.info(response.message);
this.activityForm.patchValue({ attachment: '' });
this.Attachment.markAsDirty();
this.uploaded = false;
}, error => this.toastr.error(error.error.message));
}
});
}
async doAction() {
let message = '';
if (this.data.type.match(this.re)[0] === 'Add') {
message = await (await this._http.addActivityLog(this.activityForm.getRawValue()).toPromise()).message;
} else {
message = await (await this._http.updateActivityLog(this.GaugeId.value, this.TimeStamp.value, this.activityForm.getRawValue()).toPromise()).message;
}
this.dialogRef.close(message);
}
}
add-edit-activity.component.spec.ts
import { ComponentFixture, TestBed, tick } from '#angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { FileProcessService } from '#service/file-process.service';
import { HttpClientService } from '#service/http-client.service';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { AddEditActivityComponent } from './add-edit-activity.component';
describe('AddEditActivityComponent', () => {
let component: AddEditActivityComponent;
let fixture: ComponentFixture<AddEditActivityComponent>;
let _http: HttpClientService, _file: FileProcessService;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddEditActivityComponent],
imports: [FormsModule, ReactiveFormsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { type: 'Add Activity Log', gaugeId: 'W-001' } },,
{ provider: HttpClientService, useValue: null },
{ provider: FileProcessService, useValue: null }
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddEditActivityComponent);
component = fixture.debugElement.componentInstance;
_http = fixture.debugElement.injector.get(HttpClientService);
_file = fixture.debugElement.injector.get(FileProcessService);
fixture.detectChanges();
});
// These 2 tests throwing error.
it('SHOULD mock http service', () => {
let spy = spyOn(_http, 'getDisposition').and.callFake(() => {
return of({
message: '',
data: { dispositionList: [] }
}).pipe(delay(100));
});
component.ngOnInit();
tick(100);
expect(component.dispositionList).toEqual([]);
});
it('SHOULD mock file service', () => {
let spy = spyOn(_file, 'deleteMedia').and.callFake((file: string) => {
return of({ message: '' }).pipe(delay(100));
});
component.deleteFile();
tick(100);
expect(component.uploaded).toBe(false);
});
});
The error that am getting for those 2 tests (I'm providing the error of 1 test case, the same is coming for the 2nd one also):
SHOULD mock http service
AddEditActivityComponent
Error: Invalid provider for the NgModule 'DynamicTestModule' - only instances of Provider and Type are allowed, got: [..., ..., ...,
..., ?undefined?, ..., ...]
at throwInvalidProviderError (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:240:1)
at providerToFactory (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11550:1)
at providerToRecord (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11521:1)
at R3Injector.processProvider (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11424:1)
Error: : could not find an object to spy upon for getDisposition() Usage: spyOn(, ) Error: :
could not find an object to spy upon for getDisposition() Usage:
spyOn(, )
at
Anyone, please help what's the exact error happening with my test cases? Stuck here for almost 2 days.
You have to avoid using a 'null' value when providing services in the TestBed, that what is causing your problem.
The best way to mock Angular services is to create SpyObj instances and then use them like what is done Angular tests documentation.
Here is how would be your spec file if you were using SpyObj:
import { ComponentFixture, TestBed, tick } from '#angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { FileProcessService } from '#service/file-process.service';
import { HttpClientService } from '#service/http-client.service';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { AddEditActivityComponent } from './add-edit-activity.component';
describe('AddEditActivityComponent', () => {
let component: AddEditActivityComponent;
let fixture: ComponentFixture<AddEditActivityComponent>;
let httpSpy: jasmine.SpyObj<HttpClientService>;
let fileSpy: jasmine.SpyObj<FileProcessService>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddEditActivityComponent],
imports: [FormsModule, ReactiveFormsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { type: 'Add Activity Log', gaugeId: 'W-001' } },
{
provider: HttpClientService,
useValue: jasmine.createSpyObj('HttpClientService', ['getDisposition', 'getAssignedPerson', 'addActivityLog', 'updateActivityLog'])
},
{
provider: FileProcessService,
useValue: jasmine.createSpyObj('FileProcessService', ['uploadMedia', 'getMedia', 'deleteMedia', 'processError'])
}
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddEditActivityComponent);
component = fixture.debugElement.componentInstance;
httpSpy = fixture.debugElement.injector.get(HttpClientService) as jasmine.SpyObj<HttpClientService>;
fileSpy = fixture.debugElement.injector.get(FileProcessService) as jasmine.SpyObj<FileProcessService>;
fixture.detectChanges();
});
// These 2 tests throwing error.
it('SHOULD mock http service', () => {
httpSpy.getDisposition.and.callFake(() => {
return of({
message: '',
data: { dispositionList: [] }
}).pipe(delay(100));
});
component.ngOnInit();
tick(100);
expect(component.dispositionList).toEqual([]);
});
it('SHOULD mock file service', () => {
fileSpy.deleteMedia.and.callFake((file: string) => {
return of({ message: '' }).pipe(delay(100));
});
component.deleteFile();
tick(100);
expect(component.uploaded).toBe(false);
});
});
I have a mat table in my angular project just like below:
The data is fetched from server using rest API. The server side is using nodejs and sequelize. When I click the delete button, I want my mat table to refresh with new data. But unfortunately, the refresh data is loaded before the delete is completed, so it looks as if the delete button is not functioning.
Here is my code:
list.component.ts
import {AfterViewInit, ChangeDetectorRef, Component, ViewChild} from '#angular/core';
import {MatPaginator, PageEvent} from '#angular/material/paginator';
import {MatSort} from '#angular/material/sort';
import {MatTableDataSource} from '#angular/material/table';
import { Title } from '#angular/platform-browser';
import { Router } from '#angular/router';
import { ClientService } from 'src/app/core/services/client.service';
import { MainService } from 'src/app/core/services/main.service';
import { Client } from 'src/app/models/client.model';
#Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements AfterViewInit {
clients: Client[];
displayedColumns: string[] = ['client_name', 'client_address', 'client_home_phone', 'client_mobile_phone', 'client_status', 'buttons'];
dataSource: MatTableDataSource<Client>;
length = 0;
#ViewChild(MatPaginator) paginator: MatPaginator;
#ViewChild(MatSort) sort: MatSort;
constructor(private changeDetectorRefs: ChangeDetectorRef, private router: Router, private titleService: Title, private mainService: MainService, private clientService: ClientService) {
this.titleService.setTitle('Clients');
this.mainService.setPageTitle('Clients');
this.mainService.setButtonToolbar([
{
'url': 'create',
'icon': 'add',
'color': 'primary'
},
]);
this.clientService.list(25, 0).subscribe(clients => {
this.clients = clients.data;
this.dataSource = new MatTableDataSource(this.clients);
this.length = clients.count;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}
ngAfterViewInit() {
}
onChangedPage(pageData: PageEvent) {
this.clientService.list(pageData.pageSize, pageData.pageIndex * pageData.pageSize).subscribe(clients => {
this.clients = clients.data;
this.dataSource = new MatTableDataSource(this.clients);
this.length = clients.count;
this.dataSource.sort = this.sort;
});
}
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.clientService.list(this.paginator.pageSize, 0, filterValue).subscribe(clients => {
this.clients = clients.data;
this.dataSource = new MatTableDataSource(this.clients);
this.length = clients.count;
this.dataSource.sort = this.sort;
});
}
onUpdate(id: String) {
this.router.navigate(['/client/update/'+id]);
}
onDelete(id: string) {
this.clientService.delete(id).subscribe(response => {
if (response == 200) {
this.clientService.list(this.paginator.pageSize, 0).subscribe(clients => {
this.clients = clients.data;
this.dataSource = new MatTableDataSource(this.clients);
this.length = clients.count;
this.dataSource.sort = this.sort;
this.changeDetectorRefs.detectChanges();
});
}
});
}
}
client.service.ts
import { HttpClient, HttpHeaders } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { map } from "rxjs/operators";
import { Client } from "src/app/models/client.model";
import { MainService } from 'src/app/core/services/main.service';
#Injectable()
export class ClientService {
constructor(private http: HttpClient, private mainService: MainService) {}
list(limit: number, offset: number, filter?: string) {
let queryParams = '?limit='+limit+'&offset='+offset;
if (filter) {
queryParams += '&filter='+filter;
}
return this.http
.get(this.mainService.getbaseApiUrl()+"client/list" + queryParams)
.pipe(map(responseData => {
const clientArray = [...responseData['rows']];
return {data: clientArray, count: responseData['count']};
}));
}
get(id: string) {
return this.http
.get(this.mainService.getbaseApiUrl()+"client/one/"+id);
}
add(client: Client) {
return this.http
.post(this.mainService.getbaseApiUrl()+'client/add', client, {
headers: new HttpHeaders({
// 'Origin': '*',
//'Access-Control-Allow-Origin': '*',
//'Access-Control-Allow-Credentials': 'true',
'Content-Type': 'application/json',
})
});
}
update(client: Client) {
return this.http
.post(this.mainService.getbaseApiUrl()+'client/edit', client, {
headers: new HttpHeaders({
// 'Access-Control-Allow-Origin': '*',
// 'Access-Control-Allow-Credentials': 'true',
'Content-Type': 'application/json',
})
});
}
delete(id: string) {
return this.http.delete(this.mainService.getbaseApiUrl()+'client/delete/'+id);
}
}
I'm new to Angular and this may look simple, but I couldn't think of any solution. Is there any way for Angular to wait until the deletion is completed then refresh? The temporary solution is to use setTimeout so the mat table will refresh after waiting for X seconds, but I don't think that's a clean solution.
Please help.
UPDATE:
My temporary solution is to reload the current router.
app-routing.module.ts
#NgModule({
imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})],
exports: [RouterModule]
})
list.component.ts
ngOnInit() {
this.router.routeReuseStrategy.shouldReuseRoute = () => {
return false;
}
}
onDelete(id: string) {
this.clientService.delete(id).subscribe(response => {
if (response == 200) {
this.router.navigate(['/client']);
}
});
}
But it will be great if there are other better solutions than this one.
onDelete(id: string) {
this.clientService.delete(id)
.pipe(switchMap(() => this.clientService.list(this.paginator.pageSize, 0)
.subscribe(clients => {
this.clients = clients.data;
this.dataSource = new MatTableDataSource(this.clients);
this.length = clients.count;
this.dataSource.sort = this.sort;
this.changeDetectorRefs.detectChanges();
});
I am using socket.io in my angular and node application. A user joins the room the user can see his username in the user list. When user2 joins, user1 can see both user1 and user2 in the user list. However, user2 can only see user2. If user 3 joins. user1 can see user 1, user2, and user3. User2 can see user2 and user3. However, user3 only sees user3.
chat.service.ts
import { Injectable } from '#angular/core';
import * as io from 'socket.io-client';
import { Observable, onErrorResumeNext, observable } from 'rxjs';
//import { Observable } from 'rxjs/Observable';
#Injectable()
export class ChatService {
private socket = io('http://localhost:8080');
joinRoom(data) {
this.socket.emit('join', data);
}
newUserJoined() {
let observable = new Observable<{user: String, message:String}>(observer => {
this.socket.on('new user joined ', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
});
return observable;
}
leaveRoom(data) {
this.socket.emit('leave', data);
}
userLeftRoom() {
let observable = new Observable<{user: String, message:String}>(observer => {
this.socket.on('left room', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
});
return observable;
}
sendMessage(data) {
this.socket.emit('message', data);
}
newMessageRecieved() {
let observable = new Observable<{user: String, message:String, time: any}>(observer => {
this.socket.on('new message', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
});
return observable;
}
getRoomUsers() {
let observable = new Observable<{user: String, message:String}>(observer => {
this.socket.on('roomUsers', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
});
return observable;
}
}
chat.component.ts
import { Component, OnInit } from '#angular/core';
import { ChatService } from '../../services/chat.service';
import { AuthService } from '../../services/auth.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css'],
providers: [ChatService]
})
export class ChatComponent implements OnInit {
room: any;
user: any;
username: any;
roomName: any;
messageArray: Array<{user: String, message: String, time: any}> = [];
userArray: Array<{user: String, message: String}> = [];
messageText: String;
time: any;
constructor( private chatService: ChatService, private authService: AuthService) {
// this.chatService.newUserJoined()
// .subscribe(data => this.userArray.push(data));
// this.chatService.userLeftRoom()
// .subscribe(data => this.userArray.splice(this.userArray.indexOf(data)));
this.chatService.newMessageRecieved()
.subscribe(data => this.messageArray.push(data));
this.chatService.getRoomUsers()
.subscribe(data => this.userArray.push(data));
}
ngOnInit() {
this.getUser();
}
getUser() {
this.user = localStorage.getItem('user');
this.username = JSON.parse(this.user).username;
this.getRoom();
}
getRoom() {
this.room = localStorage.getItem('room');
this.roomName = JSON.parse(this.room).name;
this.join();
}
join() {
console.log(this.roomName);
console.log(this.username);
this.chatService.joinRoom({user: this.username, room: this.roomName});
}
leave() {
console.log(this.roomName);
console.log(this.username);
let userIndex = this.userArray.indexOf(this.username);
delete this.userArray[userIndex];
localStorage.removeItem('room');
this.chatService.leaveRoom({user: this.username, room: this.roomName});
}
sendMessage() {
console.log(this.roomName);
console.log(this.username);
this.chatService.sendMessage({user: this.username, room: this.roomName, message: this.messageText, time: this.time});
this.messageText = '';
}
}
chat.component.html
<ul *ngFor="let item of userArray" id="usersList">
<li >{{item.user}}</li>
</ul>
What comes through the “roomUsers” socket event? All the current users?
To me it looks like that event is only dispatching new arrivals, so every user that shows up only sees themselves and users that come after them.
Without too much knowledge of your code, it seems like one solution is to make the “roomUsers” event contain all users and not just newly added users. Another possibility is to define that Observable outside of the function and then provide the same observable to each newcomer, so everyone has the same data. Switch that function to a stream and expose it something like:
roomUsers$ = from(this.socket.on(“roomUsers”).pipe(
scan((a,c) => [...a, c], []),
shareReplay(1));
The shareReplay(1) operator means even late subscribers get the most recent value, and the scan builds things up as an array. So on your component you wouldn’t do the .push method into the array.
Again, I could be missing context, but hopefully this points you in the right directions.
I'm trying to use latitude and longitude from the geolocation service in my list service. Unfortunately this keeps returning as undefined. Not really sure what the issue could be.
list.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent, SubscriptionLike, PartialObserver } from 'rxjs';
import { List } from '../models/list.model';
import { map } from 'rxjs/operators';
import { GeolocationService } from '../services/geolocation.service';
#Injectable()
export class ListService {
constructor(private http: Http, private geoServ: GeolocationService) { }
getLongitude() {
this.geoServ.getLongitude().subscribe((longitude) => {
console.log(longitude)
});
}
private serverApi = 'http://localhost:3000';
public getAllLists(): Observable<List[]> {
const URI = `${this.serverApi}/yelp/${longitude}/${latitude}`;
return this.http.get(URI)
.pipe(map(res => res.json()))
.pipe(map(res => <List[]>res.businesses));
}
}
geolocation.service.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class GeolocationService {
constructor() { }
getGeoLocation() {
console.log('Geolocation working!');
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
const success = (pos) => {
const crd = pos.coords;
console.log(`Latitude : ${crd.latitude}`);
console.log(`Longitude : ${crd.longitude}`);
};
const error = (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
};
navigator.geolocation.getCurrentPosition(success, error, options);
}
getLongitude() {
console.log('Geolocation working!');
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
const success = (pos) => {
const crd = pos.coords;
console.log(`Longitude : ${crd.longitude}`);
const longitude = crd.longitude;
return longitude;
};
const error = (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
};
navigator.geolocation.getCurrentPosition(success, error, options);
}
}
thank you for taking the time to look at this. And I appreciate the further help on my additional question below. text text text text won't let me post until I add more details text text text text.
You are not returning anything from your methods. You should use a return statement. You also need to use either a callback function a promise or a observable. I'll give you a observable example:
getLongitude() {
this.geoServ.getLongitude().subscribe((longitude) => {
console.log(longitude)
});
}
And change your getLongitude:
getLongitude() {
return new Observable((observer) => {
console.log('Geolocation working!');
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
const success = (pos) => {
const crd = pos.coords;
console.log(`Longitude : ${crd.longitude}`);
const longitude = crd.longitude;
observer.next(longitude);
observer.complete();
};
const error = (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
observer.error(err);
observer.complete();
};
navigator.geolocation.getCurrentPosition(success, error, options);
});
}
If you want to get the longitude and latitude in your getAllList method you can use the mergeMap operator:
public getAllLists(): Observable<List[]> {
return this.geoServ.getGeoLocation().pipe(
mergeMap(({ latitude, longitude}) =>
this.http.get(`${this.serverApi}/yelp/${longitude}/${latitude}`)
),
map(res => res.businesses)
);
}
I'm using the HttpClient here. This is the 'new' http module from angular. It's advised to use this one instead of the old one. It also automatically does the json() call for you.