Attempting to simulate a mouse click on one button, by clicking on another button. The goal is to reuse the behavior of a single custom button throughout the page. Why is the dispatchEvent not working?
How can a click on <c-custom-button> be simulated?
parentApp.html
<template>
<div>
<c-custom-button
label="New">
</c-custom-button>
</div>
<div>
<lightning-button
label="Call New"
onclick={simulateClick}>
</lightning-button>
</div>
</template>
parentApp.js
import { LightningElement, track, api } from 'lwc';
export default class App extends LightningElement {
cButtonElement;
simulateClick() {
this.cButtonElement = this.template.querySelector('c-custom-button');
let clickEvent = new CustomEvent('click');
this.cButtonElement.dispatchEvent(clickEvent);
}
}
customButton.html
<template>
<lightning-button
label={label}
icon-name="utility:new"
onclick={handleClick}>
</lightning-button>
</template>
customButton.js
import { LightningElement, track, api } from 'lwc';
export default class App extends LightningElement {
#api label;
handleClick() {
this.label = 'CLICKED!'
}
}
Thanks to Nathan Shulman for helping with this.
Add call to child method in parentApp.js
simulateClick() {
this.cButtonElement = this.template.querySelector('c-custom-button');
this.cButtonElement.handleClick();
}
Add #api decorator to method in customButton.js
#api handleClick() {
this.label = 'CLICKED!'
}
Related
I want to test whether a modal opens up or not with bunit. The problem is, that the modal doesn't get rendered. How to open a blazored modal with bunit?
Modal Creation in my component under test:
<div style="display: flex; justify-content: flex-end">
<button class="btn btn-success
btn-lg"
id="openModalButton"
#onclick="CheckOpenModal">
Hinzufügen
</button>
</div>
#code
{
[CascadingParameter] public IModalService Modal { get; set; }
private async Task OpenModalForCreation()
{
List<string> ParameterA = new List<string>();
var parameters = new ModalParameters();
parameters.Add(nameof(CreationModal.ParameterA), ParameterA);
Modal.Show<CreationModal>("Create something", parameters);
}
}
My TestClass:
public class PrivateMachinesCompTest : TestContext
{
public CompTest()
{
Services.AddBlazoredModal();
}
[Fact]
public void CheckOpenModal()
{
modalService = new ModalService();
var cut = RenderComponent<ComponentUnderTest>(parameters => parameters
.AddCascadingValue(modalService));
var openModalButton = cut.Find("#openModalButton");
openModalButton.Click();
cut.MarkupMatches("Create something");
}
The problem is that you are not rendering the component that actually does the rendering. Just passing in an IModalService doesn't do it.
My approach would be to create a mock of IModalService and assert that the expected method on it is called.
I have create one custom object. Using a LWC component, I try to create one record but when try to save it from apex, only ID is getting printed not the Name.
I am not getting why only Id is getting printed not the name.
Could anybody please help me ? Would be Appreciable.
LWC Component
import { LightningElement, track, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import insertDe from '#salesforce/apex/insertEvent.insertDe';
import Detail_OBJECT from '#salesforce/schema/Detail__c';
export default class insertEvent extends LightningElement {
// #api childName;
#track conRecord = Detail_OBJECT;
handleChildNameChange(event) {
this.conRecord.childName = event.target.value;
}
createRec() {
insertDe({
de: this.conRecord
})
.then(result => {
// Clear the user enter values
this.conRecord = {};
// Show success messsage
this.dispatchEvent(new ShowToastEvent({
title: 'Success!!',
message: 'Contact Created Successfully!!',
variant: 'success'
}), );
})
.catch(error => {
this.error = error.message;
});
}
}
<template>
<lightning-card title="Create Contact Record">
<template if:true={conRecord}>
<div class="slds-m-around--xx-large">
<div class="container-fluid">
<div class="form-group">
<lightning-input
label="Child Name"
name="childName"
type="text"
value={conRecord.childName}
onchange={handleChildNameChange}
></lightning-input>
</div>
</div>
<br />
<lightning-button label="Submit" onclick={createRec} variant="brand"></lightning-button>
</div>
</template>
</lightning-card>
</template>
Apex code
public with sharing class insertEvent {
#AuraEnabled
public static void insertDe(Detail__c de) {
try {
insert de;
} catch (Exception e) {
System.debug('--->'+e);
}
}
}
If you're using an LWC component then I suggest to also use Lightning Data Service.
To answer your specific issue, after an insert DML, only the Id field is returned. If you need other fields, then you need to run a query. This is because trigger / workflow / process builder can change some field value.
My suggestion if you want insert record directly from LWC component, you should use Lightning Data Service. But you need to execute some custom code or insert record from apex method, then you should pass only the data LWC component and create object in apex method then insert it.
public static void insertDe(String name) {
Detail__c obj = new Detail__c();
obj.childName = name;
try {
insert obj;
} catch (Exception e) {
System.debug('--->'+e);
}
}
Only pass the name from lwc component according to your posting code.
I am having much difficulties implementing RadListView in my application. According to documentation the ListView accepts items as an ObservableArray, but regardless of whether that or plain array is used, items are not being displayed. I prepared a sample app in the Playground
https://play.nativescript.org/?template=play-ng&id=39xE2X&v=11
Component is like this:
import { Component, ViewChild, OnInit, ChangeDetectorRef, ElementRef } from "#angular/core";
import { GestureTypes, PanGestureEventData, TouchGestureEventData } from "tns-core-modules/ui/gestures";
import { ObservableArray } from "tns-core-modules/data/observable-array";
#Component({
selector: "ns-app",
moduleId: module.id,
templateUrl: "./app.component.html",
})
export class AppComponent implements OnInit {
public gridData = []; //new ObservableArray<any>();
constructor(
private cd: ChangeDetectorRef
) {
this.feedTestData();
}
ngOnInit() {
}
ngAfterViewInit() {
}
public detectChanges() {
this.cd.detectChanges();
}
private feedTestData() {
let data = [
{
description: 'line 1',
},
{
description: 'line 2',
},
];
this.gridData.splice(0, 0, data);
}
}
and template like this:
<GridLayout tkExampleTitle tkToggleNavButton>
<RadListView [items]="gridData">
<ng-template tkListItemTemplate let-item="item">
<StackLayout orientation="vertical">
<Label [text]="description"></Label>
</StackLayout>
</ng-template>
</RadListView>
</GridLayout>
What's perplexing me even more is that in my real application RadListView displays something, but does it on its own, completely ignoring my template. I can literally leave nothing inside RadListView, but it would still display items
I have updated your playground and it is working now.
https://play.nativescript.org/?template=play-ng&id=39xE2X&v=12
You were trying to access the description in ng-template while it should be item.description
<Label [text]="item.description"></Label>
Also for the testing purpose, I am creating an Onservable array from your data. this.gridData = new ObservableArray(data); in your feedTestData function.
I have a custom form control component (it is a glorified input). The reason for it being a custom component is for ease of UI changes - i.e. if we change the way we style our input controls fundamentally it will be easy to propagate change across the whole application.
Currently we are using Material Design in Angular https://material.angular.io
which styles controls very nicely when they are invalid.
We have implemented ControlValueAccessor in order to allow us to pass a formControlName to our custom component, which works perfectly; the form is valid/invalid when the custom control is valid/invalid and the application functions as expected.
However, the issue is that we need to style the UI inside the custom component based on whether it is invalid or not, which we don't seem to be able to do - the input that actually needs to be styled is never validated, it simply passes data to and from the parent component.
COMPONENT.ts
import { Component, forwardRef, Input, OnInit } from '#angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '#angular/forms';
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputComponent),
multi: true
}
]
})
export class InputComponent implements OnInit, ControlValueAccessor {
writeValue(obj: any): void {
this._value = obj;
}
registerOnChange(fn: any): void {
this.onChanged = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
get value() {
return this._value;
}
set value(value: any) {
if (this._value !== value) {
this._value = value;
this.onChanged(value);
}
}
#Input() type: string;
onBlur() {
this.onTouched();
}
private onTouched = () => {};
private onChanged = (_: any) => {};
disabled: boolean;
private _value: any;
constructor() { }
ngOnInit() {
}
}
COMPONENT.html
<ng-container [ngSwitch]="type">
<md-input-container class="full-width" *ngSwitchCase="'text'">
<span mdPrefix><md-icon>lock_outline</md-icon> </span>
<input mdInput placeholder="Password" type="text" [(ngModel)]="value" (blur)="onBlur()" />
</md-input-container>
</ng-container>
example use on page:
HTML:
<app-input type="text" formControlName="foo"></app-input>
TS:
this.form = this.fb.group({
foo: [null, Validators.required]
});
You can get access of the NgControl through DI. NgControl has all the information about validation status. To retrieve NgControl you should not provide your component through NG_VALUE_ACCESSOR instead you should set the accessor in the constructor.
#Component({
selector: 'custom-form-comp',
templateUrl: '..',
styleUrls: ...
})
export class CustomComponent implements ControlValueAccessor {
constructor(#Self() #Optional() private control: NgControl) {
this.control.valueAccessor = this;
}
// ControlValueAccessor methods and others
public get invalid(): boolean {
return this.control ? this.control.invalid : false;
}
public get showError(): boolean {
if (!this.control) {
return false;
}
const { dirty, touched } = this.control;
return this.invalid ? (dirty || touched) : false;
}
}
Please go through this article to know the complete information.
Answer found here:
Get access to FormControl from the custom form component in Angular
Not sure this is the best way to do it, and I'd love someone to find a prettier way, but binding the child input to the form control obtained in this manner solved our issues
In addition: Might be considered dirty, but it does the trick for me:
let your component implement the Validator interface.
2 In the validate function you use the controlcontainer to get to the outer formcontrol of your component.
Track the status of the parent form control (VALID/INVALID) by using a variable.
check for touched. and perform validation actions on your fields only when touched is true and the status has changed.
I don't seem to be able to set focus on a input field in dynamically added FormGroup:
addNewRow(){
(<FormArray>this.modalForm.get('group1')).push(this.makeNewRow());
// here I would like to set a focus to the first input field
// say, it is named 'textField'
// but <FormControl> nor [<AbstractControl>][1] dont seem to provide
// either a method to set focus or to access the native element
// to act upon
}
How do I set focus to angular2 FormControl or AbstractControl?
I made this post back in December 2016, Angular has progressed significantly since then, so I'd make sure from other sources that this is still a legitimate way of doing things
You cannot set to a FormControl or AbstractControl, since they aren't DOM elements. What you'd need to do is have an element reference to them, somehow, and call .focus() on that. You can achieve this through ViewChildren (of which the API docs are non-existent currently, 2016-12-16).
In your component class:
import { ElementRef, ViewChildren } from '#angular/core';
// ...imports and such
class MyComponent {
// other variables
#ViewChildren('formRow') rows: ElementRef;
// ...other code
addNewRow() {
// other stuff for adding a row
this.rows.first().nativeElement.focus();
}
}
If you wanted to focus on the last child...this.rows.last().nativeElement.focus()
And in your template something like:
<div #formRow *ngFor="let row in rows">
<!-- form row stuff -->
</div>
EDIT:
I actually found a CodePen of someone doing what you're looking for https://codepen.io/souldreamer/pen/QydMNG
For Angular 5, combining all of the above answers as follows:
Component relevant code:
import { AfterViewInit, QueryList, ViewChildren, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
// .. other imports
export class MyComp implements AfterViewInit, OnDestroy {
#ViewChildren('input') rows: QueryList<any>;
private sub1:Subscription = new Subscription();
//other variables ..
// changes to rows only happen after this lifecycle event so you need
// to subscribe to the changes made in rows.
// This subscription is to avoid memory leaks
ngAfterViewInit() {
this.sub1 = this.rows.changes.subscribe(resp => {
if (this.rows.length > 1){
this.rows.last.nativeElement.focus();
}
});
}
//memory leak avoidance
ngOnDestroy(){
this.sub1.unsubscribe();
}
//add a new input to the page
addInput() {
const formArray = this.form.get('inputs') as FormArray;
formArray.push(
new FormGroup(
{input: new FormControl(null, [Validators.required])}
));
return true;
}
// need for dynamic adds of elements to re
//focus may not be needed by others
trackByFn(index:any, item:any){
return index;
}
The Template logic Looks like this:
<div formArrayName="inputs" class="col-md-6 col-12"
*ngFor="let inputCtrl of form.get('phones').controls;
let i=index; trackBy:trackByFn">
<div [formGroupName]="i">
<input #input type="text" class="phone"
(blur)="addRecord()"
formControlName="input" />
</div>
</div>
In my template I add a record on blur, but you can just as easily set up a button to dynamically add the next input field. The important part is that with this code, the new element gets the focus as desired.
Let me know what you think
This is the safe method recommend by angular
#Component({
selector: 'my-comp',
template: `
<input #myInput type="text" />
<div> Some other content </div>
`
})
export class MyComp implements AfterViewInit {
#ViewChild('myInput') input: ElementRef;
constructor(private renderer: Renderer) {}
ngAfterViewInit() {
this.renderer.invokeElementMethod(this.input.nativeElement,
'focus');
}
}
With angular 13, I did it this way:
import { Component, OnInit, Input } from '#angular/core';
import { FormGroup, Validators, FormControl, FormControlDirective, FormControlName } from '#angular/forms';
// This setting is required
const originFormControlNgOnChanges = FormControlDirective.prototype.ngOnChanges;
FormControlDirective.prototype.ngOnChanges = function ()
{
this.form.nativeElement = this.valueAccessor._elementRef.nativeElement;
return originFormControlNgOnChanges.apply(this, arguments);
};
const originFormControlNameNgOnChanges = FormControlName.prototype.ngOnChanges;
FormControlName.prototype.ngOnChanges = function ()
{
const result = originFormControlNameNgOnChanges.apply(this, arguments);
this.control.nativeElement = this.valueAccessor._elementRef.nativeElement;
return result;
};
#Component({
selector: 'app-prog-fields',
templateUrl: './prog-fields.component.html',
styleUrls: ['./prog-fields.component.scss']
})
export class ProgFieldsComponent implements OnInit
{
...
generateControls()
{
let ctrlsForm = {};
this.fields.forEach(elem =>
{
ctrlsForm[elem.key] = new FormControl(this.getDefaultValue(elem), this.getValidators(elem));
});
this.formGroup = new FormGroup(ctrlsForm);
}
...
validateAndFocus()
{
if (formGroup.Invalid)
{
let stopLoop = false;
Object.keys(formGroup.controls).map(KEY =>
{
if (!stopLoop && formGroup.controls[KEY].invalid)
{
(<any>formGroup.get(KEY)).nativeElement.focus();
stopLoop = true;
}
});
alert("Warn", "Form invalid");
return;
}
}
}
Reference:
https://stackblitz.com/edit/focus-using-formcontrolname-as-selector?file=src%2Fapp%2Fapp.component.ts
Per the #Swiggels comment above, his solution for an element of id "input", using his solution after callback:
this.renderer.selectRootElement('#input').focus();
worked perfectly in Angular 12 for an element statically defined in the HTML (which is admittedly different somewhat from the OP's question).
TS:
#ViewChild('licenseIdCode') licenseIdCodeElement: ElementRef;
// do something and in callback
...
this.notifyService.info("License Updated.", "Done.");
this.renderer.selectRootElement('#licenseIdCode').focus();
HTML:
<input class="col-3" id="licenseIdCode" type="text" formControlName="licenseIdCode"
autocomplete="off" size="40" />
If you are using Angular Material and your <input> is a matInput, you can avoid using .nativeElement and ngAfterViewInit() as follows:
Component Class
import { ChangeDetectorRef, QueryList, ViewChildren } from '#angular/core';
import { MatInput } from '#angular/material/input';
// more imports...
class MyComponent {
// other variables
#ViewChildren('theInput') theInputs: QueryList<MatInput>;
constructor(
private cdRef: ChangeDetectorRef,
) { }
// ...other code
addInputToFormArray() {
// Code for pushing an input to a FormArray
// Force Angular to update the DOM before proceeding.
this.cdRef.detectChanges();
// Use the matInput's focus() method
this.theInputs.last.focus();
}
}
Component Template
<ng-container *ngFor="iterateThroughYourFormArrayHere">
<input #theInput="matInput" type="text" matInput>
</ng-container>