Could you explain me how I could use concatMap on getPrices() and getDetails()?
export class HistoricalPricesComponent implements OnInit, OnDestroy {
private unsubscribe$ = new Subject < void > ();
infoTitle: string = "";
lines: HistoryPoint[] = [];
model: Currency = new Currency();
svm: string;
constructor(
private location: Location,
private service: HistoricalPricesService,
private activatedRoute: ActivatedRoute
) {}
ngOnInit(): void {
let svm: string | null;
svm = this.activatedRoute.snapshot.paramMap.get('svm');
if (!svm) {
this.goBack();
return;
}
this.svm = svm;
this.getPrices();
this.getDetails(svm)
}
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
getPrices(): void {
this.service.getInstrumentHistoryEquities(this.svm, this.model).pipe(
takeUntil(this.unsubscribe$)
).subscribe(res => {
if (res.RETURNCODE === ApiResponseCodeEnum.Ok) {
if (res.HISTO.POINT.length > 0) {
this.lines = res.HISTO.POINT.reverse();
}
}
});
}
getDetails(svm: string): void {
this.service.getInstrumentInfo(svm).pipe(
takeUntil(this.unsubscribe$)
).subscribe(res => {
if (res.RETURNCODE === ApiResponseCodeEnum.Ok) {
this.infoTitle += " " + res.ADVTITRE.BASIQUETITRE.LABEL + " (" + res.ADVTITRE.BASIQUETITRE.PLACELABEL + ")";
}
});
}
goBack(): void {
this.location.back();
}
}
I tried to look on this page
https://www.tektutorialshub.com/angular/using-concatmap-in-angular/
But I don't know where to start?
The example does not allow me to understand how I could create this?
here it is
concatMap is a operator stream so you just need to use it through a creator stream to that I initialize two Subject for each actions getPrices & getDetails.
Then I perform the AJAX call following the concatMap strategy and get that into Observable in order to be combined or used directly into the template of the component.
export class HistoricalPricesComponent implements OnInit, OnDestroy {
private unsubscribe$ = new Subject < void > ();
infoTitle: string = "";
lines: HistoryPoint[] = [];
model: Currency = new Currency();
svm: string;
// actions
getPrices$ = new Subject<void>();
getDetails$ = new Subject<string>();
// states
prices$: Observable<any>();
details$: Observable<any>();
constructor(
private location: Location,
private service: HistoricalPricesService,
private activatedRoute: ActivatedRoute
) {}
ngOnInit(): void {
let svm: string | null;
svm = this.activatedRoute.snapshot.paramMap.get('svm');
if (!svm) {
this.goBack();
return;
}
this.svm = svm;
this.prices$ = this.getPrices$.pipe(concatMap(this.getPrices)
this.details$ = this.getDetails$.pipe(concatMap((svm => this.getDetails(svm))
}
ngOnDestroy(): void {
this.getPrices$.complete()
this.getDetails$.complete()
this.unsubscribe$.complete();
}
getPrices(): void {
this.service.getInstrumentHistoryEquities(this.svm, this.model).pipe(
takeUntil(this.unsubscribe$)
).subscribe(res => {
if (res.RETURNCODE === ApiResponseCodeEnum.Ok) {
if (res.HISTO.POINT.length > 0) {
this.lines = res.HISTO.POINT.reverse();
}
}
});
}
getDetails(svm: string): void {
this.service.getInstrumentInfo(svm).pipe(
takeUntil(this.unsubscribe$)
).subscribe(res => {
if (res.RETURNCODE === ApiResponseCodeEnum.Ok) {
this.infoTitle += " " + res.ADVTITRE.BASIQUETITRE.LABEL + " (" + res.ADVTITRE.BASIQUETITRE.PLACELABEL + ")";
}
});
}
goBack(): void {
this.location.back();
}
}
Related
I've been trying to make a simple WebRTC app using Firebase Database and PeerJs that can switch cameras. I found one tutorial and it works properly, but I want to switch the camera between front and back which is not included in the tutorial.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
...
<uses-feature android:name="android.hardware.camera.any"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
...
</manifest>
CallActivity.java
import static android.view.View.GONE;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.UUID;
public class CallActivity extends AppCompatActivity {
private static final String TAG = CallActivity.class.getSimpleName();
private final String CAMERA_FRONT = "user";
private final String CAMERA_BACK = "environment"; // Tried to use it on navigator.mediaDevices.getUserMedia({video: {facingMode: camera}}) but it didn't work.
private RelativeLayout layoutIncoming, layoutCall, layoutCallControl;
private Button buttonReject, buttonAccept, buttonCall, buttonAudio, buttonVideo, buttonCamera;
private EditText editTextCallName;
private TextView textViewIncoming;
private WebView webView;
private String name;
private String callerName;
private boolean isPeerConnected = false;
private DatabaseReference usersRef = FirebaseDatabase.getInstance("link_to_firebase_database").getReference("users");
private boolean videoEnabled = true;
private boolean audioEnabled = true;
private String camera = CAMERA_FRONT;
private String uniqueID;
//== Overridden ==//
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call);
layoutIncoming = findViewById(R.id.activity_call_layoutIncoming);
layoutCall = findViewById(R.id.activity_call_layoutCall);
layoutCallControl = findViewById(R.id.activity_call_layoutCallControl);
buttonAccept = findViewById(R.id.activity_call_buttonAccept);
buttonReject = findViewById(R.id.activity_call_buttonReject);
buttonCall = findViewById(R.id.activity_call_buttonCall);
buttonVideo = findViewById(R.id.activity_call_buttonVideo);
buttonAudio = findViewById(R.id.activity_call_buttonAudio);
buttonCamera = findViewById(R.id.activity_call_buttonCamera);
editTextCallName = findViewById(R.id.activity_call_editTextCallName);
textViewIncoming = findViewById(R.id.activity_call_textViewIncoming);
webView = findViewById(R.id.activity_call_webView);
if (getIntent().hasExtra("name")) {
name = getIntent().getStringExtra("name");
}
buttonCall.setOnClickListener(view -> {
callerName = editTextCallName.getText().toString().trim();
if (!callerName.isEmpty()) sendCallRequest();
});
buttonVideo.setOnClickListener(view -> {
videoEnabled = !videoEnabled;
callJsFunction("javascript:toggleVideo(\"" + videoEnabled + "\")");
if (videoEnabled)
buttonVideo.setText("Video Off");
else
buttonVideo.setText("Video On");
});
buttonAudio.setOnClickListener(view -> {
audioEnabled = !audioEnabled;
callJsFunction("javascript:toggleAudio(\"" + audioEnabled + "\")");
if (audioEnabled)
buttonAudio.setText("Mute");
else
buttonAudio.setText("Unmute");
});
buttonCamera.setOnClickListener(view -> {
if (camera.equals(CAMERA_FRONT)) camera = CAMERA_BACK;
else camera = CAMERA_FRONT;
switchCamera();
});
setupWebView();
}
//== Public ==//
public void onPeerConnected() {
isPeerConnected = true;
}
//== Private ==//
private void setupWebView() {
WebChromeClient client = new WebChromeClient() {
#Override
public void onPermissionRequest(PermissionRequest request) {
runOnUiThread(() -> request.grant(request.getResources()));
}
};
webView.setWebChromeClient(client);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
webView.addJavascriptInterface(new JsInterface(this), "Android");
loadVideoCall();
}
private void loadVideoCall() {
String filePath = "file:///android_asset/call.html";
webView.loadUrl(filePath);
WebViewClient client = new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
initializePeer();
}
};
webView.setWebViewClient(client);
}
private void initializePeer() {
uniqueID = getUniqueID();
callJsFunction("javascript:init(\"" + uniqueID + "\")");
usersRef.child(name).child("incoming").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
Log.d(TAG, "Received incoming call!!!");
onCallRequest(snapshot.getValue(String.class));
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
private void sendCallRequest() {
if (!isPeerConnected) {
Toast.makeText(this, "You're not connected to internet. Please try again.", Toast.LENGTH_SHORT).show();
return;
}
usersRef.child(callerName).child("incoming").setValue(name);
usersRef.child(callerName).child("isAvailable").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
boolean isAvailable = snapshot.getValue() != null? snapshot.getValue(boolean.class): false;
if (isAvailable) {
listenForConnectionID();
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
private void onCallRequest(String caller) {
if (caller == null) return;
String incomingMessage = caller + " is calling...";
textViewIncoming.setText(incomingMessage);
buttonAccept.setOnClickListener(view -> {
usersRef.child(name).child("connectionID").setValue(uniqueID);
usersRef.child(name).child("isAvailable").setValue(true);
layoutIncoming.setVisibility(GONE);
switchToCallControls();
});
buttonReject.setOnClickListener(view -> {
usersRef.child(name).child("incoming").setValue(null);
layoutIncoming.setVisibility(GONE);
});
layoutIncoming.setVisibility(View.VISIBLE);
}
private void listenForConnectionID() {
usersRef.child(callerName).child("connectionID").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.getValue() == null) return;
switchToCallControls();
callJsFunction("javascript:startCall(\"" + snapshot.getValue(String.class) + "\")");
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
private void switchToCallControls() {
layoutCall.setVisibility(GONE);
layoutCallControl.setVisibility(View.VISIBLE);
}
private void switchCamera() {
Log.d(TAG, "switchCamera: " + camera);
callJsFunction("javascript:switchCamera(\"" + camera + "\")");
}
private void callJsFunction(String functionString) {
webView.post(() -> webView.evaluateJavascript(functionString, value -> Log.d(TAG, value)));
}
private String getUniqueID() {
return UUID.randomUUID().toString();
}
}
call.html
<!DOCTYPE html>
<html>
<head>
<link href="./style.css" rel="stylesheet"/>
</head>
<body>
<script src="./peerjs.js"></script>
<video class="secondaryVideo" autoplay id="remoteVideo"></video>
<video class="primaryVideo" autoplay muted id="localVideo"></video>
<script src="./call.js"></script>
</body>
</html>
call.js
let localVideo = document.getElementById("localVideo")
let remoteVideo = document.getElementById("remoteVideo")
localVideo.style.opacity = 0
remoteVideo.style.opacity = 0
let peer
function init(userID) {
peer = new Peer(userID)
peer.on('open', () => {
Android.onPeerConnected();
})
listen()
}
let localStream
function listen() {
peer.on('call', (call) => {
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(function(mediaStream) {
localStream = mediaStream
localVideo.srcObject = localStream
localVideo.style.opacity = 1
call.answer(localStream)
call.on('stream', (remoteStream) => {
remoteVideo.srcObject = remoteStream
remoteVideo.style.opacity = 1
// Swap classes of localVideo and remoteVideo
localVideo.className = "secondaryVideo"
remoteVideo.className = "primaryVideo"
})
})
})
}
function startCall(otherUserID) {
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(function(mediaStream) {
localStream = mediaStream
localVideo.srcObject = localStream
localVideo.style.opacity = 1
const call = peer.call(otherUserID, localStream)
call.on('stream', (remoteStream) => {
remoteVideo.srcObject = remoteStream
remoteVideo.style.opacity = 1
// Swap classes of localVideo and remoteVideo
localVideo.className = "secondaryVideo"
remoteVideo.className = "primaryVideo"
})
})
}
function toggleVideo(b) {
if (b == "true") {
localStream.getVideoTracks()[0].enabled = true
} else {
localStream.getVideoTracks()[0].enabled = false
}
}
function toggleAudio(b) {
if (b == "true") {
localStream.getAudioTracks()[0].enabled = true
} else {
localStream.getAudioTracks()[0].enabled = false
}
}
let camIndex = 0
function switchCamera() {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
var cameras = []
devices.forEach(function(device) {
'videoinput' === device.kind && cameras.push(device.deviceId)
})
console.log(cameras.length)
if (camIndex == cameras.length - 1) {
camIndex = 0
} else {
camIndex = camIndex + 1
}
var constraints = {
video: {deviceId: {exact: cameras[camIndex]}},
audio: true
}
navigator.mediaDevices.getUserMedia(constraints).then(function(mediaStream) {
localStream = mediaStream
localVideo.srcObject = localStream
console.log("camera switched to camIndex " + camIndex) // Only triggered when camIndex = 0
})
})
}
I assume that camIndex = 1 is a back camera but it gives this error message in the logcat
D/CallActivity: switchCamera: environment
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
D/CallActivity: null
I/chromium: [INFO:CONSOLE(97)] "2", source: file:///android_asset/call.js (97)
E/libc: Access denied finding property "persist.vendor.camera.privapp.list"
W/ThreadPoolSingl: type=1400 audit(0.0:35101): avc: denied { read } for name="u:object_r:vendor_camera_prop:s0" dev="tmpfs" ino=19669 scontext=u:r:untrusted_app:s0:c161,c256,c512,c768 tcontext=u:object_r:vendor_camera_prop:s0 tclass=file permissive=0
E/cr_VideoCapture: cameraDevice encountered an error
I/chromium: [INFO:CONSOLE(0)] "Uncaught (in promise) NotReadableError: Could not start video source", source: file:///android_asset/call.html (0)
D/CallActivity: switchCamera: user
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
D/CallActivity: null
I/chromium: [INFO:CONSOLE(97)] "2", source: file:///android_asset/call.js (97)
D/: PlayerBase::stop() from IPlayer
D/AudioTrack: stop(398): called with 62088 frames delivered
I/chromium: [INFO:CONSOLE(115)] "camera switched to camIndex 0", source: file:///android_asset/call.js (115)
W/.testapp_webrt: Attempt to remove non-JNI local reference, dumping thread
W/AudioManager: Use of stream types is deprecated for operations other than volume control
W/AudioManager: See the documentation of requestAudioFocus() for what to use instead with android.media.AudioAttributes to qualify your playback use case
I have 3 private methods in the angular component which return arrays of objects.
I need to create one array of the object which will contain all 3. All of them has the same class
Here is it
export class TimelineItemDto {
id: any;
creatorAvatarUrl: string;
categoryName: string;
creatorName: string;
subcategoryName: string;
description: string;
type: string;
}
Here is the code of the component
export class HomeComponent implements OnInit {
constructor(private _router: Router, private http: HttpClient) { }
color: ThemePalette = 'primary';
classes: TimelineItemDto[] = [];
requests: TimelineItemDto[] = [];
courses: TimelineItemDto[] = [];
timelineItems: TimelineItemDto[] = [];
checked = false;
disabled = false;
ngOnInit(): void {
const token = localStorage.getItem('jwt');
if (!token) {
this._router.navigate(['/main/login']);
}
this.getTimelineItems();
}
getCourses(): any {
return this.http
.get(environment.baseUrl + '/Course/GetCourses')
.subscribe((data: TimelineItemDto[]) => {
return data;
});
}
getClasses(): any {
return this.http
.get(environment.baseUrl + '/Class/GetClasses')
.subscribe((data: TimelineItemDto[]) => {
return data;
});
}
getRequest(): any {
return this.http
.get(environment.baseUrl + '/Requests/GetRequests')
.subscribe((data: TimelineItemDto[]) => {
return data;
});
}
getTimelineItems(): any {
var courses = this.getCourses();
var classes = this.getClasses();
var requests = this.getRequest();
this.timelineItems = [...classes, ...courses, ...requests];
console.log(this.timelineItems);
}
}
At this row this.timelineItems = [...classes, ...courses, ...requests]; I have this error
core.js:4197 ERROR TypeError: classes is not iterable
How I can fix this?
The Problem
Consider the code below
getCourses(): any {
return this.http
.get(environment.baseUrl + '/Course/GetCourses')
.subscribe((data: TimelineItemDto[]) => {
return data;
});
}
The above code calls .get() method then calls .subscription() method. This indicates that this method actually returns a subscription and NOT an Observable. As the error indicates you are trying to iterate over these subscription hence the error
Solution
To solve this, there are various ways, my approach will be as below
get classes as Observable
get requests as Observable
get courses as Observable
combine these 3 Observables to one Observable
subscribe to the new Observable
See Below code
constructor(private _router: Router, private http: HttpClient) {}
color: ThemePalette = "primary";
timelineItems: TimelineItemDto[] = []
getCourses = () =>
this.http.get<TimelineItemDto[]>(
environment.baseUrl + "/Course/GetCourses"
);
getClasses = () =>
this.http.get<TimelineItemDto[]>(environment.baseUrl + "/Class/GetClasses");
getRequest = () =>
this.http.get<TimelineItemDto[]>(
environment.baseUrl + "/Requests/GetRequests"
);
classes$: Observable<TimelineItemDto[]> = this.getClasses();
requests$: Observable<TimelineItemDto[]> = this.getRequest();
courses$: Observable<TimelineItemDto[]> = this.getCourses();
timelineItems$: Observable<TimelineItemDto[]> = combineLatest([
this.classes$,
this.courses$,
this.requests$
]).pipe(
map(([classes, courses, requests]) => [...classes, ...courses, ...requests])
);
checked = false;
disabled = false;
ngOnInit(): void {
const token = localStorage.getItem("jwt");
if (!token) {
this._router.navigate(["/main/login"]);
}
this.getTimelineItems();
}
getTimelineItems(): any {
this.timelineItems$.subscribe({
next: (items) => this.timelineItems = items
})
}
See this solution on stackblitz
That isn't how asynchronous data works. Please refer here for more info on async data.
In short, you need to wait till the data is emitted by the source. In this specific case, you need to wait for the RxJS observables emit the values before trying to assign them. And seeing that you need to subscribe to multiple observables, you could use RxJS forkJoin function to trigger the requests in parallel
export class HomeComponent implements OnInit {
constructor(private _router: Router, private http: HttpClient) { }
color: ThemePalette = 'primary';
classes: TimelineItemDto[] = [];
requests: TimelineItemDto[] = [];
courses: TimelineItemDto[] = [];
timelineItems: TimelineItemDto[] = [];
checked = false;
disabled = false;
ngOnInit(): void {
const token = localStorage.getItem('jwt');
if (!token) {
this._router.navigate(['/main/login']);
}
this.getTimelineItems();
}
getTimelineItems(): any {
forkJoin(
<Observable<TimelineItemDto[]>>this.http.get(environment.baseUrl + '/Class/GetClasses'),
<Observable<TimelineItemDto[]>>this.http.get(environment.baseUrl + '/Course/GetCourses'),
<Observable<TimelineItemDto[]>>this.http.get(environment.baseUrl + '/Requests/GetRequests')
).subscribe({
next: ([classes, courses, requests]) => {
this.classes = classes;
this.courses = courses;
this.requests = requests;
this.timelineItems = [...classes, ...courses, ...requests];
console.log(this.timelineItems);
},
error: error => {
console.log('handle error');
}
});
}
}
Please go through the link above. The variable this.timelineItems might still be empty when you try to access it outside the subscription as it might not yet be assigned the values.
In other words, the this.timelineItems would only be properly accessible inside the subscription.
when trying to cancel upload by unsubscribing what actually happen that i unsubscribe to upload progress but that actual upload is not cancelled and keep uploading to the server.
upload.components.ts
import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '#angular/core';
import { Subject, Subscription, Observable } from 'rxjs';
import { HttpEventType } from '#angular/common/http';
import { UploadService } from '../../../services';
import { takeUntil } from 'rxjs/operators';
#Component({
selector: 'app-image-upload-item',
templateUrl: './image-upload-item.component.html',
styleUrls: ['./image-upload-item.component.scss']
})
export class ImageUploadItemComponent implements OnInit, OnDestroy {
#Input() index: any;
#Output() uploadSuccess: EventEmitter<any>;
#Output() uploadCanceled: EventEmitter<any>;
public localimageURL: string;
public uploadProgress: number;
public isUploadCompleted: boolean;
public uploadImageObservable: Subscription;
public isReadyForUpload: boolean;
public isUploading: boolean;
public progressMode: string;
public readonly unique: string = Math.floor((Math.random() *
100)).toString();
public readonly imagePreviewID = 'imagePreview' + this.unique;
_file: any;
#Input() public set file(value: any) {
const reader = new FileReader();
reader.onload = (e: any) => {
this.localimageURL = e.target.result;
};
this._file = value;
reader.readAsDataURL(this._file);
console.log(this._file);
}
constructor(private uploadService: UploadService) {
this.uploadProgress = 0;
this.isUploading = false;
this.localimageURL = '';
this.isUploadCompleted = false;
this.uploadSuccess = new EventEmitter<any>();
this.uploadCanceled = new EventEmitter<any>();
this.progressMode = 'indeterminate';
}
ngOnInit() {
this.uploadImageToServer(this._file);
// setTimeout(() => {
// console.log('im in set time out unsubscripting',
this.uploadImageObservable);
// this.uploadImageObservable.forEach(subscription => {
// subscription.unsubscribe();
// });
// }, 100);
}
ngOnDestroy() {
console.log('component destroyed');
this.uploadImageObservable.unsubscribe();
}
public clearUploadButtonClicked() {
// if (this.uploadImageObservable !== undefined) {
// console.log('image observable is defined');
// this.uploadImageObservable.unsubscribe();
// console.log(this.uploadImageObservable.closed);
// }
// this.uploadImageObservable.unsubscribe();
this._file = '';
this.uploadCanceled.emit({ index: this.index, uploaded: false });
}
public get showUploadProgress(): boolean {
return this.uploadProgress !== 0;
}
public uploadImageToServer(file) {
this.isUploading = true;
const progress = new Subject<number>();
progress.subscribe(value => {
this.uploadProgress = value;
});
this.uploadImageObservable = this.uploadService.uploadImage(file)
.subscribe(result => {
const type = result.type;
const data = result.data;
console.log(result);
if (type === HttpEventType.UploadProgress) {
const percentDone = Math.round(100 * data.loaded / data.total);
progress.next(percentDone);
if (percentDone === 100) {
this.progressMode = 'indeterminate';
}
} else if (type === HttpEventType.Response) {
if (data) {
progress.complete();
this.progressMode = 'determinate';
this.isReadyForUpload = false;
this.isUploadCompleted = true;
this.isUploading = false;
this.uploadSuccess.emit({ index: this.index, mediaItem: data });
}
}
}, errorEvent => {
});
}
}
upload.service.ts
public uploadImage(imageFile: File): Observable<any> {
const formData: FormData = new FormData();
if (imageFile !== undefined) {
formData.append('image', imageFile, imageFile.name);
const req = new HttpRequest('POST', environment.uploadImageEndPoint,
formData, {
reportProgress: true,
});
return new Observable<any>(observer => {
this.httpClient.request<any>(req).subscribe(event => {
if (event.type === HttpEventType.Response) {
const responseBody = event.body;
if (responseBody) {
this.alertService.success(responseBody.message);
observer.next({ type: event.type, data: new
MediaItem(responseBody.mediaItem) });
}
} else if (event.type === HttpEventType.UploadProgress) {
observer.next({ type: event.type, data: { loaded: event.loaded, total:
event.total } });
} else {
observer.next(event);
}
}, errorEvent => {
if (errorEvent.status === 400) {
this.alertService.error(errorEvent.error['image']);
} else {
this.alertService.error('Server Error, Please try again later!');
}
observer.next(null);
});
});
}
}
how can i cancel upload request properly with observable unsubscribe
note i already tried pipe takeuntil() and nothing changed
What you'll want to do is return the result from the pipe function on the http request return observable. Right now you have multiple streams and the component's unsubscribe is only unsubscribing to the observable wrapping the http request observable (not connected).
You'll want to do something like:
return this.httpClient.request<any>(req).pipe(
// use rxjs operators here
);
You'll then use rxjs operators (I've been doing this for a while, but I still highly reference this site) to perform any logic needed and reflect things like your errors and upload progress to the component calling the service. On the component side, you'll keep your subscribe/unsubscribe logic.
For instance, you can use the switchMap operator to transform what is returning to the component from the http request observable and specify the value to return to the component, and catchError to react to any errors accordingly.
return this.httpClient.request<any>(req).pipe(
switchMap(event => {
if (event.type === HttpEventType.Response) {
const responseBody = event.body;
if (responseBody) {
this.alertService.success(responseBody.message);
return { type: event.type, data: new MediaItem(responseBody.mediaItem) };
}
} else if (event.type === HttpEventType.UploadProgress) {
return { type: event.type, data: { loaded: event.loaded, total: event.total } };
}
return event;
}),
catchError(errorEvent => {
if (errorEvent.status === 400) {
this.alertService.error(errorEvent.error['image']);
} else {
this.alertService.error('Server Error, Please try again later!');
}
return of(<falsy or error value>);
}),
);
Alternatively you could model it a little more after this example by just returning the http function call from the service to the component and handling things in the subscribe there.
actually i found a way as follows
public uploadImage(imageFile: File): Observable<any> {
const formData: FormData = new FormData();
if (imageFile !== undefined) {
formData.append('image', imageFile, imageFile.name);
const req = new HttpRequest('POST', environment.uploadImageEndPoint, formData, {
reportProgress: true,
});
return this.httpClient.request<any>(req).pipe(
map((res: any) => {
return res;
}),
catchError(errorEvent => {
if (errorEvent.status === 400) {
this.alertService.error(errorEvent.error['image']);
} else {
this.alertService.error('Server Error, Please try again later!');
return Observable.throw(errorEvent);
}
return Observable.throw(errorEvent);
}));
}
}
For this challenge, you are going to build a mock comments section.
Design
We're going to focus on two aspects:
Users
Users come in 3 flavors, normal users, moderators, and admins. Normal users can only create new comments, and edit the their own
comments. Moderators have the added ability to delete comments (to
remove trolls), while admins have the ability to edit or delete any
comment.
Users can log in and out, and we track when they last logged in
Comments
Comments are simply a message, a timestamp, and the author.
Comments can also be a reply, so we'll store what the parent comment was.
Beneath is my code:
class Admin extends Moderator {
constructor(name) {
super(name);
}
canEdit(comment) {
return true;
}
}
class Comment {
constructor(author, message, repliedTo) {
this.createdAt = new Date();
this._author = author;
this._message = message;
this.repliedTo = repliedTo || null;
}
getMessage() {
return this._message;
}
setMessage(message) {
this._message = message;
}
getCreatedAt() {
return this.createdAt;
}
getAuthor() {
return this._author;
}
getRepliedTo() {
return this.repliedTo;
}
getString(comment) {
const authorName = comment.getAuthor().getName();
if (!comment.getRepliedTo()) return authorName;
return `${comment.getMessage()} by ${authorName} (replied to ${this.getString(comment.getRepliedTo())})`;
}
toString() {
const authorName = this.getAuthor().getName();
if (!this.getRepliedTo()) {
return `${this._message} by ${authorName}`;
}
return this.getString(this);
}
}
I get the error
The toString method should return the correct hierarchy (nested reply)
Although this supposed to be an assignment, the question was a bit technical and unclear; this's the proven solution
class User {
constructor(name) {
this._name = name;
this._loggedIn = false;
this._lastLoggedInAt = null;
}
isLoggedIn() {
return this._loggedIn;
}
getLastLoggedInAt() {
return this._lastLoggedInAt;
}
logIn() {
this._lastLoggedInAt = new Date();
this._loggedIn = true;
}
logOut() {
this._loggedIn = false
}
getName() {
return this._name;
}
setName(name) {
this._name = name;
}
canEdit(comment) {
if(comment._author._name === this._name) {
return true;
}
return false;
}
canDelete(comment) {
return false;
}
}
class Moderator extends User {
constructor(name) {
super(name);
}
canDelete(comment) {
return true;
}
}
class Admin extends Moderator {
constructor(name) {
super(name)
}
canEdit(comment) {
return true;
}
}
class Comment {
constructor(author = null, message, repliedTo = null) {
this._createdAt = new Date();
this._message = message;
this._repliedTo = repliedTo;
this._author = author;
}
getMessage() {
return this._message;
}
setMessage(message) {
this._message = message;
}
getCreatedAt() {
return this._createdAt;
}
getAuthor() {
return this._author;
}
getRepliedTo() {
return this._repliedTo;
}
toString() {
if(this._repliedTo === null) {
return this._message + " by " + this._author._name
}
return this._message + " by " + this._author._name + " (replied to " +
this._repliedTo._author._name + ")"
}
}
The error was because you were calling a getName() method on getAuthor method which wasn't available. You can get author name directly from Comment this._author._name.
I make use of JavaScript constructor coding style, to write this solution but it should not matter much as the solution does not need you to change your style. Observe that the fields (_author, _message, _repliedTo) are private, and private fields can only be accessed through public methods. And that is basically what I did here in the toString() method.
function Comment(author, message, repliedTo = null) {
var _author = author;
var _message = message;
var _repliedTo = repliedTo;
this.getAuthor = function() {
return _author;
};
this.getRepliedTo = function() {
return _repliedTo;
};
this.toString = function() {
return ((_repliedTo === null) ? message + " by " + _author.getName() : message + " by " + _author.getName() + " (replied to " + this.getRepliedTo().getAuthor().getName() + ")");
}
};
You can remove the getString() method...
toString()
{
return ((this._repliedTo === null) ? this._message + " by " +
this._author.getName() : this._message + " by " +
this._author.getName() + " (replied to " + this._repliedTo._author.getName() + ")");
}
class User {
function __construct($name) {
private $name;
private $loggedIn;
private $lastLoggedInAt;
$this->name = $name;
$this->loggedIn = false;
$this->lastLoggedInAt = null;
}
function isLoggedIn() {
return $this->loggedIn;
}
function getLastLoggedInAt() {
return $this->lastLoggedInAt;
}
function logIn() {
$this->lastLoggedInAt = new Date('Y-m-d H:i:s');
$this->loggedIn = true;
}
function logOut() {
$this->loggedIn = false;
}
function getName() {
return $this->name;
}
function setName($name) {
$this->name = $name;
}
function canEdit($comment) {
if($comment->author->name === $this->name) {
return true;
}
return false;
}
function canDelete($comment) {
return false;
}
}
class Moderator extends User {
function __construct($name) {
$this->name = $name;
}
function canDelete($comment) {
return true;
}
}
class Admin extends Moderator {
function constructor($name) {
$this->name = $name;
}
function canEdit($comment) {
return true;
}
}
class Comment {
function __construct($author = null, $message, $repliedTo = null) {
private $createdAt;
private $message;
private $repliedTo;
private $author;
$this->createdAt = new Date('Y-m-d H:i:s');
$this->message = $message;
$this->repliedTo = $repliedTo;
$this->author = $author;
}
function getMessage() {
return $this->message;
}
function setMessage($message) {
$this->message = $message;
}
function getCreatedAt() {
return $this->createdAt;
}
function getAuthor() {
return $this->author;
}
function getRepliedTo() {
return $this->repliedTo;
}
function __toString() {
if($this->repliedTo === null) {
return $this->message + " by " + $this->author->name;
}
return $this->message + " by " + $this->author->name + " (replied to " +
$this->repliedTo->author->name + ")";
}
}
Java version if anyone needs:
import java.util.Date;
public class Solution {
public static class User {
String name;
boolean loggedIn;
Date lastLoggedInAt;
public User(String name) {
this.name = name;
this.loggedIn = loggedIn;
this.lastLoggedInAt = lastLoggedInAt;
}
public boolean isLoggedIn() {
return this.loggedIn;
}
public Date getLastLoggedInAt() {
return this.lastLoggedInAt;
}
public void logIn() {
this.lastLoggedInAt = new Date();
this.loggedIn = true;
}
public void logOut() {
this.loggedIn = false;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public boolean canEdit(Comment comment) {
if(comment.getAuthor().name == this.name) {
return true;
}
return false;
}
public boolean canDelete(Comment comment) {
return false;
}
}
public static class Moderator extends User{
public Moderator(String name) {
super(name);
}
public boolean canDelete(Comment comment) {
return true;
}
}
public static class Admin extends Moderator{
public Admin(String name) {
super(name);
}
public boolean canEdit(Comment comment) {
return true;
}
}
public static class Comment {
User author;
//注意下面也要是author
String message;
Comment comment;
Date createdAt;
Comment repliedTo;
public Comment(User author, String message) {
this.author = author;
this.message = message;
}
public Comment(User author, String message, Comment repliedTo) {
this.author = author;
this.message = message;
this.repliedTo = repliedTo;
}
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public Date getCreatedAt() {
return this.createdAt;
}
public User getAuthor() {
return this.author;
}
public Comment getRepliedTo() {
return this.repliedTo;
}
public String toString() {
if(this.repliedTo == null) {
return this.message + " by " + this.author.getName();
}
return this.message + " by " + this.author.getName() + " (replied to " +
this.repliedTo.getAuthor().name + ")";
}
}
}
Answers provided above won't pass basic unit tests for this assignment. Here's an option I successfully submitted to complete the challenge:
export class User {
constructor(name) {
this._name = name;
this._lastLoginDate = null;
this._loggedIn = false;
}
isLoggedIn() {
return this._loggedIn;
}
getLastLoggedInAt() {
return this._lastLoginDate;
}
logIn() {
this._lastLoginDate = new Date();
return Promise.resolve('Success').then(() => {
this._loggedIn = true;
});
}
logOut() {
this._loggedIn = false;
}
getName() {
return this._name;
}
setName(name) {
this._name = name;
}
canEdit(comment) {
if (comment.getAuthor().getName() === this.getName()) {
return true;
}
return false;
}
canDelete(comment) {
return false;
}
}
export class Moderator extends User {
constructor(name) {
super(name);
}
canDelete(comment) {
return true;
}
}
export class Admin extends Moderator {
constructor(name) {
super(name);
}
canEdit(comment) {
return true;
}
}
export class Comment {
constructor(author, message, repliedTo = null) {
this._author = author;
this._message = message;
this._repliedTo = repliedTo || null;
this._createdAt = new Date();
}
getMessage() {
return this._message;
}
setMessage(message) {
this._message = message;
}
getCreatedAt() {
return this._createdAt;
}
getAuthor() {
return this._author;
}
getRepliedTo() {
return this._repliedTo;
}
toString() {
return this.getRepliedTo() === null
? `${this.getMessage()} by ${this.getAuthor().getName()}`
: `${this.getMessage()} by ${this.getAuthor().getName()} (replied to ${this.getRepliedTo()
.getAuthor()
.getName()})`;
}
}
If you want to solve this thing using typescript:
export class User {
private _name: string;
private _loggedIn: boolean;
private _lastLoggedInAt: Date | null;
constructor(name: string) {
this._name = name;
this._loggedIn = false;
this._lastLoggedInAt = null;
}
isLoggedIn(): boolean {
return this._loggedIn;
}
getLastLoggedInAt(): Date | null {
return this._lastLoggedInAt;
}
async logIn(): Promise<void> {
this._lastLoggedInAt = new Date();
await Promise.resolve("suceess");
this._loggedIn = true;
}
logOut(): void {
this._loggedIn = false;
}
getName(): string {
return this._name;
}
setName(name: string): void {
this._name = name;
}
canEdit(comment: Comment): boolean {
if (comment.getAuthor().getName() === this._name) {
return true;
}
return false;
}
canDelete(comment: Comment): boolean {
return false;
}
}
export class Moderator extends User {
constructor(name: string) {
super(name);
}
canDelete(_comment: Comment): boolean {
return true;
}
}
export class Admin extends Moderator {
constructor(name: string) {
super(name);
}
canEdit(_comment: Comment): boolean {
return true;
}
}
export class Comment {
private _author: User;
private _message: string;
private _repliedTo?: Comment | null;
private _createdAt: Date;
constructor(author: User, message: string, repliedTo?: Comment) {
this._author = author;
this._message = message;
this._repliedTo = repliedTo;
this._createdAt = new Date();
}
getMessage(): string {
return this._message;
}
setMessage(message: string): void {
this._message = message;
}
getCreatedAt(): Date {
return this._createdAt;
}
getAuthor(): User {
return this._author;
}
getRepliedTo(): Comment | null {
if (this._repliedTo) {
return this._repliedTo;
}
return null;
}
toString(): string {
if (this.getRepliedTo()) {
return `${this.getMessage()} by ${this.getAuthor().getName()} (replied to ${this._repliedTo
?.getAuthor()
.getName()})`;
}
return `${this.getMessage()} by ${this.getAuthor().getName()}`;
}
}
and few unit tests using Jest
describe('Normal user tests', function() {
it('should return name of the User', () => {
const user = new User("User 1");
expect(user.getName()).toEqual('User 1');
});
it('shoud return isLoggedIn as true when logIn method is called' , () => {
const user = new User("User 1");
user.logIn().then(() => {
expect(user.isLoggedIn()).toBeTruthy();
}).catch((error) => {
expect(user.isLoggedIn()).toBe(false);
});;
});
it('shoud return _lastLoggedInAt as the date when logIn method is called' , () => {
const user = new User("User 1");
user.logIn().then(() => {
expect(user.getLastLoggedInAt()).toBe(new Date());
})
});
it('shoud return _loggedIn as false when logOut method is called', () => {
const user = new User("User 1");
user.logOut();
expect(user.isLoggedIn()).toBe(false);
});
it('shoud setName of the user' , () => {
const user = new User("User");
user.setName("User 2");
expect(user.getName()).toEqual('User 2');
});
});
describe('Moderator user tests', function() {
it('should return name of the User', () => {
const moderator = new Moderator("Moderator 1");
const message = "Hello there"
const comment = new Comment (moderator, message);
expect(moderator.canDelete(comment)).toBeTruthy();
});
});
describe('Admin user tests', function() {
it('should return name of the User', () => {
const admin = new Admin("Admin 1");
const message = "Hello there"
const comment = new Comment (admin, message);
expect(admin.canEdit(comment)).toBeTruthy();
});
});
describe('Comment tests', function() {
it('should return message of the author', () => {
const user = new User("User 1");
const message = "Hi! This is my message."
const comment = new Comment (user, message)
expect(comment.getMessage()).toEqual(message);
});
it('should set new message', () => {
const user = new User("User 1");
const message = "Hi! This is my message."
const newMessage = "Hi! This is new message."
const comment = new Comment (user, message);
comment.setMessage(newMessage)
expect(comment.getMessage()).toEqual(newMessage);
});
it('should return null when replied to does not exists', () => {
const user = new User("User 1");
const message = "Hi! This is my message."
const comment = new Comment (user, message);
expect(comment.getRepliedTo()).toBe(null);
});
it('should return repliedTo when replied to exists', () => {
const user = new User("User 1");
const message = "Hi! This is my message."
const repliedToUser = new User("User 2");
const repliedToMessage = "Hi! This is my replied message.";
const repliedToComment = new Comment (repliedToUser, repliedToMessage);
const comment = new Comment (user, message, repliedToComment);
expect(comment.getRepliedTo()).toBe(repliedToComment);
});
it('should return repliedTo string message when replied to exists', () => {
const user = new User("User1");
const message = "Hello there"
const repliedToUser = new User("User2");
const repliedToMessage = "Hi! This is my replied message.";
const repliedToComment = new Comment (repliedToUser, repliedToMessage);
const comment = new Comment (user, message, repliedToComment);
expect(comment.toString()).toBe("Hello there by User1 (replied to User2)");
});
it('should return comment string message when replied to does not exists', () => {
const user = new User("User1");
const message = "Hello there"
const comment = new Comment (user, message);
expect(comment.toString()).toBe("Hello there by User1");
});
});
...It does in the documentation P:
I import * as firebase from "firebase"; at the top of the file. The uid-fetching function works. It doesn't like the syntax of the for-loop for some reason...
I have also tried the syntax: for (DataSnapshot child : parent.getChildren()) { } and then the compiler tells me a semicolon is expected in the line where the for-loop starts.
getMessages() {
return new Promise(function (resolve) {
return firebase.auth().onAuthStateChanged(function (user) {
if (user) {
resolve(user.uid);
}
});
}).then((result) => {
return firebase.database().ref('mailboxes/' + result).once('value').then((snapshot) => {
let messageArray;
for (let snap of snapshot.getChildren()) {
messageArray.push(snap.val());
console.log('snapshot key:' + snap.key);
console.log('snapshot val:' + snap.val());
};
return messageArray;
});
});
}
You need to use a subscription to watch for the changes. Use AngularFire to watch for when they are logged in and get the UID (assuming you are using the Authentication login in Firebase so that all data is saved using the UID as the tree path
import { AngularFirestore } from 'angularfire2/firestore';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import { switchMap, map } from 'rxjs/operators';
import { Observable, pipe } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import firebase as firebase from 'firebase/app';
private myOAuthSubscription: Subscription;
private myDatasubscription: Subscription;
public userloggedin:boolean = false;
public uid:string = '';
public this.items:any = [];
constructor(
public _DB: AngularFireDatabase,
public _afAuth: AngularFireAuth,
) {
try {
this.myOAuthSubscription = this._afAuth.authState.subscribe(user => {
if (user && user.uid) {
console.log('loggedin = true');
this.userloggedin = true;
this.uid = String(user.uid);
this.funDoDB():
} else {
console.log('loggedin = false');
this.userloggedin = true;
this.uid = '';
}
});
} catch (e) {
console.error("fbData_subscription", e);
}
}
ngOnDestroy() {
this.myOAuthSubscription.unsubscribe();
this.myDatasubscription.unsubscribe();
}
private funDoDB(){
if(this.userloggedin == true){
try {
//subscription using AngulaFire
this.myDatasubscription = this._DB.list('mailboxes/' + this.uid).snapshotChanges().pipe(map(actions => {
return actions.map(action => ({ key: action.key, val: action.payload.val() }));
}))
.subscribe(items => {
this.items = [];
this.items = items.map(item => item);
console.log("db results",this.items);
var icount=0;
for (let i in this.items) {
console.log("key",this.items[i].key);
console.log("val",this.items[i].val);
console.log("----------------------------------);
//checking if something exists
if (this.items[i].key == 'SomeNodePath') {
var log = this.items[i].val;
}
}
} catch (e) {
console.error(e);
}
});
}
}
npm install --save angularfire2 firebase
npm install -D rxjs#6.2.2 rxjs-compat#6.2.2