I created a dialog using angular in order to collect a information and save it on my back-end.
The problem is when i will send it to my back-end, using my post method, the variable of the coment stay undefined. The variable in question is:
val: " "
this is my dialog ts file:
import { Component, OnInit, Inject } from '#angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '#angular/material/dialog'; import { IstManagementService } from './../../core/services/ist-management.service'; import { ReasonpopupService } from 'ClientApp/app/core/services/reasonpopup.service';
#Component({
selector: 'app-reasonpopup', templateUrl: './reasonpopup.component.html', styleUrls: ['./reasonpopup.component.css'] }) export class ReasonpopupComponent implements OnInit {
val : " "
/* reason2 : string = this.reason */
onSubmit() { this.MatDialogRef.close(this.val); }
getValue(val:string){
console.log(val)
}
constructor(
private istManagementService: IstManagementService,
public MatDialogRef: MatDialogRef<ReasonpopupComponent>,
#Inject(MAT_DIALOG_DATA) public data: any,
private shared:ReasonpopupService,
) { }
ngOnInit(): void {
this.shared.setMessage(this.val) }
reason :string closeDialog() {
this.MatDialogRef.close(false); }
}
my html file:
<div>
<div class="content-container">
<mat-icon id="close-icon" (click)="closeDialog()">close</mat-icon>
<span class="content-span full-width">{{ data.message }}</span>
</div>
<form #userForm="ngForm">
<div class="input-reason">
<mat-form-field class="example-full-width" appearance="fill" [style.width.px]=420 style="padding-bottom: 100px;" >
<mat-label>Leave a comment</mat-label>
<textarea
[(ngModel)]="val"
type="text"
ngModel class="form-control"
required
#text
minlength="3"
class="form-control"
name="tx"
matInput
placeholder="please describe the reason"
></textarea>
<span *ngIf="text.invalid && text.touched" class="error">input the reason</span>
</mat-form-field>
</div>
<button mat-raised-button id="no-button" [mat-dialog-close]="false">NO</button>
<button
mat-raised-button
[disabled]="userForm.invalid"
id="yes-button"
(click)="onSubmit()"
(click)="getValue(text.value)"
[mat-dialog-close]="data.text"
cdkFocusInitial
>
YES
</button>
</form>
</div>
The method where i pass my variable as argument on the other component that have the post method
saveRow() {
let addRowsRequest = {
IstValues: this.dataSource.data.filter(r => r.editing)
};
console.log(this.val)
this.istManagementService.postRecord(this.inputTable, addRowsRequest, this.val).subscribe(
(res: any) => {
console.log(this.dataSource.data.filter(r => r.editing));
this.dataSource.data.push(this.dataSource.data.filter(r => r.editing));
this.dataSource._updateChangeSubscription();
}
)
}
My setter and getter service that i created to share the variable between the components
mport { ReasonpopupComponent } from './../../tasks/reasonpopup/reasonpopup.component';
import { Injectable } from '#angular/core';
import { MatDialog } from '#angular/material/dialog';
#Injectable({
providedIn: 'root'
})
export class ReasonpopupService {
val:string
constructor(private messageDialog: MatDialog) { }
openReasonDialog(msg: string) {
return this.messageDialog.open(ReasonpopupComponent, {
width: '570px',
panelClass: 'confirm-dialog-container',
disableClose: true,
data: { message: msg }
})
}
setMessage(data: string){
this.val=data
console.log(this.val)
}
getMessage(){
return this.val
}
}
and finally, my service that contain all the CRUD methods
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { ISTGenericResponse } from '../../pages/ist-management/ist-generic-response';
import { environment } from 'ClientApp/environments/environment';
#Injectable({
providedIn: 'root'
})
export class IstManagementService {
constructor(private httpClient: HttpClient) { }
public getGenericStaticTablesFiltered(inputTable: string, inputKey: string, inputValue: string, inputComparison: string): Observable<ISTGenericResponse> {
var filter = "";
if (inputKey && inputValue) {
filter = "?key=" + inputKey + "&value=" + inputValue + "&comparison=" + inputComparison;
return this.httpClient.get<ISTGenericResponse>(environment.apiRoot + "/StaticTable/Filter/" + inputTable + filter);
}
else {
return this.httpClient.get<ISTGenericResponse>(environment.apiRoot + "/StaticTable/" + inputTable);
}
}
message:string
setMessage(data: string){
this.message=data
}
getMessage(){
return this.getMessage
}
postRecord(inputTable: string, addRecord: any, message:any ) {
return this.httpClient.post(environment.apiRoot + "/StaticTable/Add/" + inputTable, addRecord, message);
}
deleteRecord(inputTable: string, deleteRecord: any) {
const headers = new HttpHeaders({ 'Content-Type': 'application/json; charset=utf-8' });
return this.httpClient.request('delete', environment.apiRoot + "/StaticTable/Delete/" + inputTable, { body: deleteRecord, headers: headers });
}
editRecord(inputTable: string, editRecord: any): Observable<any> {
const headers = new HttpHeaders({ 'Content-Type': 'application/json; charset=utf-8' });
return this.httpClient.request('put', environment.apiRoot + "/StaticTable/Update/" + inputTable, { body: editRecord, headers: headers, });
}
}
Thank you in advanced
according to this code, you are calling this.shared.setMessage(this.val) inside the ngOnInit() method of ReasonpopupComponent
which will always be undefined because ngOnInit() is only called on the initialization of the component before the user inputs any data.
what you need to do is to move this.shared.setMessage(this.val) inside onSubmit() method, so in the end it looks like this
export class ReasonPopupComponent implements OnInit {
val = '';
constructor(
private istManagementService: IstManagementService,
public MatDialogRef: MatDialogRef<ReasonPopupComponent>,
#Inject(MAT_DIALOG_DATA) public data: any,
private shared:ReasonpopupService,
) { }
ngOnInit(): void {
}
onSubmit() {
this.shared.setMessage(this.val);
this.MatDialogRef.close(this.val);
}
closeDialog() {
this.MatDialogRef.close(false);
}
}
what would be even better, if your "other component" is the one opening the pop-up, you can make use of angular material dialog subscriber. as you see in onSubmit() method this.matDialogRef.close(this.val)is already called with the value. all you need to do is to subscribe to it on the "other component" like so.
dialogRef.afterClosed().subscribe(result => {
console.log(result)
});
Related
I made a custom Toast component, which is called at each query error by a notification service (called by an http interceptor).
Here is what my custom toast component looks like :
toast.component.html
<div class="wrapper mx-auto shadow-max" *ngIf="toasts.length">
<div [#toast] *ngFor="let toast of toasts" class="mw-100" [ngClass]="getToastClass(toast)" role="alert">
<div class="toast-header">
{{ 'notify.header.' + getToastType(toast) | translate }}
<button
type="button"
class="close"
data-dismiss="alert"
aria-label="Close"
(click)="removeToast(toast)"
>
<i class="icon" aria-hidden="true">close</i>
</button>
</div>
<div class="toast-body">
<p>{{ 'notify.body.' + toast.message | translate }}</p>
<p>{{ toast.detailsMessage }}</p>
</div>
</div>
</div>
toast.component.ts
import { animate, style, transition, trigger } from '#angular/animations';
import { Component, Input, OnInit } from '#angular/core';
import { ToastType } from '../../enums/toast-type.enum';
import { Toast } from '../../model/toast.model';
import { Utils } from '../../utils/utils';
import { ToasterService } from './toaster.service';
#Component({
selector: 'bigdata-toaster',
moduleId: module.id,
templateUrl: './toast.component.html',
//styleUrls: ['./toast.component.scss'],
animations: [
trigger('toast', [
transition('void => *', [style({ transform: 'scale3d(.3, .3, .3)' }), animate(200)]),
transition('* => void', [animate(200, style({ transform: 'scale3d(.0, .0, .0)' }))])
])
]
})
export class ToastComponent implements OnInit {
#Input() id: string;
toasts: Toast[] = [];
constructor(private readonly toastService: ToasterService) {}
ngOnInit() {
this.toastService.getToast(this.id).subscribe((toast: Toast) => {
if (!toast.message) {
// clear alerts when an empty alert is received
this.toasts = [];
return;
}
// add alert to array
this.toasts.push(toast);
setTimeout(() => this.removeToast(toast), 5000);
});
}
removeToast(toast: Toast) {
this.toasts = this.toasts.filter(x => x !== toast);
}
getToastType(toast: Toast) {
return Utils.enumToString(ToastType, toast.type).toLocaleLowerCase();
}
getToastClass(toast: Toast) {
if (!toast) {
return;
}
switch (toast.type) {
case ToastType.Success:
return 'toast toast-success fade show';
case ToastType.Error:
return 'toast toast-danger fade show';
case ToastType.Info:
return 'toast toast-info fade show';
case ToastType.Warning:
return 'toast toast-warning fade show';
}
}
}
toaster.service.ts
import { Injectable } from '#angular/core';
import { NavigationStart, Router } from '#angular/router';
import { Observable, Subject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { ToastType } from '../../enums/toast-type.enum';
import { Toast } from '../../model/toast.model';
#Injectable({
providedIn: 'root'
})
export class ToasterService {
private readonly subject = new Subject<Toast>();
private keepAfterRouteChange = false;
constructor(private readonly router: Router) {
// clear alert messages on route change unless 'keepAfterRouteChange' flag is true
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
if (this.keepAfterRouteChange) {
// only keep for a single route change
this.keepAfterRouteChange = false;
} else {
this.clear();
}
}
});
}
getToast(toastId?: string): Observable<any> {
return this.subject.asObservable().pipe(filter((x: Toast) => x && x.toastId === toastId));
}
success(message: string, detailsMessage?: string) {
this.toast(new Toast({ message, type: ToastType.Success, detailsMessage }));
}
error(message: string, detailsMessage?: string) {
this.toast(new Toast({ message, type: ToastType.Error, detailsMessage }));
}
info(message: string, detailsMessage?: string) {
this.toast(new Toast({ message, type: ToastType.Info, detailsMessage }));
}
warn(message: string, detailsMessage?: string) {
this.toast(new Toast({ message, type: ToastType.Warning, detailsMessage }));
}
toast(toast: Toast) {
this.keepAfterRouteChange = toast.keepAfterRouteChange;
this.subject.next(toast);
}
clear(toastId?: string) {
this.subject.next(new Toast({ toastId }));
}
}
My notification service looks like this :
import {HttpErrorResponse} from '#angular/common/http';
import { Injectable } from '#angular/core';
// import { ActiveToast, IndividualConfig, ToastrService } from 'ngx-toastr';
import { ActiveToast, IndividualConfig } from 'ngx-toastr';
import {ToastComponent} from '../../components/toast/toast.component';
import {ToasterService} from '../../components/toaster/toaster.service';
/**
* Toaster Notification Component
*/
#Injectable()
export class NotificationService {
private readonly DEFAULT_TOASTR_SETTINGS: Partial<IndividualConfig> = {
closeButton: true,
positionClass: 'toast-top-right',
toastComponent: ToastComponent, // custom toast
toastClass: 'toast',
progressBar: true,
tapToDismiss: false,
disableTimeOut: true,
enableHtml: true
};
constructor(private toastrService: ToasterService) {
}
showErrorNotification(err: HttpErrorResponse) {
this.toastrService.success('approve_success');
}
}
I declared my notification service in my app.module.ts :
providers: [
NotificationService,
{
provide: HTTP_INTERCEPTORS, useClass: AppHttpInterceptor, multi: true
}
]
And my HTTP interceptor :
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '#angular/common/http';
import {Injectable} from '#angular/core';
import {ToastrService} from 'ngx-toastr';
import {Observable, of} from 'rxjs';
import {catchError} from 'rxjs/operators';
import {NotificationService} from './shared/services/notifications/notifications.service';
#Injectable()
export class AppHttpInterceptor implements HttpInterceptor {
// constructor(public toastr: ToastrService) {
// }
constructor(public notif: NotificationService) {
}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err: any) => {
if (err instanceof HttpErrorResponse) {
// this.notif.showNotification(MESSAGE_TYPE.Error, err.message, err.status.toString(), {disableTimeOut: true});
this.notif.showErrorNotification(err);
}
return of(err);
}));
}
}
My HTTP interceptor is working, I can log something.
But I don't have anything on my UI. Anything in the console too. I've been widely inspired from another code, so I think I get something wrong.
Please what am I doing wrong here ? Thanks.
I had a similar problem using primeng toast sent in ngOnInit won't appear, since the component is not rendered yet.
Tryp to wrap the toast request within a setTimeout call, this helped in my case.
Example:
success(message: string, detailsMessage?: string) {
setTimeout( () => this.toast(new Toast({ message, type: ToastType.Success, detailsMessage }));
}
See https://stackblitz.com/edit/angular-primeng-toast-oninit for details.
The reason for this workaround is explained in Call setTimeout without delay
I have a custom error handler service which gets notified whenever there is an error in the application, now i want to notify a component about the error so that the component will show a error dialog to the user, i have tried event emitter, observer but nothing is notifying the component...
here is my service...
#Injectable()
export class ErrorHandlerService implements ErrorHandler {
public apiError: Subject<any> = new BehaviorSubject(false);
apiError$ = this.apiError.asObservable();
constructor(private errorLogService: ErrorLogService
) {}
handleError(error) {
this.apiError.next(error);
console.log("ERROR = " + error);
};}
And the component...
#Component({
selector: 'app-error-log',
templateUrl: './error-log.component.html',
styleUrls: ['./error-log.component.scss'],
providers: [ErrorLogService]
})
export class ErrorLogComponent implements OnInit {
constructor(
private errorHandlerService: ErrorHandlerService
) {
this.errorHandlerService.apiError$.subscribe(data => {
alert("error in component = " + data);
});
}
onNoClick(): void {
// this.dialogRef.close();
}
ngOnInit() {
this.errorHandlerService.apiError$.subscribe(data => {
alert("error in component = " + data);
});
}
}
Method with Following format can give an output of service execution is successful or not. Hope the following code will help you
//Code in service.ts
#Injectable()
export class ErrorHandlerService implements ErrorHandler {
public apiError: Subject<any> = new BehaviorSubject(false);
apiError$ = this.apiError.asObservable();
constructor(private errorLogService: ErrorLogService
) { }
private handleError(error) {
return Observable.throw(error.json().msg || 'Server error');
}
_forgotPassword(userId, passwords, isResetPwd) {
return this.http.post(this.forgotPasswordUrl,
{
userId: userId,
passwords: passwords,
isResetPwd: isResetPwd
})
.map(res => res.json())
.catch(this.handleError);
}
}
//Code in component class file
this.errorHandlerService._forgotPassword(userId, passwords, isResetPwd).
subscribe(data => {
// Code here.....
});
This is how i have fixed it...
app.component.html
<error-log></error-log>
error-log.component
import { Component, OnInit } from '#angular/core';
import { ErrorLogService } from './error-log.service';
#Component({
selector: 'error-log',
templateUrl: './error-log.component.html',
styleUrls: ['./error-log.component.scss']
})
export class ErrorLogComponent implements OnInit {
constructor(private errorLogService: ErrorLogService) { }
ngOnInit() {
this.errorLogService.apiEvent.subscribe(data =>{
alert("error in errorlog component through errorLogService event emitter = " + data);
})
this.errorLogService.apiError$.subscribe(data =>{
alert("error in errorlog component errorLogService = " + data);
})
}
}
error-handler.service
import { Injectable, ErrorHandler} from '#angular/core';
import { ErrorLogService } from './error-log.service';
#Injectable()
export class ErrorHandlerService implements ErrorHandler {
constructor(private errorLogService: ErrorLogService
) {}
handleError(error) {
this.errorLogService.setError(error);
};}
error-log.service
import { Injectable, ErrorHandler, EventEmitter, Output} from '#angular/core';
import { EventListener } from '#angular/core/src/debug/debug_node';
import { ReplaySubject} from 'rxjs/Rx';
#Injectable()
export class ErrorLogService {
public apiError: ReplaySubject<any> = new ReplaySubject();
public apiEvent:EventEmitter<any> = new EventEmitter();
public apiError$ = this.apiError.asObservable();
constructor() {
// super();
}
setError(error){
this.apiEvent.emit(error);
this.apiError.next(error);
// super.handleError(error);
console.log("ERROR = " + error);
} }
No idea why i cant raise an event directly from error-handler.service
if you initialize the component after the service recieve the errors then he can only emit errors that's being recieved after his init.
user ReplaySubject to emit all previous errors aswell
#Injectable()
export class ErrorHandlerService implements ErrorHandler {
public apiError: ReplaySubject<any> = new ReplaySubject();
apiError$ = this.apiError.asObservable();
constructor(private errorLogService: ErrorLogService
) {}
handleError(error) {
this.apiError.next(error);
console.log("ERROR = " + error);
};}
I am building an Angular 4 app that requires the BriteVerify email validation on form fields in several components. I am trying to implement this validation as a custom async validator that I can use with reactive forms. Currently, I can get the API response, but the control status is stuck in pending state. I get no errors so I am a bit confused. Please tell me what I am doing wrong. Here is my code.
Component
import { Component,
OnInit } from '#angular/core';
import { FormBuilder,
FormGroup,
FormControl,
Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EmailValidationService } from '../services/email-validation.service';
import { CustomValidators } from '../utilities/custom-validators/custom-validators';
#Component({
templateUrl: './email-form.component.html',
styleUrls: ['./email-form.component.sass']
})
export class EmailFormComponent implements OnInit {
public emailForm: FormGroup;
public formSubmitted: Boolean;
public emailSent: Boolean;
constructor(
private router: Router,
private builder: FormBuilder,
private service: EmailValidationService
) { }
ngOnInit() {
this.formSubmitted = false;
this.emailForm = this.builder.group({
email: [ '', [ Validators.required ], [ CustomValidators.briteVerifyValidator(this.service) ] ]
});
}
get email() {
return this.emailForm.get('email');
}
// rest of logic
}
Validator class
import { AbstractControl } from '#angular/forms';
import { EmailValidationService } from '../../services/email-validation.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
export class CustomValidators {
static briteVerifyValidator(service: EmailValidationService) {
return (control: AbstractControl) => {
if (!control.valueChanges) {
return Observable.of(null);
} else {
return control.valueChanges
.debounceTime(1000)
.distinctUntilChanged()
.switchMap(value => service.validateEmail(value))
.map(data => {
return data.status === 'invalid' ? { invalid: true } : null;
});
}
}
}
}
Service
import { Injectable } from '#angular/core';
import { HttpClient,
HttpParams } from '#angular/common/http';
interface EmailValidationResponse {
address: string,
account: string,
domain: string,
status: string,
connected: string,
disposable: boolean,
role_address: boolean,
error_code?: string,
error?: string,
duration: number
}
#Injectable()
export class EmailValidationService {
public emailValidationUrl = 'https://briteverifyendpoint.com';
constructor(
private http: HttpClient
) { }
validateEmail(value) {
let params = new HttpParams();
params = params.append('address', value);
return this.http.get<EmailValidationResponse>(this.emailValidationUrl, {
params: params
});
}
}
Template (just form)
<form class="email-form" [formGroup]="emailForm" (ngSubmit)="sendEmail()">
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<fieldset class="form-group required" [ngClass]="{ 'has-error': email.invalid && formSubmitted }">
<div>{{ email.status }}</div>
<label class="control-label" for="email">Email</label>
<input class="form-control input-lg" name="email" id="email" formControlName="email">
<ng-container *ngIf="email.invalid && formSubmitted">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Please enter valid email address.
</ng-container>
</fieldset>
<button type="submit" class="btn btn-primary btn-lg btn-block">Send</button>
</div>
</div>
</form>
There's a gotcha!
That is, your observable never completes...
This is happening because the observable never completes, so Angular does not know when to change the form status. So remember your observable must to complete.
You can accomplish this in many ways, for example, you can call the first() method, or if you are creating your own observable, you can call the complete method on the observer.
So you can use first()
UPDATE TO RXJS 6:
briteVerifyValidator(service: Service) {
return (control: AbstractControl) => {
if (!control.valueChanges) {
return of(null);
} else {
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
switchMap(value => service.getData(value)),
map(data => {
return data.status === 'invalid' ? { invalid: true } : null;
})
).pipe(first())
}
}
}
A slightly modified validator, i.e always returns error: STACKBLITZ
OLD:
.map(data => {
return data.status === 'invalid' ? { invalid: true } : null;
})
.first();
A slightly modified validator, i.e always returns error: STACKBLITZ
So what I did was to throw a 404 when the username was not taken and use the subscribe error path to resolve for null, and when I did get a response I resolved with an error. Another way would be to return a data property either filled width the username or empty
through the response object and use that insead of the 404
Ex.
In this example I bind (this) to be able to use my service inside the validator function
An extract of my component class ngOnInit()
//signup.component.ts
constructor(
private authService: AuthServic //this will be included with bind(this)
) {
ngOnInit() {
this.user = new FormGroup(
{
email: new FormControl("", Validators.required),
username: new FormControl(
"",
Validators.required,
CustomUserValidators.usernameUniqueValidator.bind(this) //the whole class
),
password: new FormControl("", Validators.required),
},
{ updateOn: "blur" });
}
An extract from my validator class
//user.validator.ts
...
static async usernameUniqueValidator(
control: FormControl
): Promise<ValidationErrors | null> {
let controlBind = this as any;
let authService = controlBind.authService as AuthService;
//I just added types to be able to get my functions as I type
return new Promise(resolve => {
if (control.value == "") {
resolve(null);
} else {
authService.checkUsername(control.value).subscribe(
() => {
resolve({
usernameExists: {
valid: false
}
});
},
() => {
resolve(null);
}
);
}
});
...
I've been doing it slightly differently and faced the same issue.
Here is my code and the fix in case if someone would need it:
forbiddenNames(control: FormControl): Promise<any> | Observable<any> {
const promise = new Promise<any>((resolve, reject) => {
setTimeout(() => {
if (control.value.toUpperCase() === 'TEST') {
resolve({'nameIsForbidden': true});
} else {
return null;//HERE YOU SHOULD RETURN resolve(null) instead of just null
}
}, 1);
});
return promise;
}
I tries using the .first(). technique described by #AT82 but I didn't find it solved the problem.
What I eventually discovered was that the form status was changing but it because I'm using onPush, the status change wasn't triggering change detection so nothing was updating in the page.
The solution I ended up going with was:
export class EmailFormComponent implements OnInit {
...
constructor(
...
private changeDetector: ChangeDetectorRef,
) {
...
// Subscribe to status changes on the form
// and use the statusChange to trigger changeDetection
this.myForm.statusChanges.pipe(
distinctUntilChanged()
).subscribe(() => this.changeDetector.markForCheck())
}
}
import { Component,
OnInit } from '#angular/core';
import { FormBuilder,
FormGroup,
FormControl,
Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EmailValidationService } from '../services/email-validation.service';
import { CustomValidators } from '../utilities/custom-validators/custom-validators';
#Component({
templateUrl: './email-form.component.html',
styleUrls: ['./email-form.component.sass']
})
export class EmailFormComponent implements OnInit {
public emailForm: FormGroup;
public formSubmitted: Boolean;
public emailSent: Boolean;
constructor(
private router: Router,
private builder: FormBuilder,
private service: EmailValidationService
) { }
ngOnInit() {
this.formSubmitted = false;
this.emailForm = this.builder.group({
email: [ '', [ Validators.required ], [ CustomValidators.briteVerifyValidator(this.service) ] ]
});
}
get email() {
return this.emailForm.get('email');
}
// rest of logic
}
I'm getting crazy trying to inject request custom header (something like 'AUTH-TOKEN':'SomeToken123') to an on Angular 4.
I need to pass to the iframe page some required custom header parameters.
Anyone can please help me?
foo.component.html
<iframe [hidden]="isLoading" class="full" #iframe [src]="secureSrc" (load)="onIframeLoad()" frameborder="0" ></iframe>
component.ts
#Component({
selector: 'app-foo',
templateUrl: './foo.component.html'
})
export class FooComponent implements OnInit {
#ViewChild('iframe') iframe: ElementRef;
public isLoading: Boolean;
public secureSrc: SafeResourceUrl;
constructor(
private sanitizer: DomSanitizer,
private renderer: Renderer2,
private router: Router
) { }
public ngOnInit() {
this.isLoading = true;
this.secureSrc = this.getIframeURL();
}
private getIframeURL(): SafeResourceUrl {
return this.sanitizer
.bypassSecurityTrustResourceUrl('https://iframe.address');
}
public onIframeLoad(): void {
if (typeof this.iframe !== 'undefined') {
// Once iFrame Loaded
if (typeof this.token !== 'undefined') {
this.iframe
.nativeElement
.contentWindow
.postMessage({
externalCommand: 'some-action',
parameter : 'action parameter'
}, '*');
}
this.isLoading = false;
}
}
}
Thank you!
You can do it like this:
Create a service that will send http get request along with headers,
and expects blob response.
Use that service in your component to set src of an iframe.
Remove [src]="secureSrc" from iframe and leave only #iframe
.
// service
import { Injectable } from '#angular/core';
import { ResponseContentType, Http, RequestOptions, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
#Injectable()
export class UrlHelperService {
constructor(private http: Http) {
}
get(url: string): Observable<any> {
let options = new RequestOptions();
options.headers = new Headers();
options.headers.append('AUTH-TOKEN', 'SomeToken123');
options.responseType = ResponseContentType.Blob;
return new Observable((observer: Subscriber<any>) => {
let objectUrl: string = null;
this.http
.get(url, options)
.subscribe(m => {
objectUrl = URL.createObjectURL(m.blob());
observer.next(objectUrl);
});
return () => {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
objectUrl = null;
}
};
});
}
}
// component
import { Component, ViewChild, OnInit, ElementRef } from '#angular/core';
import { UrlHelperService } from './url-helper.service';
#Component({
selector: '',
templateUrl: './my-app.component.html'
})
export class MyAppComponent implements OnInit {
#ViewChild('iframe') iframe: ElementRef;
constructor(private urlHelperService: UrlHelperService) { }
ngOnInit() {
this.urlHelperService.get('http://localhost/api/file/123')
.subscribe(blob => this.iframe.nativeElement.src = blob);
}
}
I am building my first Angular app and need some help. The component I'm working on is an image search box. The user enters search query, request is sent to API, API responds with JSON data. Why is my *ngFor loop not working? The iterable is updated when the server sends response.
image-search.component.ts
import { Component, OnInit } from '#angular/core';
import { ImageSearchService } from './image-search.service';
import { Image } from '../shared/image';
#Component({
selector: 'vb-image-search',
templateUrl: './image-search.component.html',
styleUrls: ['./image-search.component.css'],
providers: [ImageSearchService]
})
export class ImageSearchComponent implements OnInit {
images: Image[] = [];
constructor(private ImageSearchService: ImageSearchService) { }
ngOnInit() {
}
getImages(query: string) {
this.ImageSearchService.getImages(query)
.subscribe(function(images) {
this.images = images;
});
}
onClick(query:string) {
this.getImages(query);
}
}
image-search.service.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Http, Response } from '#angular/http';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { Image } from '../shared/image';
#Injectable()
export class ImageSearchService {
constructor(private http: Http) {}
getImages(query: string): Observable<any[]> {
return this.http.get(`http://localhost:3000/api/search/${query}`)
.map(this.extractData)
}
private extractData(res: Response) {
let body = res.json();
return body.data.map(e => new Image(e.farmID, e.serverID, e.imageID, e.secret)) || {};
}
}
image.ts
export class Image {
constructor(public farmID: string, public serverID: string, public imageID: string, public secret: string) {
this.farmID = farmID;
this.serverID = serverID;
this.imageID = imageID;
this.secret = secret;
}
}
image-search.component.html
<div class="col-lg-6 col-md-6">
<div class="input-group">
<input type="text" [(ngModel)]="query" class="form-control" placeholder="Search for images..." />
<span class="input-group-btn">
<button (click)="onClick(query)" class="btn btn-default" type="button">Go!</button>
</span>
</div>
<h2>Images</h2>
<div *ngFor="let image of images">
{{image.imageID}}
</div>
</div>
The reason is very simple. In typescript the function call back loses the current scope if you use function(){} so instead you have to used => {} to retain the current scope. So please modify your current getImages method as mentioned below:
getImages(query: string) {
this.ImageSearchService.getImages(query)
.subscribe(images => {
this.images = images;
});
}