How to capture anctions window:beforeunload Angular 8 - javascript

I have a problem with window event beforeunload in Angular app.
i wants to check if the user navigate to another page, if so i want to clean sessionStorage. I use.
onBeforeUnload(event: BeforeUnloadEvent) {
event.returnValue = '';
}
Beforeunload event also workd as a page refresh. How to check if the user leaves the application and clear the sesion storage after confirm dialog.

You can try below code in constructor of your component class.
Option 1
window.addEventListener("beforeunload", function (e) {
exampleService.logout();
});
Option 2
#HostListener('window:beforeunload', ['$event'])
beforeUnloadHandler(event: any) {
event.preventDefault();
// any other code / dialog logic
}
To distinguish from 2 cases, you should check out this stackoverflow question or related questions, here's one:
Question
Hope, this helps.

The following code will make browser to ask user that whether he wants to lose the unsaved changes or not.
Also, I'm checking whether the form is dirty or not.
If the form is not dirty, browser won't bother the user and close it.
import { Component, ViewChild, HostListener } from '#angular/core';
import { NgForm } from '#angular/forms';
import { DetailedUser } from 'src/app/models/detailed-user';
#Component({
selector: 'app-member-edit',
templateUrl: './member-edit.component.html',
styleUrls: ['./member-edit.component.css']
})
export class MemberEditComponent {
user: DetailedUser = new DetailedUser();
#ViewChild('userForm') userForm: NgForm;
#HostListener('window:beforeunload', ['$event'])
WindowBeforeUnoad($event: any) {
if (this.userForm.dirty) {
$event.returnValue = 'Your data will be lost!';
}
}
saveChanges(){
....
this.userForm.reset(this.user);
}
}
the following is the html section
<form (ngSubmit)="saveChanges()" #userForm="ngForm">
<div class="form-group">
<label for="introduction">Introduction</label>
<textarea
name="introduction"
class="form-control"
[(ngModel)]="user.introduction"
rows="6"></textarea>
</div>
<div class="form-group">
<label for="lookingFor">Looking For</label>
<textarea
name="lookingFor"
class="form-control"
[(ngModel)]="user.lookingFor"
rows="6"></textarea>
</div>
</form>

Related

button component with BUILT-IN disable / re-enable (PREVENT DOUBLE SUBMIT!)

Is it just a good rule of thumb to handle all 'disable' validation logic on the form component itself? Does that sound right??
I'm really just trying to find a way to share the 'disable' logic across the application without repeating it in every form component, but i guess that is the proper way to do things?? Can someone verify this??
I would like to create a reusable submit button component.
This submit button component should act like any other submit button component, except for one thing...
The submit button needs to "disable itself" after being clicked.
That should be easy enough right. However, the rub here is that the button also needs to "re-enable itself" after the "call" is 100% completed. (In case there is an error, or the application needs to allow another action after the first is completed, etc).
I would like 100% of "that" logic to exist inside of the component so I can easily reuse it everywhere in the application.
I thought it would be kinda easy, but I think I'm still missing something...
I guess the idea would be to use an #Input() preferably (or maybe Output) in order to pass in some "async callback type of thing" to the button control itself...
That way the button could react to the "async callback type of thing" completing and use the callback handler to re-enable itself.
Thanks for the help in advance!
I wrote an abstract class which I use all the time for this exact case:
import { ElementRef, EventEmitter, Input, Output, ViewChild } from '#angular/core';
import { FormGroup, FormGroupDirective } from '#angular/forms';
export abstract class FormBase<T = any> {
#Input() isSubmitting: boolean;
#Output('onSubmit') _submit = new EventEmitter<T>();
#ViewChild(FormGroupDirective, { static: true })
ngForm: FormGroupDirective;
#ViewChild('submitButton', { static: true }) button: ElementRef;
form: FormGroup;
onSubmit(): void {
if (this.isFormValid()) this._submit.emit(this.getFormValue());
}
submit(): void {
if (!this.button || !this.button.nativeElement) return;
this.button.nativeElement.click();
}
reset(value?: any) {
this.ngForm.resetForm(value);
}
isFormValid(): boolean {
return this.form.valid;
}
getFormValue(): T {
return this.form.value;
}
shouldDisable(): boolean {
return (
this.isSubmitting ||
((this.form.invalid || this.form.pending) &&
(this.ngForm ? this.ngForm.submitted : true))
);
}
}
component.ts
import { FormBase } from 'path';
#Component({
...
})
export class FormComponent extends FormBase {
constructor(formBuilder: FormBuilder) {
super();
this.form = formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required],
});
}
}
component.html
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<mat-form-field>
<mat-label>Username</mat-label>
<input matInput type="text" formControlName="username" />
</mat-form-field>
<mat-form-field>
<mat-label>Password</mat-label>
<input matInput type="text" formControlName="password" />
</mat-form-field>
<button [disabled]="shouldDisable()" mat-flat-button color="primary">
Submit
</button>
</form>
Your form components basically extend from this class which should work 99% of the time. If your component needs some very specific functionality like changing when the button becomes disabled or something else, you can simply override the methods in the FormComponent.

Data binding not updating when the variable is, in Chrome extension

I am creating a chrome extension using Angular4.
In one of my component, I am setting the value of a textarea to the value of what's selected on the chrome tab.
Everything looks like working but once the value is passed to my variable, the view of the plugin isn't updated.
This is my code :
HTML:
<div class="container-fluid main">
<textarea name="selectedText" id="selectedText" [(ngModel)]="selectedText"></textarea>
<button (click)="getSelectedText()">Get the selected text</button>
TS:
import {Component, OnInit} from '#angular/core';
#Component({
selector: 'app-capture',
templateUrl: './capture.component.html',
styleUrls: ['./capture.component.css']
})
export class CaptureComponent implements OnInit {
selectedText = '';
constructor() {
}
ngOnInit() {}
getSelectedText() {
chrome.tabs.executeScript( {
code: 'window.getSelection().toString();'
}, (selection) => {
this.selectedText = selection[0];
console.log('the callback value is: ' + this.selectedText);
});
}
}
What I tried:
I added a button to trigger a useless function just to refresh the view like this:
HTML:
<button (click)="refresh()">Test button</button>
TS:
refresh() {
console.log('hello world');
}
If I click on the "Get selected text" button twice, or click on "get selected text" then on "Test button", I get the value in my textarea. I don't know where the bug is or how I can tell angular to keep watching for my data binding.
How do I get the right value in my textarea when clicking only once on the "get selected text" button?
Also, why would angular stop watching for my variable with my data binding? Is this Chrome-related?

Textarea to ignore enter but need to trigger Save button

A bit tricky situation. For the code below, I have added (keydown.enter)="false" to ignore the break line/enter button in textarea
This is causing a user issue and would like the existing behaviour where Pressing enter should automatically trigger the "Save button"
Any idea how to trigger the Save button when still focusing in textArea but ignore the breakline?
<textarea #textArea
style="overflow:hidden; height:auto; resize:none;"
rows="1"
class="form-control"
[attr.placeholder]="placeholder"
[attr.maxlength]="maxlength"
[attr.autofocus]="autofocus"
[name]="name"
[attr.readonly]="readonly ? true : null"
[attr.required]="required ? true : null"
(input)="onUpdated($event)"
[tabindex]="skipTab ? -1 : ''"
(keydown.enter)="false"
[(ngModel)]="value">
</textarea >
Extending the answer by #Pengyy
You can bind the bind the enter key to a pseudoSave function, and preventDefault inside of that, thus preventing both the Save function and the newline. Then you can either call the save function from there(assuming it is accessible such as a service) or you can emit an EventEmitter, and have that emit get caught to trigger the Save function.
you can bind the same function of Save button to keydown.enter of texterea, and call $event.preventDefault to avoid the newline.
sample plunker.
Assuming that your textarea is inside a form element.
{Plunker Demo}
You can achieve it by using a hidden submit input, like this
#Component({
selector: 'my-app',
template: `
<form (submit)="formSubmitted($event)">
<input #proxySubmitBtn type="submit" [hidden]="true"/>
<textarea #textArea (keydown.enter)="$event.preventDefault(); proxySubmitBtn.click()">
</textarea>
</form>
`,
})
export class App {
formSubmitted(e) {
e.preventDefault();
alert('Form is submitted!');
}
}
You can create a service which can send a notification to other components that will handle the command. The service could look like this:
import { Injectable } from "#angular/core";
import { Subject } from "rxjs/Subject";
#Injectable()
export class DataSavingService {
private dataSavingRequested = new Subject<void>();
public dataSavingRequested$ = this.dataSavingRequested.asObservable();
public requestDataSaving(): void {
this.dataSavingRequested.next();
}
}
... and should be registered in the providers section of the module. Note: if data must be passed in the notification, you can declare a non-void parameter type for the dataSavingRequested Subject (e.g. string).
The service would be injected in the component with the textarea element and called in the handler of the Enter keypress event:
import { DataSavingService } from "./services/data-saving.service";
...
#Component({
template: `
<textarea (keypress.enter)="handleEnterKeyPress($event)" ...></textarea>
`
})
export class ComponentWithTextarea {
constructor(private dataSavingService: DataSavingService, ...) {
...
}
public handleEnterKeyPress(event: KeyboardEvent): void {
event.preventDefault(); // Prevent the insertion of a new line
this.dataSavingService.requestDataSaving();
}
...
}
The component with the Save button would subscribe to the dataSavingRequested$ notification of the service and save the data when notified:
import { Component, OnDestroy, ... } from "#angular/core";
import { Subscription } from "rxjs/Subscription";
import { DataSavingService } from "../services/data-saving.service";
...
#Component({
...
})
export class ComponentWithSaveButton implements OnDestroy {
private subscription: Subscription;
constructor(private dataSavingService: DataSavingService, ...) {
this.subscription = this.dataSavingService.dataSavingRequested$.subscribe(() => {
this.saveData();
});
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
private saveData(): void {
// Perform data saving here
// Note: this method should also be called by the Save button
...
}
}
The code above assumes that the saving must be performed in the component with the Save button. An alternative would be to move that logic into the service, which would expose a saveData method that could be called by the components. The service would need to gather the data to save, however. It could be obtained with a Subject/Observable mechanism, or supplied directly by the components as a parameter to saveData or by calling another method of the service.
it could be 2 solutions:
Use javascript to handle enter event and trigger Save function in it
or
Use Same thing from Angular side as describe in this.
This may also help you

Angular 2 Focus on first invalid input after Click/Event

I have an odd requirement and was hoping for some help.
I need to focus on the first found invalid input of a form after clicking a button (not submit). The form is rather large, and so the screen needs to scroll to the first invalid input.
This AngularJS answer would be what I would need, but didn't know if a directive like this would be the way to go in Angular 2:
Set focus on first invalid input in AngularJs form
What would be the Angular 2 way to do this? Thanks for all the help!
This works for me. Not the most elegant solution, but given the constraints in Angular we are all experiencing for this particular task, it does the job.
scrollTo(el: Element): void {
if(el) {
el.scrollIntoView({ behavior: 'smooth' });
}
}
scrollToError(): void {
const firstElementWithError = document.querySelector('.ng-invalid');
this.scrollTo(firstElementWithError);
}
async scrollIfFormHasErrors(form: FormGroup): Promise <any> {
await form.invalid;
this.scrollToError();
}
This works, allowing you to evade manipulating the DOM. It simply goes to the first element with .ng-invalid on the page through the document.querySelector() which returns the first element in the returned list.
To use it:
this.scrollIfFormHasErrors(this.form).then(() => {
// Run any additional functionality if you need to.
});
I also posted this on Angular's Github page: https://github.com/angular/angular/issues/13158#issuecomment-432275834
Unfortunately I can't test this at the moment, so might be a few bugs, but should be mostly there.
Just add it to your form.
import {Directive, Input, HostListener} from '#angular/core';
import {NgForm} from '#angular/forms';
#Directive({ selector: '[scrollToFirstInvalid]' })
export class ScrollToFirstInvalidDirective {
#Input('scrollToFirstInvalid') form: NgForm;
constructor() {
}
#HostListener('submit', ['$event'])
onSubmit(event) {
if(!this.form.valid) {
let target;
for (var i in this.form.controls) {
if(!this.form.controls[i].valid) {
target = this.form.controls[i];
break;
}
}
if(target) {
$('html,body').animate({scrollTop: $(target.nativeElement).offset().top}, 'slow');
}
}
}
}
If you are using AngularMaterial, the MdInputDirective has a focus() method which allow you to directly focus on the input field.
In your component, just get a reference to all the inputs with the #ViewChildren annotation, like this:
#ViewChildren(MdInputDirective) inputs: QueryList<MdInputDirective>;
Then, setting focus on the first invalid input is as simple as this:
this.inputs.find(input => !input._ngControl.valid).focus()
I don't know if this is valid approach or not but this is working great for me.
import { Directive, Input, HostListener, ElementRef } from '#angular/core';
import { NgForm } from '#angular/forms';
import * as $ from 'jquery';
#Directive({ selector: '[accessible-form]' })
export class AccessibleForm {
#Input('form') form: NgForm;
constructor(private el: ElementRef) {
}
#HostListener('submit', ['$event'])
onSubmit(event) {
event.preventDefault();
if (!this.form.valid) {
let target;
target = this.el.nativeElement.querySelector('.ng-invalid')
if (target) {
$('html,body').animate({ scrollTop: $(target).offset().top }, 'slow');
target.focus();
}
}
}
}
In HTML
<form [formGroup]="addUserForm" class="form mt-30" (ngSubmit)="updateUser(addUserForm)" accessible-form [form]="addUserForm"></form>
I have mixed the approach of angularjs accessible form directive in this.
Improvements are welcomed!!!
I've created an Angular directive to solve this problem. You can check it here ngx-scroll-to-first-invalid.
Steps:
1.Install the module:
npm i #ismaestro/ngx-scroll-to-first-invalid --save
2.Import the NgxScrollToFirstInvalidModule:
import {BrowserModule} from '#angular/platform-browser';
import {NgModule} from '#angular/core';
import {NgxScrollToFirstInvalidModule} from '#ismaestro/ngx-scroll-to-first-invalid';
#NgModule({
imports: [
BrowserModule,
NgxScrollToFirstInvalidModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
3.Use the directive inside a form:
<form [formGroup]="testForm" ngxScrollToFirstInvalid>
<input id="test-input1" type="text" formControlName="someText1">
<button (click)="saveForm()"></button>
</form>
Hope it helps!
:)
Plain HTML solution.
If you don't need to be scrolling , just focus on first valid input, I use :
public submitForm() {
if(this.form.valid){
// submit form
} else {
let invalidFields = [].slice.call(document.getElementsByClassName('ng-invalid'));
invalidFields[1].focus();
}
}
This is for template driven form here. We focus on second element of invalidFields cuz first is the whole form which is invalid too.
For Angular Material ,
The below worked for me
#ViewChildren(MatInput) inputs: QueryList <MatInput>;
this.inputs.find(input => !input.ngControl.valid).focus();
I recommend putting this in a service, for me it worked like this:
if (this.form.valid) {
//submit
} else {
let control;
Object.keys(this.form.controls).reverse().forEach( (field) => {
if (this.form.get(field).invalid) {
control = this.form.get(field);
control.markAsDirty();
}
});
if(control) {
let el = $('.ng-invalid:not(form):first');
$('html,body').animate({scrollTop: (el.offset().top - 20)}, 'slow', () => {
el.focus();
});
}
}
#HostListener('submit', ['$event'])
onSubmit(event) {
event.preventDefault();
if (!this.checkoutForm.valid) {
let target;
target = $('input[type=text].ng-invalid').first();
if (target) {
$('html,body').animate({ scrollTop: $(target).offset().top }, 'slow', ()=> {
target.focus();
});
}
}
}

how to share data between two component using Event Emitter?

Could you please suggest how to add item in list when something enter in input field and then enter press.But these are different component .I want to share input field value to different component using #Input ,#Output ,EventEmitter.
I make one component .
<todo></todo>
here is ts file
import { Page,NavController,Modal } from 'ionic-angular/index';
import { Component, Input, Output, EventEmitter} from 'angular2/core';
#Component({
selector:'todo'
templateUrl:"addtodo.html",
})
export class AddToDO{
constructor() {
}
addItem(v){
alert(v.value)
}
}
here is my html
<label class="item item-input">
<span class="input-label" >Add Todo</span>
<input type="text" #todo placeholder="Add todo" (keyup.enter)="addItem(todo)">
</label>
I want to add item list when enter is press .so need to share data between component
http://plnkr.co/edit/N6aXDZXUr99MC6w77H6d?p=preview
I would leverage an #Output in your AddToDO component to trigger that an element was added. So you can catch it from the parent component and add the corresponding data in the list.
#Component({
selector:'todo'
templateUrl:"addtodo.html",
})
export class AddToDO{
#Output()
todoAdded:EventEmitter = new EventEmitter();
constructor() {
}
addItem(v){
alert(v.value)
this.todoAdded.emit(v);
}
}
and in the parent component template:
<todo (todoAdded)="addTodoInList($event)"></todo>
Here is the content of the addTodoInList method:
addTodoInList(todo) {
this.Todo.push(todo);
}
See this plunkr: http://plnkr.co/edit/mQtQIXIJwMfJSfNVHvac?p=preview.

Categories

Resources