How to call parent function in child button - html

Suppose I have a component called ButtonComponent which will be used in various places in the application, so I make is as generic as possible, like so:
button.component.ts
import { Component, ViewEncapsulation, Input, Output, EventEmitter } from '#angular/core';
import { FormGroup } from '#angular/forms';
#Component({
selector: 'app-button',
templateUrl: './button.component.html',
styleUrls: ['./button.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ButtonComponent{
#Input() group: FormGroup;
#Input() type: string;
#Input() description: string;
#Input() class: string;
#Input() callFunction: Function;
}
button.component.html
<div [formGroup]="group">
<button type="{{ type }}" class="{{ class }}" (click)="callFunction()">{{ description }}</button>
</div>
Now my button is completely customizable (in theory). I am now going to import it to a login component which has a function called login(). I want my button instance to run this specific function when I click it:
login.component.ts
//imports
/**
* This component is rendered at the start of application, it provides the UI
* & functionality for the login page.
*/
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
/**
* This class is used to build a login form along with initialization of validators
* as well as authenticate the user, and reroute upon success
*/
export class LoginComponent implements OnInit, AfterContentInit{
#ViewChild('login', { read: ViewContainerRef }) login_button;
/**
* This property initializes the formGroup element.
*/
userForm: FormGroup;
/**
* The constructor initializes Router, FormBuilder, OauthService, LoggerService, ToastrService
* & TranslatePipe in the component.
*/
constructor(//initializations
) { }
/**
* This is the hook called on the initialization of the component, it initializes
* the form.
*/
ngOnInit() {
this.buildForm();
}
/**
* This method initialized the the formGroup element. Its properties and the validators.
*
* #method buildForm
* #return
*/
buildForm() {
// validations
});
}
/**
* This method returns the values of the form controls.
*
* #return
*/
get form() { return this.userForm.controls; }
/**
* This method is triggered on success, it reroutes the user to main page.
*
* #return
*/
onSuccess() {
let result = this.translate.transform("pages[login_page][responses][success]");
this.logger.info(result);
this.toastr.success(result);
this.router.navigate(['main']);
}
/**
* This method is triggered when user clicks log-in, it calls the aunthenication method
* from oauth service.
*
* #return
*/
login() {
this.oauth.authenticateUser(this.form.username.value, this.form.password.value, this.onSuccess.bind(this));
}
ngAfterContentInit() { //here I build my login button instance after init
this.buildLoginButton();
}
/**
* This function builds the login button, imports the ButtonComponent
*
*/
buildLoginButton(){
let data = {
type: "button",
class: "btn btn-primary px-4",
description: this.translate.transform("pages[login_page][login_form][buttons][login]"),
function: "login",
group: this.userForm
}
const inputFactory = this.resolver.resolveComponentFactory(ButtonComponent);
const loginButton = this.login_button.createComponent(inputFactory);
loginButton.instance.group = data.group;
loginButton.instance.type = data.type;
loginButton.instance.class = data.class;
loginButton.instance.description = data.description;
loginButton.instance.callFunction = function(){ //I call parent function using a static method
LoginComponent.executeMethod(data.function);
}
}
static executeMethod(someMethod){ //for my login button this should return this.login()
eval("this."+someMethod+"()");
}
}
To make the button instance visible I add the reference into my login template like this:
<div #login></div>
Now my button is visible, great! But now when i click the button:
ERROR TypeError: this.login is not a function
at eval (eval at push../src/app/views/login/login.component.ts.LoginComponent.executeMethod
(login.component.ts:225), :1:6)
at Function.push../src/app/views/login/login.component.ts.LoginComponent.executeMethod
(login.component.ts:225)
at ButtonComponent.loginButton.instance.callFunction (login.component.ts:179)
at Object.eval [as handleEvent] (ButtonComponent.html:2)
at handleEvent (core.js:10251)
at callWithDebugContext (core.js:11344)
at Object.debugHandleEvent [as handleEvent] (core.js:11047)
at dispatchEvent (core.js:7710)
at core.js:8154
at HTMLButtonElement. (platform-browser.js:988)
How do I make my button run the function in the parent component instead of looking for the function within itself? I don't want to change a lot in the ButtonComponent that would make it less generic as I have to make other buttons as well that would probably run other functions.
There was a solution that stated using EventEmitter for this, but I am unsure how this would work given how I am importing the button into the login component, both the ts and the html
Edit the complete login.component.html:
<div class="app-body">
<main class="main d-flex align-items-center">
<div class="container center">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card-group">
<div class="card p-4">
<div class="card-body">
<form [formGroup]="userForm" (submit)="login()">
<h1>{{ 'pages[login_page][login_form][labels][login]' | translate }}</h1>
<p class="text-muted">{{ 'pages[login_page][login_form][labels][sign_in]' | translate }}</p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="icon-user"></i></span>
</div>
<div #username> </div>
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text"><i class="icon-lock"></i></span>
</div>
<div #password> </div>
</div>
<div class="row">
<div class="col-6">
<div #login></div>
<!-- <button type="button" class="btn btn-primary px-4" (click)="login()">{{ 'pages[login_page][login_form][buttons][login]' | translate }}</button> -->
</div>
<div class="col-6 text-right">
<div #forgot></div>
<!-- <button type="button" class="btn btn-link px-0">{{ 'pages[login_page][login_form][urls][forgot_password]' | translate }}</button>-->
</div>
</div>
</form>
</div>
</div>
<div class="card text-white bg-primary py-5 d-md-down-none" style="width:44%">
<div class="card-body text-center">
<div>
<h2>{{ 'pages[login_page][sign_up_panel][labels][sign_up]' | translate }}</h2>
<p>{{ 'pages[login_page][sign_up_panel][labels][new_account]' | translate }}</p>
<div #signUp></div>
<!-- <button type="button" class="btn btn-primary active mt-3">{{ 'pages[login_page][sign_up_panel][buttons][register]' | translate }}</button> -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>

Add this code in button.component.ts
#Output() clickFunctionCalled = new EventEmitter<any>();
callFunction() {
this.clickFunctionCalled.emit();
}
No change in button.template.html
Add this code where you use app-button component in html
<app-button (clickFunctionCalled)="callCustomClickFunction($event)"></app-button>
Add this in login.component.ts
callCustomClickFunction() {
console.log("custom click called in login");
this.login();
}
Basically, emit the click event from the child component. Catch the event in the parent component and call the desired function of the parent component.
You can also directly call the parent component's function like this
<app-button (clickFunctionCalled)="login($event)"></app-button>
As you are using dynamic component creator for creating the button component, you need to do something like this, for binding output event
loginButton.instance.clickFunctionCalled.subscribe(data => {
console.log(data);
});

Related

initialize html tag from angular type-script

as shown in the below angular type script code, i would like to refer to the divisions mentioned in the below posted .html code using document.getElementById
the result of the log statement is null
please let me know how correctly to referece an html-tag in type-script
.ts:
export class GridCellPopupOverlayComponent implements OnInit {
isVisible = true
container: any
content
closer: any
overlay: any
AoC: any
AvgH: any
Dist: any
I: any
constructor() {
}
initHTMLElements() {
console.log("html init")
this.container = document.getElementById('idGridCellInfoPopupDiv');
this.AoC = document.getElementById('idGridCellInfoAoCValueDiv');
this.AvgH = document.getElementById('idGridCellInfoAvgHValueDiv');
this.Dist = document.getElementById('idGridCellInfoDistValueDiv');
this.I = document.getElementById('idGridCellInfoIValueDiv');
this.closer = document.getElementById('gridCellInfoPopup-closer');
console.log("this.AoC:",this.AoC)
}
}
html:
<div *ngIf="isVisible" id="idGridCellInfoPopupDiv" class="ol-popup">
<!-- <span id="idGridCellLabel" class="label label-success">dsfdsfsa</span> -->
<div class="alert alert-success alert-sm" role="alert">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<clr-icon class="alert-icon" shape="check-circle"></clr-icon>
</div>
<div id="idGridCellAlertText"class="alert-text">
</div>
</div>
</div>
<!-- <button type="button" class="close" aria-label="Close">
<clr-icon aria-hidden="true" shape="close"></clr-icon>
</button> -->
</div>
<div id="idGridCellInfoAoCValueDiv"></div>
<div id="idGridCellInfoAvgHValueDiv"></div>
<div id="idGridCellInfoDistValueDiv"></div>
<div id="idGridCellInfoIValueDiv"></div>
You can get the elements from the .html by using #ViewChild/#ViewChildren decorators. Behind the scenes they are using document.getElementById. This is the correct way in Angular.
Also watch out for ngAfterViewInit lifeycle method in which you can access your references. View queries are set before the ngAfterViewInit callback is called. (form Angular documentation)
Here is the reference: https://angular.io/api/core/ViewChild
Btw, you can omit static: false since it's default.
TS file
import { HelloComponent } from './hello.component';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit {
name = 'Angular';
#ViewChild('pRef', {static: false}) pRef: ElementRef;
ngAfterViewInit() {
console.log(this.pRef.nativeElement.innerHTML);
this.pRef.nativeElement.innerHTML = "DOM updated succesfully!!!";
}
}
Template file
<hello name="{{ name }}" ></hello>
<p #pRef>
Start editing to see some magic happen :)
</p>```

Angular add toggle switch inside datepicker calendar

Is it possible to add the toggle switch inside the calender? I want to put it at the top of the calender when the calendar is open so the user can choose to show date details by the toggle switch
<form #uploadForm="ngForm" (keydown.enter)="$event.preventDefault()">
<div class="input-wrapper">
<mat-form-field>
<input
id="date"
name="date"
[disabled]="datePickerDisabled || uploadForm.submitted"
[matDatepicker]="datepicker"
placeholder="Expiration date"
autocomplete="off"
matInput
required />
<mat-datepicker-toggle matSuffix [for]="datepicker"></mat-datepicker-toggle>
<mat-datepicker touchUi #datepicker></mat-datepicker>
</mat-form-field>
<div class="tooltip">This field is required.</div>
<mat-slide-toggle
(change)="setMaxExpirationDate($event)">Show Date Details</mat-slide-toggle>
</div>
</form>
I think you has two aproachs
1.-Customize the header, see the docs:customizing header
From this SO answer. replicate the header:
/** Custom header component for datepicker. */
#Component({
selector: 'example-header',
template: `
<mat-slide-toggle
(change)="setMaxExpirationDate($event)">Show Date Details</mat-slide-toggle>
<div class="mat-calendar-controls">
<button mat-button type="button" class="mat-calendar-period-button"
(click)="currentPeriodClicked()" [attr.aria-label]="periodButtonLabel"
cdkAriaLive="polite">
{{periodButtonText}}
<div class="mat-calendar-arrow"
[class.mat-calendar-invert]="calendar.currentView != 'month'"></div>
</button>
<div class="mat-calendar-spacer"></div>
<ng-content></ng-content>
<button mat-icon-button type="button" class="mat-calendar-previous-button"
[disabled]="!previousEnabled()" (click)="previousClicked()"
[attr.aria-label]="prevButtonLabel">
</button>
<button mat-icon-button type="button" class="mat-calendar-next-button"
[disabled]="!nextEnabled()" (click)="nextClicked()"
[attr.aria-label]="nextButtonLabel">
</button>
</div>
</div> `,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExampleHeader extends MatCalendarHeader<any> {
/** Handles user clicks on the period label. */
currentPeriodClicked(): void {
this.calendar.currentView = this.calendar.currentView == 'month' ? 'multi-year' : 'month';
}
}
(change the .html to add the controls you want)
2.-Enclose the mat-datepicker in a mat-menu, like it showed in this another SO answer
Updated Access to an element of header it's not very easy. but control the toogle in easy if we use an intermediate service
Imagine you has a service like
#Injectable({
providedIn: "root"
})
export class CalendarService {
private _event = new Subject<void>();
public onEvent = this._event as Observable<any>;
constructor() {}
command(value: any) {
this._event.next(value);
}
}
You can inject the service in the constructor of customHeader
constructor(
private _calendar: MatCalendar<D>,
private _dateAdapter: DateAdapter<D>,
#Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
cdr: ChangeDetectorRef,
private service: CalendarService
)
Then, if our toogle call a function
<mat-slide-toggle #toogle (change)="toogleChange($event)">
Show Date Details
</mat-slide-toggle>
The function becomes like
toogleChange(event: any) {
this.service.command(event);
}
Just in ngOnInit in the component subscribe to the service
ngOnInit() {
this.service.onEvent.subscribe(res => {
console.log(res.checked);
});
}
You can see in stackblitz

Angular 7 components: how to detect a change by user interaction?

I have a component with several checkboxes, drop-downs, and a save button.
Here is a simplified example component template:
<aside class="container">
<div class="row">
<input
type="checkbox"
id="all-users"
[(ngModel)]="showAllUsers"
(ngModelChange)="onChange($event)"
/>
<label for="all-users">Show all users</label>
</div>
<div class="row">
<ng-select
[(ngModel)]="selectedUser"
[clearable]="false"
appendTo="body"
(change)="onChange($event)"
>
<ng-option *ngFor="let user of activeUsers" [value]="user">{{ user }}</ng-option>
</ng-select>
</div>
<div class="row">
<button type="button" class="btn btn-primary" [disabled]="!dirty" (click)="onSave()">
Save Changes
</button>
</div>
</aside>
I want to enable the Save Changes button only when the user made a change, either by unchecking the check-box or changing a selection in drop-down box.
Right now I have an event handler registered at each and every control in the component (the onChange function in the example above), and use a dirty flag to disable or enable the Save Changes button.
Here is the component.ts for the above template:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-filter',
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.css']
})
export class FilterComponent implements OnInit {
dirty: boolean;
showAllUsers: boolean;
selectedUser: string;
activeUsers: string[];
ngOnInit() {
this.dirty = false;
this.showAllUsers = true;
this.activeUsers = ['Thanos', 'Thor', 'Starlord'];
this.selectedUser = 'Thor';
}
onChange(event) {
console.log('Event is ' + event);
this.dirty = true;
}
onSave() {
console.log('Gonna save changes...');
this.dirty = false;
}
}
Registering the event handler to every control does not seem intuitive to me.
Is this the correct approach to figure out a change made by user or does angular provide a different way to achieve this?
I would highly recommand using both FormGroup and FormControl to achieve this behavior.
Both exposes the dirty property, a read-only boolean.
The dirty property is set to true when the user changes the value of the FormControl from the UI. In the case of the FormGroup, the dirty property is set to true as long as at least 1 of the FormControl in that group is dirty.
As a side note, the property pristine is the opposite property. So you can use one or the other if it simplifies the condition.
[disabled]="myFormGroup.pristine" might be easier to read than [disabled]="!myFormGroup.dirty".

How to stop property binding to every element in Angular within *ngFor

In my html, I want to apply property binding to every element.
I have a click and hover event that I want to do whenever the user
hovers or clicks on an individual element. But right now the hover or
click happens to every element within the *ngFor. I want it to only
happen on the element they are selecting/hovering over. What do I need
to change?
I saw another stackoverflow answer and they simply applied the name
within the for loop (ex: *ngFor="let article of articles" and they
used article) in front of the boolean/variable they were setting.
Like my boolean is favorite so they did article.favorite within
the element and it apparently worked, but that method doesn't work for
me.
Code:
<div class="row">
<!--/span-->
<div class="col-6 col-sm-6 col-lg-4"
*ngFor="let article of articles">
<h2>{{article.title}}</h2>
<h4>By: {{article.author}}</h4>
<p>{{article.body}}</p>
<div class="col-lg-4">
<button type="button" class="btn btn-default" (click)="addFavorite()"
(mouseenter)="hoverFavorite()"
(mouseleave)="removeHoverFavorite()">
<span
class="glyphicon"
[class.glyphicon-heart]="favorite"
[class.glyphicon-heart-empty]="!favorite"
aria-hidden="true"></span> Favorite
</button>
</div>
<div class="col-lg-4">
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Comment
</button>
</div>
<div class="col-lg-4">
<button type="button" class="btn btn-info pull-left" [routerLink]="['/articles', article.articleId]">Read More »
</button>
</div>
</div>
</div>
<!--/row-->
Adding component
import { Component, OnInit } from '#angular/core';
import {ArticlesService} from "./articles.service";
import {Article} from "./article.model";
import {Router} from "#angular/router";
#Component({
selector: 'app-articles',
templateUrl: './articles.component.html',
styleUrls: ['./articles.component.css']
})
export class ArticlesComponent implements OnInit {
articles: Article[];
// if favorite = false then full heart is not shown. if true then heart shown
favorite: boolean = false;
// clicked will be used to determine if we should keep hovering effect on
clicked: boolean = false;
constructor(private router: Router, private articleService: ArticlesService) { }
ngOnInit() {
this.articleService.getArticles()
.subscribe(
(articles: Article[]) => {
this.articles = articles;
}
);
}
addFavorite(){
// toggle full and empty heart
this.clicked = !this.clicked;
if (this.clicked === true){
// if clicked then add to database and show full heart
this.favorite = true;
} else { // if false then remove from database and show empty heart
this.favorite = false;
}
}
hoverFavorite(){
// if clicked is false then show hover effect, else dont
if (this.clicked === false){
this.favorite = true;
}
}
removeHoverFavorite(){
// if clicked is false then show hover effect, else dont
if (this.clicked === false){
this.favorite = this.favorite = false;
}
}
}
You can use the index
*ngFor="let article of articles; let i=index"
(click)="addFavorite(i)"
// or
(click)="addFavorite(article)"
(mouseenter)="hoverFavorite = i"
(mouseleave)="hoverFavorite = -1"
[class.glyphicon-heart]="favorite === i"
[class.glyphicon-heart-empty]="favorite !== i"
The reason you're not getting the desired result is because defined your boolean variables in the ArticlesComponent, which is the component rendering the list of articles. Thus, if the variables become true, it would be true for all the articles, instead of a single one. To fix it you should define all of the content within the ngFor loop as its own component, and in that component you would set those boolean variables. That way, each instance of an ArticleComponent would have its own variables and they wouldn't interfere with each other.

Angular 2+ initialize value of input

I am trying to implement a Component which corresponds to a Bootstrap modal including an input. The input is hooked to a variable in the Component class via [(ngModel)]="..." This works if I enter text inside the input (the variable's value gets updated).
What I want to do is that when this component's show() method gets called the input should be populated with text passed in as a parameter. This does not seem to work and I can't figure out how I can set the initial text passed in as a parameter (without using jQuery).
Here's the relevant code:
editdialog.component.html
<div id="edit_todo_modal" class="modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit todo</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Editing todo: {{currentText}}</p>
<div class="row">
<div class="col-md-12">
<!-- THIS IS WHERE I WANT THE INITAL TEXT -->
<input id="edit-todo-modal-input" type="text" class="form-control" [(ngModel)]="currentText">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">Save changes</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
editdialog.component.ts
import { Component } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { ListComponent } from './list.component';
import { Injectable } from '#angular/core';
declare var jQuery : any;
#Injectable()
#Component({
selector: 'edit-todo-dialog',
templateUrl: './editdialog.component.html',
styleUrls: ['./editdialog.component.css']
})
export class EditTodoDialogComponent{
currentText: string = "";
index: number;
/* I want to use this method to set the initial value */
show(index: number, text: string): void {
this.currentText = text;
this.index = index;
jQuery("#edit-todo-modal-input").val(this.currentText); // I don't want to use jQuery for showing the initial value, however this works
jQuery("#edit_todo_modal").modal(); // show bootstrap modal
}
}
Thanks in advance.
UPDATE
The show()method gets called from this component
import { Component } from '#angular/core';
import { ListService } from './list.service';
import { OnInit } from '#angular/core';
import { EditTodoDialogComponent } from './editdialog.component';
/**
* The main todo list component
*/
#Component({
selector: 'list-component',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css'],
providers: [ListService, EditTodoDialogComponent]
})
export class ListComponent implements OnInit {
private listService: ListService;
private editTodoDialog: EditTodoDialogComponent;
/* ... */
constructor(listService: ListService, editTodoDialog: EditTodoDialogComponent) {
this.listService = listService;
this.editTodoDialog = editTodoDialog;
}
ngOnInit(): void {
this.getTodos();
}
/* ... */
// TO BE IMPLEMENTED
showEditTodoDialog(index: number) : void {
this.editTodoDialog.show(index, this.todos[index]);
}
}
The event is hooked like this:
<li class="list-group-item" *ngFor="let todo of todos; let i = index">
<div class="todo-content">
<p class="todo-text" (dblclick)="showEditTodoDialog(i)">
{{todo}}
</p>
</div>
<div class="todo-close">
<button (click)="removeTodo(i)" class="btn btn-danger btn-sm">
<i class="fa fa-remove"></i>
</button>
</div>
</li>
The problem is that you are calling the show from ListComponent by using the componentReference.
You should not do that to pass information between components .
You should either use a #Input and #Output i:e Event Emitters if these component have Parent child relationship else the best way is to go for Shared Services where once you load he data to the service the other component is notified of the change and subscribes to the new data.
More info on how to use parent child link
More info on how to use shared serviceslink
Have you tried value?:
<input id="edit-todo-modal-input" type="text" class="form-control" [value]="currentText" ngModel>
For objects, use ngValue:
<input id="edit-todo-modal-input" type="text" class="form-control" [ngValue]="currentText" ngModel>