Angular 2+ initialize value of input - html

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>

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>```

Passing data (which got from backend API) between component got undefined in Angular

I have my parent components like this to get list of product by calling HTTP request and it works fine:
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { ApiServiceService } from 'src/app/api-service.service';
import { Injectable } from '#angular/core';
#Component({
selector: 'app-show-products',
templateUrl: './show-products.component.html',
styleUrls: ['./show-products.component.css']
})
export class ShowProductsComponent implements OnInit {
ProductsList:any =[];
pro:any;
ActiveEdit: boolean=false;
constructor(private service: ApiServiceService) {}
ngOnInit(): void{
this.refreshProductsList();
}
refreshProductsList(){
this.service.GetProductList().subscribe(data => {
this.ProductsList=data;
})
}
EditProductClick(product:any){
this.pro=product;
this.ActiveEdit=true;
}
}
And parent html like
<button type="button" class="btn btn-light mr-1">
Create Product
</button>
<table class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>ReleaseDate</th>
<th>Discontinued</th>
<th>Rating</th>
<th>Price</th>
<th>Option</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let dataProduct of ProductsList">
<td>{{dataProduct.id}}</td>
<td>{{dataProduct.name}}</td>
<td>{{dataProduct.description}}</td>
<td>{{dataProduct.releaseDate}}</td>
<td>{{dataProduct.discontinued}}</td>
<td>{{dataProduct.rating}}</td>
<td>{{dataProduct.price}}</td>
<td>
<button type="button" routerLink="Edit-Product" (click)="EditProductClick(dataProduct)" class="btn btn-light mr-1">
Edit
</button>
<router-outlet></router-outlet>
<button type="button" class="btn btn-light mr-1">
Delete
</button>
</td>
</tr>
</tbody>
</table>
<div class="modal-body" style="display: none;" >
<app-edit-products [Pro]="pro"></app-edit-products>
</div>
And the problem is even I try to pass data by this way <app-edit-products [Pro]="pro"> I can get this data in child component, it is undefined in the console when I run the app
#Component({
selector: 'app-edit-products',
templateUrl: './edit-products.component.html',
styleUrls: ['./edit-products.component.css']
})
export class EditProductsComponent implements OnInit {
constructor() { }
#Input() Pro:any;
ngOnInit(): void {
console.log(this.Pro); <= this is undefined
}
Did I do something wrong or is there any other way to pass data?
I suggest to use some flag like below.
// Define isEdit variable first...
EditProductClick(product:any){
this.isEdit = true;
this.pro=product;
console.log(this.pro); // Check this comes proper or not...
this.ActiveEdit=true;
}
Then use this flag in HTML like this
<div class="modal-body" style="display: none;" *ngIf="isEdit">
<app-edit-products [Pro]="pro"></app-edit-products>
</div>
You initialize your component even when you don't have an item selected.
You could do this:
<div class="modal-body" style="display: none;">
<app-edit-products *ngIf="pro" [Pro]="pro"></app-edit-products>
</div>
Or you chould implement the OnChanges interface.
<div class="modal-body" style="display: none;" >
<app-edit-products [Pro]="pro"></app-edit-products>
</div>
call to app-edit-products is in sync and executing before you are assigning value to this.pro=product; in the method EditProductClick(product:any)
So you can achieve what you want in one way that set a flag in EditProductClick(product:any) method as below
this.proFlag = true; and so your html call will be like below
<div class="modal-body" style="display: none;">
<ng-container *ngIf="proFlag">
<<app-edit-products [Pro]="pro"></<app-edit-products>
</ng-container>
</div>

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

How to call parent function in child button

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);
});

Ng bootstrap modal don't open

I've had this problem since this morning and I can't solve it, I've tried a lot of things but there's no way I can get it opened.
In practice I have to implement a test button that opens a modal with a header and a body, in addition there must be a closing and a disengagement button. This part of HTML works and I tested it, the problem is the part of TypeScript.
In the open function there is the command const modalRef = this.modalService.open(NgbdModalContent); that doesn't work correctly, the fact is that the command works, in fact if instead of NgbdModalContent I put a string works, so it's just NgbdModalContent that should theoretically take HTML.
If you have any idea how to do this would be of immense pleasure, below I leave you the files concerned.
PS. are new to this world, especially about Angular2 and TypeScript so criticism and comments are welcome (I know I've made a big mistake and probably a trivial one (for you)). THANK YOU SO MUCH
TypeScript
import { Component, Input } from '#angular/core';
import {NgbModal, NgbActiveModal} from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'post-ngb-modal-demo',
templateUrl: './modal-demo.component.html',
})
export class NgbModalDemoComponent{
constructor(private modalService: NgbModal) {}
open() {
const modalRef = this.modalService.open(NgbdModalContent);
}
}
export class NgbdModalContent {
#Input() name;
constructor(public activeModal: NgbActiveModal) {}
}
HTML
<ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Hi there!</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Hello, world!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Close click')">Close</button>
</div>
</ng-template>
<button class="btn btn-lg btn-outline-primary" (click)="open()">Launch demo modal</button>
I think you need to add dynamically created components to entryComponents inside your #NgModule in your app.module.ts.
#NgModule({
imports: [
....
...
],
declarations: [
NgbdModalContent,
...
],
entryComponents: [NgbdModalContent],
providers: [
....
]
})