How to close parent dialog from child in angular? - angular6

I have one parent dialog inside which there is one child dialog box resides.
In child dialog box there is a close button .
On click of this close button, I want to close both parent and child dialog box. How can we do it in angular6 ?

In my case works :
Parent:
const dialogRef = this.dialog.open(AssignResourcePageComponent);
dialogRef.componentInstance.modal_principal_parent.on('CLOSE_PARENT_MODAL',()=>{
dialogRef.close();
});
Child
#Output() public modal_principal_parent = new EventEmitter();
in the method close:
this.modal_principal_parent.emit('CLOSE_PARENT_MODAL');

You have to just pass the MatDialogRef of Parent dialog to the child dialog component in dialog data and close the same in child component code.
Please find below code
This is code of Parent Component dialog which opens Child dialog and sends parent MatDialogRef to child dialog component in Data :
#Component({
selector: 'confirmation-dialog',
templateUrl: 'confirmation-dialog.html',
})
export class ConfirmationDialog {
childDilogRef = null;
message: string = "Are you sure?"
confirmButtonText = "Yes"
cancelButtonText = "Cancel"
constructor(
public dialog: MatDialog,
#Inject(MAT_DIALOG_DATA) private data: any,
private parentDilogRef: MatDialogRef<ConfirmationDialog>) {
if(data){
this.message = data.message || this.message;
if (data.buttonText) {
this.confirmButtonText = data.buttonText.ok || this.confirmButtonText;
this.cancelButtonText = data.buttonText.cancel || this.cancelButtonText;
}
}
}
onConfirmClick(): void {
this.parentDilogRef.close(true);
}
// this method is used for opening child dialog
OpenChild(){
if (this.childDilogRef === null) {
this.childDilogRef = this.dialog.open(MyChildComponent, {
data: this.parentDilogRef, // parent dialog sent as data to child dialog component
});
this.childDilogRef.afterClosed().subscribe(result => {
this.childDilogRef = null;
});
}
}
}
This is code of child component which initializes provided ParentDialogRef to local dialogRef variable. and we close both the dialog ref on click of button on child dialog.
#Component({
selector: "child-dialog",
template: `<mat-dialog-content>
<p>
Click on button to close both dialogs
</p>
</mat-dialog-content>
<mat-dialog-actions align="center">
<button (click)="closeBoth()">close both dialogs</button>
</mat-dialog-actions>`,
})
export class MyChildComponent {
constructor(
public childDialogRef: MatDialogRef<MyChildComponent>,
public parentDialogRef : MatDialogRef<ConfirmationDialog>,
#Inject(MAT_DIALOG_DATA) public data: MatDialogRef<ConfirmationDialog>
) {
if(data){
this.parentDialogRef = data
}
}
// close the about dialog
onNoClick(): void {
this.childDialogRef.close();
}
closeBoth():void{
this.childDialogRef.close();
this.parentDialogRef.close();
}
}

Related

Angular 10: Call a child component function from parent component

I have 2 component parent (Login Screen) and a child (user-list). Parent component has dropdowlist. The grid loads according to the item chosen in the drop down list I need to fire function of the child component and this is not working for me. I have the following code:
parent component html:
I have the following code:
<div>[items]="UserTypeSelectItems"[(ngModel)]="UserTypeId" id="fieldType"
bindLabel="value" bindKey="key" (change)="changeUserType()" [clearable]="false">
</div>
<app-user-list></app-user-list>
parent component ts:
I have the following code:
export class Login-ScreenComponent implements OnInit {
#ViewChild(UserListComponent)child:UserListComponent;
userTypeSelectItems: Array<SelectItem>;
userTypeId: any;
items: any;
constructor(
private userTypeSettingsService: userTypeSettingsService,
) {
this.userTypeSettingsService.getuserTypes().subscribe((data) => {
this.userTypeSelectItems = data;
if (
this.userTypeSelectItems &&
this.userTypeSelectItems.length > 0
) {
this.userTypeId =
this.userTypeSettingsService.selectedContractTypeId ??
this.userTypeSelectItems[0].key;
this.userTypeSettingsService.setContractTypeId(this.contractTypeId);
this.userTypeSettingsService.fillSelectedFields(this.userTypeId).subscribe(dataFields => {
this.items = dataFields;
this.child.getUser();
});
}
});
}
changeUserType() {
this.child.getUser();
}
child component ts:
I have the following code:
getUser() {
this.loading = true;
this.userService
.getAllUsers(this.userTypeId)
.pipe(finalize(() => (this.loading = false)))
.subscribe(
(data) => {
this.rows = data.map(notif => {
return {
user_status_id: status_id,
});
},
(err) => this.toastr.error(err),
() => (this.loading = false)
);
}
'''''''
if I understand your question correctly, that's what I'd suggest
Parent HTML
<div>[items]="UserTypeSelectItems"[(ngModel)]="UserTypeId" id="fieldType"
bindLabel="value" bindKey="key" (change)="changeUserType()" [clearable]="false">
</div>
<app-user-list [userTypeId]="userTypeId"></app-user-list>
In the parent ts remove all the calls of the this.child.getUser()
In the child component you should have input parameter userTypeId with setter. It will invoke the getUser() function every time when value is changed.
private _userTypeId: number;
#Input()
get userTypeId(): number {
return this._userTypeId;
}
set userTypeId(value: number): void {
this._userTypeId = value;
this.getUser();
}
You also can use the external service which will be injected in the parent and child components or use some Subject in the parent component, create Observable base of it which will be sent as input parameter to the child component. There you subscribe on the observable and then you need to emit the value with subjectvar.next(value) and as result function will be called. I can write down the example if you need.
UPD: example with observables
Parent component ts file:
private userTypeIdSubject$ = new Subject<string>();
private userTypeId$ = this.userTypeIdSubject$.asObservable();
changeUserType(): void {
// some code goes here
this.userTypIdSubject$.next(userTypeId); // this should send the message to the observer (child)
}
Parent HTML:
<div>[items]="UserTypeSelectItems"[(ngModel)]="UserTypeId" id="fieldType"
bindLabel="value" bindKey="key" (change)="changeUserType()" [clearable]="false">
</div>
<app-user-list [userTypeObservable]="userTypeId$"></app-user-list>
Child TS
#Input()
userTypeObservable: Observable<string>;
ngOnInit() {
if(this.userTypeObservable) {
this.userTypeObservable.subscribe(
(userTypeId) => {
this.userTypeId = userTypeId;
this.getUser();
}
}
}
}

Angular: ExpressionChangedAfterItHasBeenCheckedError when trying to disable button

I use mat-dialog to edit details of my profile page. I'm getting an ExpressionChangedAfterItHasBeenCheckedError when I click the 'Edit age' button and the dialog window pops up.
I decided to extract the styling of all edit dialogs into a single edit.component:
edit.component.html
<div class="navigation-control">
<mat-icon (click)="onCancelButtonClicked()"
class="close-button">close</mat-icon>
</div>
<div class="content-main">
<ng-content select=".content-main"></ng-content>
</div>
<div class="content-bot">
<button mat-raised-button
(click)="onCancelButtonClicked()">Cancel</button>
<button mat-raised-button
(click)="onActionButtonClicked()"
[lnDisableButton]="actionButtonDisabled">{{actionButtonValue}}</button>
</div>
edit.component.ts
#Component({ selector: 'ln-edit', ... })
export class EditComponent {
#Input() actionButtonValue: string;
#Input() actionButtonDisabled: boolean;
#Output() cancelButtonClicked = new EventEmitter<void>();
#Output() actionButtonClicked = new EventEmitter<void>();
onCancelButtonClicked() {
this.cancelButtonClicked.emit();
}
onActionButtonClicked() {
this.actionButtonClicked.emit();
}
}
To avoid the ExpressionChangedAfterItHasBeenCheckedError when trying to disable buttons and controls, I used this snippet. But that didn't solve this issue.
disable-button.directive.ts
#Directive({ selector: '[lnDisableButton]' })
export class DisableButtonDirective {
#Input('lnDisableButton') isDisabled = false;
#HostBinding('attr.disabled')
get disabled() { return this.isDisabled; }
}
The following is the contents of a mat-dialog window. This gets instantiated when I click the 'Edit age' button. When I remove the [actionButtonDisabled]="actionButtonDisabled", the error goes away, but obivously I need that line to make the functionality disable the button.
age-edit.component.html
<ln-edit [actionButtonValue]="actionButtonValue"
[actionButtonDisabled]="actionButtonDisabled"
(cancelButtonClicked)="onCancelButtonClicked()"
(actionButtonClicked)="onActionButtonClicked()">
<form [formGroup]="ageForm"
class="content-main">
<ln-datepicker formControlName="birthday"
[appearance]="'standard'"
[label]="'Birthday'"
class="form-field">
</ln-datepicker>
</form>
</ln-edit>
I handle the disabling/enabling the button in the 'ts' part of the mat-dialog popup.
age-edit.component.ts
#Component({ selector: 'ln-age-edit', ... })
export class AgeEditComponent implements OnInit, OnDestroy {
ageForm: FormGroup;
private initialFormValue: any;
actionButtonDisabled = true;
private unsubscribe = new Subject<void>();
constructor(
private editPhotoDialogRef: MatDialogRef<AgeEditComponent>,
private fb: FormBuilder,
#Inject(MAT_DIALOG_DATA) public dialogData: Date) { }
ngOnInit() {
this.initializeAgeForm();
this.loadDataToAgeForm(this.dialogData);
this.trackFormDistinct();
}
private initializeAgeForm(): void {
this.ageForm = this.fb.group({
birthday: null,
});
}
loadDataToAgeForm(birthday: Date | null): void {
if (!birthday) { return; }
this.ageForm.setValue({ birthday });
this.initialFormValue = this.ageForm.value;
}
get birthdayAC() { return this.ageForm.get('birthday') as AbstractControl; }
get actionButtonValue(): string {
return this.birthdayAC.value ? 'Update age' : 'Add age';
}
onCancelButtonClicked(): void {
this.editPhotoDialogRef.close();
}
onActionButtonClicked(): void {
this.editPhotoDialogRef.close({ ... });
}
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
ngOnDestroy() { ... }
}
I suspect this has something to do with content projection, but I'm not sure.
(...or perhaps with my custom 'ln-datepicker'?)
Any ideas?
Thanks.
From what I can tell, the problem resides in trackFormDistinct() method:
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
Looks like because of this.ageForm.valueChanges, will have different values in the 2 change detection cycles. I think this.ageForm.valueChanges emits due to <ln-datepicker>.
In a tree of form controls, if one node calls setValue, all its ancestors will have to be updated. I've written more about how Angular Forms work in this article.
I'm thinking of 2 alternatives:
skip the first emission of ageForm since it indicates the initialization of the form control tree, so this is irrelevant to the logic inside subscribe's callback.
this.ageForm.valueChanges.pipe(
skip(1),
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(/* .... */)
initialize actionButtonDisabled with false, since the error complains that it switched from true to false
actionButtonDisabled = false;

onClick event in Dart Web

I am a beginner to Dart-Web. I tried to handle click events in the HTML DOM via dart, but it doesn't seem to work. Below it my current code.
<div>
<button type="button" onclick="(onclick)=clickHandle()">Sign-in</button>
</div>
#Component(
selector: 'todo-list',
styleUrls: ['login_component.css'],
templateUrl: 'login_component.html',
directives: [
MaterialButtonComponent,
MaterialCheckboxComponent,
MaterialFabComponent,
MaterialIconComponent,
materialInputDirectives,
materialInputDirectives,
NgFor,
NgIf,
],
providers: [ClassProvider(LoginService)],
)
class LoginComponent implements OnInit {
final LoginService loginService;
List<String> items = [];
String newTodo = '';
LoginComponent(this.loginService);
#override
Future<Null> ngOnInit() async {
items = await loginService.getTodoList();
}
void add() {
items.add(newTodo);
newTodo = '';
}
void clickHandle() {
print("Button Clicked");
}
String remove(int index) => items.removeAt(index);
}
Please do request any additional files that are needed to answer the question.
onclick="(onclick)=clickHandle()"
is wrong.
It should be
(click)="clickHandle()"
The event is click, onClick is just a property where you can register a handler function to be called when that click event happens, but Angular is registering the handler in onClick for you.

Ionic2 - invisible tabs - when generated from an observable

My tabs.ts (simpilified) - data used to generated tabs with *ngFor is brought from php backend:
import ...
export interface Group {
id: number;
group: string;
};
#Component( {
template: `
<ion-tabs #myTabs selectedIndex="0">
<ion-tab *ngFor="let tab of userGroups" [root]="page" [rootParams]="tab.id" [tabTitle]="tab.group" tabIcon="pulse"></ion-tab>
</ion-tabs>
`
})
export class GroupsTabsPage {
userGroups: Group[];
page: any = TabStudentsPage;
constructor( public app: App, public api: Api, public navParams: NavParams ) {
this.api.getGroupsList()
.subscribe(
data => {
this.userGroups = data;
},
err => {
this.app.getRootNav().push( LoginPage )
}
);
// ionViewDidEnter() {
// }
}
}
The result is invisible tabs. But when you hover your mouse ovet them, the cursor changes into 'hand' and you can click them. When clicked, the whole tabs bar becomes visible and all works as expected.
When I used #ViewChild to refer to the tabs elements, the interesting thing is that its 'length' property is always 0 (I checked in ionViewDidLoad event). Trying to select one of the tabs programatically also fails - they are like ghosts;)
Also when you place at least one static tab next to *ngFor ones in the template, all *ngFor ones show up but the static is always selected no matter what you select programatically or in selectedIndex property on tabs element.
Any idea guys? I've wasted three days..
that's a known bug, take a look at the element css, the subview's .tabbar has opacity of 0. I've just fixed it with a an override of opacity: 1. Ugly, but works...
Creating ion-tab from observable (dynamically) has some bugs (duplicates, wrong rendering etc) I use a workaround to avoid it, it consist of removing and loading the ion-tabs runtime every time then observable changes.
Parent template:
<div #pluginTabContainer></div>
Parent component:
#ViewChild("pluginTabContainer", {read: ViewContainerRef}) pluginTabContainer:ViewContainerRef;
...
plugins$.subscribe((pluginTabs:Array<PluginTabType>) => { let componentFactory = this.componentFactoryResolver.resolveComponentFactory(PluginTabContainerComponent); this.pluginTabContainer.clear(); this.pluginTabContainertRef = this.pluginTabContainer.createComponent(componentFactory); this.pluginTabContainertRef.instance.data = pluginTabs;
...
ngOnDestroy() { this.pluginTabContainertRef.destroy(); }
Loaded ion-tabs template:
<ion-tabs> <ion-tab *ngFor="let tab of data" [root]="'PluginTabPage'" [rootParams]="tab"></ion-tab> </ion-tabs>
Loaded ion-tabs component (getting parameter):
#Input() data: PluginTabType;
Hope will be helpful for you.
I had a similar issue during development and I was able to solve this by making ngOninit async and calling a timeout to set the selected tab.
view
<ion-tabs #ctrlPanelTabs class="tabs-basic">
<ion-tab *ngFor="let appTab of appTabs" tabTitle={{appTab.name}} [root]="rootPage"></ion-tab>
</ion-tabs>
1) ngOninit is async
2) this.ctrlPanelTabs.select(0); is set inside a timeout function
import { Component, OnInit, ViewChild } from '#angular/core';
import { NavController, Tabs } from 'ionic-angular';
import { AppSettings } from '../../common/app.config';
import { AppTab } from '../../models/app-tab';
import { AppTabService } from '../../services/app-tab.service';
import { PanelTabComponent } from './panel-tab';
#Component({
selector: 'page-control-panel',
templateUrl: 'control-panel.html',
providers: [AppTabService]
})
export class ControlPanelPage implements OnInit {
#ViewChild("ctrlPanelTabs") ctrlPanelTabs: Tabs;
appTabs: AppTab[] = [];
message: string;
rootPage = PanelTabComponent;
constructor(public navCtrl: NavController,
private appTabService: AppTabService) {
console.log("Control Panel Page : Constructor called..");
}
async ngOnInit() {
console.log("Control Panel Page : Entering ngOninit..");
await this.loadAppTabs();
setTimeout(() => {
this.ctrlPanelTabs.select(0);
}, 100);
console.log("Control Panel Page : Exiting ngOninit..");
}
async loadAppTabs() {
console.log("Control Panel Page : Entering loadAppTabs..");
await this.appTabService.getAppTabsHierarchyBySlaveDeviceId(AppSettings.selSlaveDeviceId)
.then((response: any) => {
this.appTabs = JSON.parse(response.result);
console.log(this.appTabs);
console.log("Control Panel Page : Exiting loadAppTabs..");
});
}
}

Detect click outside Angular component

How can I detect clicks outside a component in Angular?
import { Component, ElementRef, HostListener, Input } from '#angular/core';
#Component({
selector: 'selector',
template: `
<div>
{{text}}
</div>
`
})
export class AnotherComponent {
public text: String;
#HostListener('document:click', ['$event'])
clickout(event) {
if(this.eRef.nativeElement.contains(event.target)) {
this.text = "clicked inside";
} else {
this.text = "clicked outside";
}
}
constructor(private eRef: ElementRef) {
this.text = 'no clicks yet';
}
}
A working example - click here
An alternative to AMagyar's answer. This version works when you click on element that gets removed from the DOM with an ngIf.
http://plnkr.co/edit/4mrn4GjM95uvSbQtxrAS?p=preview
private wasInside = false;
#HostListener('click')
clickInside() {
this.text = "clicked inside";
this.wasInside = true;
}
#HostListener('document:click')
clickout() {
if (!this.wasInside) {
this.text = "clicked outside";
}
this.wasInside = false;
}
Binding to a document click through #Hostlistener is costly. It can and will have a visible performance impact if you overuse it (for example, when building a custom dropdown component and you have multiple instances created in a form).
I suggest adding a #Hostlistener() to the document click event only once inside your main app component. The event should push the value of the clicked target element inside a public subject stored in a global utility service.
#Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(private utilitiesService: UtilitiesService) {}
#HostListener('document:click', ['$event'])
documentClick(event: any): void {
this.utilitiesService.documentClickedTarget.next(event.target)
}
}
#Injectable({ providedIn: 'root' })
export class UtilitiesService {
documentClickedTarget: Subject<HTMLElement> = new Subject<HTMLElement>()
}
Whoever is interested for the clicked target element should subscribe to the public subject of our utilities service and unsubscribe when the component is destroyed.
export class AnotherComponent implements OnInit {
#ViewChild('somePopup', { read: ElementRef, static: false }) somePopup: ElementRef
constructor(private utilitiesService: UtilitiesService) { }
ngOnInit() {
this.utilitiesService.documentClickedTarget
.subscribe(target => this.documentClickListener(target))
}
documentClickListener(target: any): void {
if (this.somePopup.nativeElement.contains(target))
// Clicked inside
else
// Clicked outside
}
Improving J. Frankenstein's answer:
#HostListener('click')
clickInside($event) {
this.text = "clicked inside";
$event.stopPropagation();
}
#HostListener('document:click')
clickOutside() {
this.text = "clicked outside";
}
The previous answers are correct, but what if you are doing a heavy process after losing the focus from the relevant component? For that, I came with a solution with two flags where the focus out event process will only take place when losing the focus from relevant component only.
isFocusInsideComponent = false;
isComponentClicked = false;
#HostListener('click')
clickInside() {
this.isFocusInsideComponent = true;
this.isComponentClicked = true;
}
#HostListener('document:click')
clickout() {
if (!this.isFocusInsideComponent && this.isComponentClicked) {
// Do the heavy processing
this.isComponentClicked = false;
}
this.isFocusInsideComponent = false;
}
ginalx's answer should be set as the default one imo: this method allows for many optimizations.
The problem
Say that we have a list of items and on every item we want to include a menu that needs to be toggled. We include a toggle on a button that listens for a click event on itself (click)="toggle()", but we also want to toggle the menu whenever the user clicks outside of it. If the list of items grows and we attach a #HostListener('document:click') on every menu, then every menu loaded within the item will start listening for the click on the entire document, even when the menu is toggled off. Besides the obvious performance issues, this is unnecessary.
You can, for example, subscribe whenever the popup gets toggled via a click and start listening for "outside clicks" only then.
isActive: boolean = false;
// to prevent memory leaks and improve efficiency, the menu
// gets loaded only when the toggle gets clicked
private _toggleMenuSubject$: BehaviorSubject<boolean>;
private _toggleMenu$: Observable<boolean>;
private _toggleMenuSub: Subscription;
private _clickSub: Subscription = null;
constructor(
...
private _utilitiesService: UtilitiesService,
private _elementRef: ElementRef,
){
...
this._toggleMenuSubject$ = new BehaviorSubject(false);
this._toggleMenu$ = this._toggleMenuSubject$.asObservable();
}
ngOnInit() {
this._toggleMenuSub = this._toggleMenu$.pipe(
tap(isActive => {
logger.debug('Label Menu is active', isActive)
this.isActive = isActive;
// subscribe to the click event only if the menu is Active
// otherwise unsubscribe and save memory
if(isActive === true){
this._clickSub = this._utilitiesService.documentClickedTarget
.subscribe(target => this._documentClickListener(target));
}else if(isActive === false && this._clickSub !== null){
this._clickSub.unsubscribe();
}
}),
// other observable logic
...
).subscribe();
}
toggle() {
this._toggleMenuSubject$.next(!this.isActive);
}
private _documentClickListener(targetElement: HTMLElement): void {
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this._toggleMenuSubject$.next(false);
}
}
ngOnDestroy(){
this._toggleMenuSub.unsubscribe();
}
And, in *.component.html:
<button (click)="toggle()">Toggle the menu</button>
Alternative to MVP, you only need to watch for Event
#HostListener('focusout', ['$event'])
protected onFocusOut(event: FocusEvent): void {
console.log(
'click away from component? :',
event.currentTarget && event.relatedTarget
);
}
Solution
Get all parents
var paths = event['path'] as Array<any>;
Checks if any parent is the component
var inComponent = false;
paths.forEach(path => {
if (path.tagName != undefined) {
var tagName = path.tagName.toString().toLowerCase();
if (tagName == 'app-component')
inComponent = true;
}
});
If you have the component as parent then click inside the component
if (inComponent) {
console.log('clicked inside');
}else{
console.log('clicked outside');
}
Complete method
#HostListener('document:click', ['$event'])
clickout(event: PointerEvent) {
var paths = event['path'] as Array<any>;
var inComponent = false;
paths.forEach(path => {
if (path.tagName != undefined) {
var tagName = path.tagName.toString().toLowerCase();
if (tagName == 'app-component')
inComponent = true;
}
});
if (inComponent) {
console.log('clicked inside');
}else{
console.log('clicked outside');
}
}
You can use the clickOutside() method from the ng-click-outside package; it offers a directive "for handling click events outside an element".
NB: This package is currently deprecated. See https://github.com/arkon/ng-sidebar/issues/229 for more info.
Another possible solution using event.stopPropagation():
define a click listener on the top most parent component which clears the click-inside variable
define a click listener on the child component which first calls the event.stopPropagation() and then sets the click-inside variable
You can call an event function like (focusout) or (blur); then you would put in your code:
<div tabindex=0 (blur)="outsideClick()">raw data </div>
outsideClick() {
alert('put your condition here');
}
nice and tidy with rxjs.
i used this for aggrid custom cell editor to detect clicks inside my custom cell editor.
private clickSubscription: Subscription | undefined;
public ngOnInit(): void {
this.clickSubscription = fromEvent(document, "click").subscribe(event => {
console.log("event: ", event.target);
if (!this.eRef.nativeElement.contains(event.target)) {
// ... click outside
} else {
// ... click inside
});
public ngOnDestroy(): void {
console.log("ON DESTROY");
this.clickSubscription?.unsubscribe();
}