I have modal window and component where this modal window called. When i try to close this modal window, method that need to close it - don't invoked, because of this method starts when event is emited. But method subscription doesn't happen.
export class TakeOrderFormComponent implements OnInit
{
#ViewChild('orderSucModal') orderSuccessModal: OrderSuccessComponent;
orderSuccessRef: BsModalRef;
constructor(private orderService: OrderService, private cartService: CartService,
private modalService: BsModalService, private router: Router)
{
}
openCartModal(orderId: number): void
{
this.orderSuccessModal.loadOrder(orderId);
this.orderSuccessRef = this.modalService.show(this.orderSuccessModal.template, {ignoreBackdropClick: true});
}
hideSucModal(): void
{
console.log('in parent start')
this.orderSuccessRef.hide();
console.log('in parent stop')
} ...
This is code of modal window in main block. And here method hideSucModal() don't invoked.
<app-order-success
#orderSucModal
(hideModal)="hideSucModal()">
</app-order-success>
All modal window code
<ng-template #orderSuccess class="modal-md">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Ваше замовлення <b>№{{order.orderId}}</b> успішно оброблено!</h4>
</div>
<div class="modal-body">
<div class="modal-footer">
<button (click)="hideModalClick()" class="btn btn-outline-success"
type="button">Зрозуміло!
</button>
</div>
</div>
</div>
</ng-template>
And here is logic of modal-component
export class OrderSuccessComponent
{
#ViewChild('orderSuccess') template: TemplateRef<any>;
#Output() hideModal: EventEmitter<void> = new EventEmitter<void>();
#Output() confirm: EventEmitter<void> = new EventEmitter<void>();
public order: Order = new Order();
public dateAndTime: string[] = [];
constructor(private orderService: OrderService, private router: Router)
{
}
hideModalClick(): void
{
console.log('in child start')
this.hideModal.emit();
this.router.navigate(['/home']);
console.log('in child stop');
}...
Actually as I done some research, I think that method hideSucModal() can't subscribe to hideModal event. Any thoughts about this?
Maybe you can make small refactoring:
1) in core component
import { MatDialog } from '#angular/material/dialog';
add in constructor
constructor( ... ,private matDialog: MatDialog )
to open modal window use
this.matDialog.open(OrderSuccessComponent, {data},width:'100%', height:'90%')
2) in OrderSuccessComponent
add in constructor
constructor( ... ,private dialogRef: MatDialogRef )
and now you can call this.dialogRef.close() inside hideModalClick()
Pay attention different entities (MatDialog vs MatDialogRef) used in core and child components!
Related
I have a modalComponent that I create dynamically.
<div class="modal">
<div class="modal-body">
Test
</div>
<div class="modal-footer">
<button (click)="callbackFunction()">success</button>
<button>abort</button>
</div>
</div>
This component has an Input callbackFunction that'a function that I want to invoke from my parent component.
import {
Component,
Input,
OnInit,
QueryList,
ViewChildren
} from "#angular/core";
import { ModalService } from "../modal.service";
#Component({
selector: "app-modal",
templateUrl: "./modal.component.html",
styleUrls: ["./modal.component.css"]
})
export class ModalComponent implements OnInit {
#Input() callbackFunction: () => void;
constructor(private modalService: ModalService) {}
ngOnInit() {}
}
After that I created a service:
import {
ApplicationRef,
ComponentFactoryResolver,
ComponentRef,
Injectable,
Injector
} from "#angular/core";
import { ModalComponent } from "./modal/modal.component";
#Injectable()
export class ModalService {
dialogComponentRef: ComponentRef<ModalComponent>;
open(callbackFunction: any) {
const modalComponentFactory = this.cfResolver.resolveComponentFactory(ModalComponent);
const modalComponent = modalComponentFactory.create(this.injector);
modalComponent.instance.callbackFunction = callbackFunction;
this.dialogComponentRef = modalComponent;
document.body.appendChild(modalComponent.location.nativeElement);
this.appRef.attachView(modalComponent.hostView);
}
close() {
this.appRef.detachView(this.dialogComponentRef.hostView);
}
constructor(
private appRef: ApplicationRef,
private cfResolver: ComponentFactoryResolver,
private injector: Injector
) {}
}
After componentFactoryResolver I pass my function as instance.
In my parent controller I create a function
sayHello(
this.myService.doSomething();
}
and after that I create a function for opening a modal
open(this.sayHello());
When I click on the button and I invoke callback function, "this" is not referred to Parent component but to Modal Component and sayHello is undefined. How can I fix this situation?
I don't want to use emit.
This is my stackblitz: Example
Basically there are three solutions for this: Output + EventEmitter, #ViewChild and Subject
ViewChild solution
This one can be used when the button is defined on the Parent and you want to get something from the Child.
///////parent.component.ts
...
import { ChildComponent } from 'child/child.component';
...
export class ParentComponent {
#ViewChild(ChildComponent) childComponent: ChildComponent;
public buttonClick(): void {
let childResponse = this.childComponent.getValues();//will return '1234'
...
}
}
///////child.component.ts
export class ChildComponent {
valueInsideChild = '1234';
public getValues(): string {
return this.valueInsideChild;
}
}
Output + EventEmitter solution
In this scenario the child itself sends something to the parent(aka the button is inside the child)
implementation on stackblic
//////parent.component.html
<child-selector
($buttonClicked)=clickAction($event)>
</child-selector>
//////parent.component.ts
...
export class ParentComponent {
public clickAction(value: string): void {
console.log(value);//will log 'something1234 when child button is clicked
}
}
//////child.component.ts
...
import { Output, Component, EventEmitter } from '#angular/core';
...
export class ChildComponent {
#Output() $buttonClicked = new EventEmitter<string>();
public click(): void {
this.$buttonClicked.emit('something1234');
}
}
//////child.component.html
<button (click)="click()">
Subject
Interface responses using your modalService+subject+observables
///app.component.ts
...
export class AppComponent {
...
open() {
//subscribe to the observable :)
this.modalService.open(this.sayHello).subscribe(response => {
alert(response.text);
});
}
...
}
///modal.component.html
...
<button (click)="click()">success</button>
...
///modal.component.ts
...
export class ModalComponent {
constructor(private modalService: ModalService) {}
...
public click(): void {
this.modalService.close({text: 'Hello World'});
}
}
///modal.service.ts
...
import { Subject, Observable } from 'rxjs';
...
export class ModalService {
...
private _modalResponse = new Subject<any>();
...
open(): Observable<any> {//this is your open function
...
return this._modalResponse.asObservable();//return an observable where the modal responses will be emitted
}
close(response: any): void {
//receives a value from the modal component when closing
this.appRef.detachView(this.dialogComponenRef.hostView);
this._modalResponse.next(response);//emit the response on the Observable return when open was called
}
}
I suggest you to use an Output and a EventEmitter to call the parent component function from the child component, Angular documentation provides a good example on how to do it.
https://angular.io/guide/inputs-outputs#sending-data-to-a-parent-component
I'm implementing alert services in my applications however I get the error Property 'alertService' does not exist on type 'AppComponent' and Property 'options' does not exist on type 'AppComponent'
app.component.html:
<div class="form-group">
<button [disabled]="frmSignup.invalid" type="submit" class="btn btn-primary btn-block font-weight-bold"
(click)="alertService.success('Success!!', options)">Submit</button>
</div>
app.component.ts:
export class AppComponent {
public frmSignup: FormGroup;
public message = "Congrats you have successfully created your account";
constructor(private fb: FormBuilder) {
this.frmSignup = this.createSignupForm();
}
createSignupForm(): FormGroup {
return this.fb.group(
{
........
}
);
}
submit() {
// do signup or something
console.log(this.frmSignup.value);
alert(this.message);
}
You need to explicity inject the alertService in the constructor of AppComponent
constructor(private fb: FormBuilder, alertService: AlertService) {
this.frmSignup = this.createSignupForm();
this.alertService = alertService;
}
The options need to be set as well in the Component as a public property.
However:
The better option would be to create a class method, that you can call on click event:
<div class="form-group">
<button [disabled]="frmSignup.invalid" type="submit" class="btn btn-primary btn-block font-weight-bold"
(click)="handleClick()">Submit</button>
</div>
export class AppComponent {
public frmSignup: FormGroup;
public message = "Congrats you have successfully created your account";
options = {};
constructor(private fb: FormBuilder, private alertService: AlertService) {
this.frmSignup = this.createSignupForm();
}
createSignupForm(): FormGroup {
return this.fb.group(
{
........
}
);
}
submit() {
// do signup or something
console.log(this.frmSignup.value);
alert(this.message);
}
handleClick() {
this.alertService.success('Success!!', options);
}
}
Note: I don't understand, why the submit button doesn't call the submit method...
I'm using Angular 9, where I want to dynamically change data of a menu item when a person logs in. But instead, since the menu gets loaded along with the home page, when a person logs in, the data change is not reflected in the menu items until I refresh the page manually. I tried using Renderer 2, ChangeDetectorRef and ElementRef but failded to reload the menu automatically. Below I'm adding just the relevant elements since the actual component code is long. Ask me if you need to know anything else:
Html:
<div class="widget-text">
<a mat-button [matMenuTriggerFor]="accountMenu" #accountMenuTrigger="matMenuTrigger" *ngIf="!isLoggedIn">
<mat-icon>person</mat-icon>
<span fxShow="false" fxShow.gt-sm class="flag-menu-title">Account</span>
<mat-icon class="mat-icon-sm caret cur-icon">arrow_drop_down</mat-icon>
</a>
<mat-menu #accountMenu="matMenu" [overlapTrigger]="false" xPosition="before" class="app-dropdown">
<span>
<button mat-menu-item [routerLink]="['/admin/login']" routerLinkActive="router-link-active">
<mat-icon >person</mat-icon>
<span>Login</span>
</button>
<button mat-menu-item [routerLink]="['/admin/login']" routerLinkActive="router-link-active">
<mat-icon>person_add</mat-icon>
<span>Register</span>
</button>
</span>
</mat-menu>
<a mat-button [matMenuTriggerFor]="profileMenu" #profileMenuTrigger="matMenuTrigger" *ngIf="isLoggedIn">
<mat-icon>person</mat-icon>
<span fxShow="false" fxShow.gt-sm class="flag-menu-title">Howdy, {{name}}</span>
<mat-icon class="mat-icon-sm caret cur-icon">arrow_drop_down</mat-icon>
</a>
<mat-menu #profileMenu="matMenu" [overlapTrigger]="false" xPosition="before" class="app-dropdown">
<span>
<button mat-menu-item [routerLink]="['/admin/profile']" routerLinkActive="router-link-active">
<mat-icon >person</mat-icon>
<span>Profile</span>
</button>
<button mat-menu-item (click)="logout()">
<mat-icon>warning</mat-icon>
<span>Logout</span>
</button>
</span>
</mat-menu>
</div>
typescript:
public name;
public isLoggedIn = false;
constructor(public router: Router, private cartService: CartService, public sidenavMenuService:SidebarMenuService) {
this.checkLogin();
this.name = Cookie.get('userName');
}
public checkLogin(): any {
if(Cookie.get('authtoken')) {
this.isLoggedIn = true;
}
}
You don't need to make things complicated, when you logged in your logged in guard (i.e. auth guard).
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router , private sideMenuService: SideMenuService) {}
canActivate(): boolean {
if (!this.auth.isAuthenticated()) {
this.sideMenuService.sideMenuData.next({...data}); // so here you can dispatch the side menu service data .
this.router.navigate(['dashboard']); // here after authentication it
will redirect to your dashboard
page
return false;
}
return true;
}
}
}
so after redirect when you land on the Dashboard Page , in the Dashboard component you have also inject the sideMenu Service and subscribe the BehaviourSubject menu data field .
public name;
public isLoggedIn = false; // here you don't need to check login
// because you come here from auth guard
constructor(public router: Router, private cartService: CartService,
public sidenavMenuService: SidebarMenuService) {
this.checkLogin(); // same no need to check login in each
component if you use auth guard
this.name = Cookie.get('userName');
}
public ngOnInit(){
this.sideMenuService.sideMenuData.subscribe((data)=>{
// hered you get the data dynamic , you can assign to any
// component field.
});
}
public checkLogin(): any {
if(Cookie.get('authtoken')) {
this.isLoggedIn = true;
}
}
so that's how whenever you login every time you dispatch some dynamic data and your behaviourSubject will get updated and where ever you subscribe like in Dashboard component you will get the dynamic data.
Hope it will help.
The constructor is executed only one time during the creation of the page.
constructor(public router: Router, private cartService: CartService, public sidenavMenuService:SidebarMenuService) {
this.checkLogin();
this.name = Cookie.get('userName');
}
Now, according to the code, if the cookie authtoken is not found during the construction, there is no way your app to know if that was created by another (login) process.
You should call the checkLogin function and the name assignment right after your login cocmpletes.
I need to fetch my current route (using the Router) in a component (my Nav component) which is located in the App Component but as it's already loaded, it's not refreshing on a click and my function in the nav component isn't returning any new URL.
How can I manage to have the new URL with my nav component ?
Here is my app component :
<app-nav></app-nav>
<body>
<div class="container">
<router-outlet></router-outlet>
</div>
</body>
Here is my nav component (.ts) :
ngOnInit() {
console.log(this.router.url);
if(this.router.url == "/") {
this.color = "large";
this.logoPath = "assets/logos/w-logo-full.png";
} else {
this.color = "small";
this.logoPath = "assets/logos/c-logo-full.png";
}
It was working when my app-nav was in every component but it's not working anylonger since I've moved it..
you can use router service events observable
app.component
constructor( public router: Router,) {
}
public ngOnInit(): void {
this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
console.log(this.router.url);
// ...
}
});
}
NavigationEnd An event triggered when a navigation ends successfully.
You need subscribe ActivatedRoute service
something like this:
Add ActivatedRoute:
constructor(
protected route: ActivatedRoute,
...) {
}
Add add subscribe :
this.route.url.subscribe(value => {
....
});
import {
ActivatedRoute
} from '#angular/router';
export class AppComponent implements OnInit {
constructor(private route: ActivatedRoute) {
console.log("current route is " + route);
}
}
I have a component and then another component which is using it, and data is being parsed through to each other by means of a tab changing, what I am trying to do is when the page is left but the back button is pressed that the tab is remembered, to be able to do it I need to understand the data flow, the functions are identical so It's difficult to determine what is going on!
For instance, they are both using #Input and when I press a tab, both functions console if I add one in?
the component:
<div class="EngagementNavigationSecondary-Items"
*ngIf="!selectedIndividual">
<span
*ngFor="let item of navList; let i = index"
class="EngagementNavigationSecondary-Item"
[ngClass]="{
'EngagementNavigationSecondary-Item--Active': selectedItem === i,
'EngagementNavigationSecondary-Item--One' : navList.length === 1,
'EngagementNavigationSecondary-Item--Two' : navList.length === 2,
'EngagementNavigationSecondary-Item--Three' : navList.length === 3
}"
(click)="clickTab(i)">
{{ item.title | translate }}
</span>
</div>
<div *ngIf="selectedIndividual">
<span class="EngagementNavigationSecondary-Item" (click)="goBack()">
<tl-icon
size="18"
name="chevron-left">
</tl-icon>
{{ 'Go back' | translate }}
</span>
</div>
the logic:
export class EngagementNavigationSecondaryComponent implements OnInit {
#HostBinding('class.EngagementNavigationSecondary') block = true;
#Input() navList: EngagementNavigationItem[];
#Input() selectedItem: number = 0;
#Input() selectedIndividual: string;
#Output() change: EventEmitter<number> = new EventEmitter<number>();
#Output() deselectIndividual: EventEmitter<boolean> = new EventEmitter<boolean>();
className: string;
ngOnInit() {
this.className = `EngagementNavigationSecondary-Item${this.navList.length}`;
}
goBack() {
this.deselectIndividual.emit(true);
}
clickTab($event: number) {
this.selectedItem = $event;
this.change.emit($event);
}
}
and now this component being used - within the side container component:
<tl-engagement-navigation-secondary
*ngIf="navList"
[navList]="navList"
[selectedItem]="selectedTab"
[selectedIndividual]="selectedIndividual"
(change)="tabChange($event)"
(deselectIndividual)="selectedIndividual = undefined">
</tl-engagement-navigation-secondary>
logic:
export class EngagementSideContainerComponent implements OnChanges, OnInit {
#Input() focus: EngagementGraphNode;
#Input() selectedTab: number;
#Output() change: EventEmitter<number> = new EventEmitter<number>();
public navList: EngagementNavigationItem[];
public selectedIndividual: EngagementUser;
constructor(
private urlService: UrlService,
private router: Router,
private mixPanelService: MixPanelService,
private engagementService: EngagementService
) { }
ngOnInit() {
this.navList = this.getNavList();
}
tabChange(event) {
this.change.emit(event);
}
as you can see they are basically identical, so when I click on a tab, is the original component being called and then this is parsing data to the side container component? I think its important to understand so I can actually create the solution.
thanks if you can help!