How to show Angular Material Tooltip on component initialization - javascript

I want to show an Angular Material tooltip when its component is initialized/loaded.
I know I can add an HTML attribute to show it when an event happens. My overall goal is to have the tooltip showing when the component loads, then hide after a few seconds.
I've tried the following:
<div (load)="tooltip.show()"
#tooltip="matTooltip"
matTooltip="blah blah">
</div>

YoukouleleY is almost correct, you need to put it into ngAfterViewInit() and add setTimeout() to make it work:
#ViewChild('tooltip') tooltip: MatTooltip;
constructor(private cd: ChangeDetectorRef) { }
ngAfterViewInit() {
this.tooltip.show();
this.cd.detectChanges();
setTimeout(() => this.tooltip.hide(2000));
}
Added update with changeDetectorRef to avoid ExpressionChangedAfterItHasBeenCheckedError. Hope that helps.

Try this:
#ViewChild('tooltip') tooltip: MatToolTip;
ngOnInit() {
this.tooltip.show();
this.tooltip.hide(2000);
}

Related

How to refresh Font Awesome icon on Angular?

Is there any way to change font awesome icon dynamically? I want user to be able to select one of font awesome icons dynamically. It works only when you add class first time. The place where I try to do it is - MatDialog. There is form where user have to select icon, background color and category name. To select icon user should open another dialog.
I'm using Angular 9.1.4 and Font Awesome 5.13.0.
That's what I tried:
1. Using ngClass
category-dialog.component.html
<div [ngStyle]="selectedColor">
<i [ngClass]="selectedIcon"></i>
</div>
category-dialog.component.ts
openIconDialog(): void {
const dialogRef = this.dialog.open(DialogIconSelectComponent, { width: '15rem' });
dialogRef.afterClosed().subscribe(result => {
this.selectedIcon = result;
});
}
This works only first time. But when you try to change icon selectedIcon changes, but UI doesn't refresh element class.
2. Using #ViewChild
#ViewChild('iconElement') iconElement: ElementRef;
constructor(private dialog: MatDialog,
private renderer: Renderer2) { }
openIconDialog(): void {
const dialogRef = this.dialog.open(DialogIconSelectComponent, { width: '15rem' });
dialogRef.afterClosed().subscribe((result: string) => {
this.iconElement.nativeElement.className = result;
});
}
This also works only first time.
3. Using #ViewChild and Renderer2
category-dialog.component.html
<div #colorElement [ngStyle]="selectedColor">
<i #iconElement></i>
</div>
category-dialog.component.ts
#ViewChild('colorElement') parentElement: ElementRef;
#ViewChild('iconElement') childElement: ElementRef;
constructor(private dialog: MatDialog,
private renderer: Renderer2) { }
openIconDialog(): void {
const dialogRef = this.dialog.open(DialogIconSelectComponent, { width: '15rem' });
dialogRef.afterClosed().subscribe(result => {
this.replaceIcon(result);
});
}
replaceIcon(iconClass: string): void {
const i = this.renderer.createElement('i');
this.renderer.setProperty(i, 'class', iconClass);
this.renderer.removeChild(this.parentElement.nativeElement, this.childElement);
this.renderer.appendChild(this.parentElement.nativeElement, i);
}
That doesn't work at all.
Is there any way how to change font awesome dynamically?
Resolution
Wasted lot of my free time to investigate how to resolve this issue. Tried everything with Renderer2 and all dirty Javascript methods. But one day I came up with idea to use innerHtml.
Rendering new string of inner HTML changes Font Awesome icons interactively.
category-dialog.component.html
<div [ngStyle]="selectedColor" [innerHtml]="selectedIconHtml"></div>
category-dialog.component.ts
openIconDialog(): void {
const dialogRef = this.dialog.open(DialogIconSelectComponent, { width: '15rem' });
dialogRef.afterClosed().subscribe((result: string) => {
// EVERY TIME NEW ELEMENT WITH NEW FA CLASS
this.selectedIconHtml = `<i class="${result}"></i>`;
});
}
This solution - on every icon selection's changing <div> element content (inner html).
I solved this issue like this:
<div innerHTML="<i class='{{icon}}'></i>">
</div>
In this case, icon will be rerendered after value changes. innerHTML makes this happen easily. No need any code in TS file.

Losing the focus on an input that appears conditionally - Angular 7 [duplicate]

I am working a front-end application with Angular 5, and I need to have a search box hidden, but on click of a button, the search box should be displayed and focused.
I have tried a few ways found on StackOverflow with directive or so, but can't succeed.
Here is the sample code:
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello</h2>
</div>
<button (click) ="showSearch()">Show Search</button>
<p></p>
<form>
<div >
<input *ngIf="show" #search type="text" />
</div>
</form>
`,
})
export class App implements AfterViewInit {
#ViewChild('search') searchElement: ElementRef;
show: false;
name:string;
constructor() {
}
showSearch(){
this.show = !this.show;
this.searchElement.nativeElement.focus();
alert("focus");
}
ngAfterViewInit() {
this.firstNameElement.nativeElement.focus();
}
The search box is not set to focus.
How can I do that?
Edit 2022:
Read a more modern way with #Cichy's answer below
Modify the show search method like this
showSearch(){
this.show = !this.show;
setTimeout(()=>{ // this will make the execution after the above boolean has changed
this.searchElement.nativeElement.focus();
},0);
}
You should use HTML autofocus for this:
<input *ngIf="show" #search type="text" autofocus />
Note: if your component is persisted and reused, it will only autofocus the first time the fragment is attached. This can be overcome by having a global DOM listener that checks for autofocus attribute inside a DOM fragment when it is attached and then reapplying it or focus via JavaScript.
Here is an example global listener, it only needs to be placed in your spa application once and autofocus will function regardless of how many times the same fragment is reused:
(new MutationObserver(function (mutations, observer) {
for (let i = 0; i < mutations.length; i++) {
const m = mutations[i];
if (m.type == 'childList') {
for (let k = 0; k < m.addedNodes.length; k++) {
const autofocuses = m.addedNodes[k].querySelectorAll("[autofocus]"); //Note: this ignores the fragment's root element
console.log(autofocuses);
if (autofocuses.length) {
const a = autofocuses[autofocuses.length - 1]; // focus last autofocus element
a.focus();
a.select();
}
}
}
}
})).observe(document.body, { attributes: false, childList: true, subtree: true });
This directive will instantly focus and select any text in the element as soon as it's displayed. This might require a setTimeout for some cases, it has not been tested much.
import { Directive, ElementRef, OnInit } from '#angular/core';
#Directive({
selector: '[appPrefixFocusAndSelect]',
})
export class FocusOnShowDirective implements OnInit {
constructor(private el: ElementRef) {
if (!el.nativeElement['focus']) {
throw new Error('Element does not accept focus.');
}
}
ngOnInit(): void {
const input: HTMLInputElement = this.el.nativeElement as HTMLInputElement;
input.focus();
input.select();
}
}
And in the HTML:
<mat-form-field>
<input matInput type="text" appPrefixFocusAndSelect [value]="'etc'">
</mat-form-field>
html of component:
<input [cdkTrapFocusAutoCapture]="show" [cdkTrapFocus]="show">
controler of component:
showSearch() {
this.show = !this.show;
}
..and do not forget about import A11yModule from #angular/cdk/a11y
import { A11yModule } from '#angular/cdk/a11y'
I'm going to weigh in on this (Angular 7 Solution)
input [appFocus]="focus"....
import {AfterViewInit, Directive, ElementRef, Input,} from '#angular/core';
#Directive({
selector: 'input[appFocus]',
})
export class FocusDirective implements AfterViewInit {
#Input('appFocus')
private focused: boolean = false;
constructor(public element: ElementRef<HTMLElement>) {
}
ngAfterViewInit(): void {
// ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
if (this.focused) {
setTimeout(() => this.element.nativeElement.focus(), 0);
}
}
}
This is working i Angular 8 without setTimeout:
import {AfterContentChecked, Directive, ElementRef} from '#angular/core';
#Directive({
selector: 'input[inputAutoFocus]'
})
export class InputFocusDirective implements AfterContentChecked {
constructor(private element: ElementRef<HTMLInputElement>) {}
ngAfterContentChecked(): void {
this.element.nativeElement.focus();
}
}
Explanation:
Ok so this works because of: Change detection. It's the same reason that setTimout works, but when running a setTimeout in Angular it will bypass Zone.js and run all checks again, and it works because when the setTimeout is complete all changes are completed. With the correct lifecycle hook (AfterContentChecked) the same result can be be reached, but with the advantage that the extra cycle won't be run. The function will fire when all changes are checked and passed, and runs after the hooks AfterContentInit and DoCheck. If i'm wrong here please correct me.
More one lifecycles and change detection on https://angular.io/guide/lifecycle-hooks
UPDATE:
I found an even better way to do this if one is using Angular Material CDK, the a11y-package.
First import A11yModule in the the module declaring the component you have the input-field in.
Then use cdkTrapFocus and cdkTrapFocusAutoCapture directives and use like this in html and set tabIndex on the input:
<div class="dropdown" cdkTrapFocus cdkTrapFocusAutoCapture>
<input type="text tabIndex="0">
</div>
We had some issues with our dropdowns regarding positioning and responsiveness and started using the OverlayModule from the cdk instead, and this method using A11yModule works flawlessly.
In Angular, within HTML itself, you can set focus to input on click of a button.
<button (click)="myInput.focus()">Click Me</button>
<input #myInput></input>
To make the execution after the boolean has changed and avoid the usage of timeout you can do:
import { ChangeDetectorRef } from '#angular/core';
constructor(private cd: ChangeDetectorRef) {}
showSearch(){
this.show = !this.show;
this.cd.detectChanges();
this.searchElement.nativeElement.focus();
}
I'm having same scenario, this worked for me but i'm not having the "hide/show" feature you have. So perhaps you could first check if you get the focus when you have the field always visible, and then try to solve why does not work when you change visibility (probably that's why you need to apply a sleep or a promise)
To set focus, this is the only change you need to do:
your Html mat input should be:
<input #yourControlName matInput>
in your TS class, reference like this in the variables section (
export class blabla...
#ViewChild("yourControlName") yourControl : ElementRef;
Your button it's fine, calling:
showSearch(){
///blabla... then finally:
this.yourControl.nativeElement.focus();
}
and that's it.
You can check this solution on this post that I found, so thanks to -->
https://codeburst.io/focusing-on-form-elements-the-angular-way-e9a78725c04f
There is also a DOM attribute called cdkFocusInitial which works for me on inputs.
You can read more about it here: https://material.angular.io/cdk/a11y/overview
Only using Angular Template
<input type="text" #searchText>
<span (click)="searchText.focus()">clear</span>
When using an overlay/dialog, you need to use cdkFocusInitial within cdkTrapFocus and cdkTrapFocusAutoCapture.
CDK Regions:
If you're using cdkFocusInitial together with the CdkTrapFocus directive, nothing will happen unless you've enabled the cdkTrapFocusAutoCapture option as well. This is due to CdkTrapFocus not capturing focus on initialization by default.
In the overlay/dialog component:
<div cdkTrapFocus cdkTrapFocusAutoCapture>
<input cdkFocusInitial>
</div>
#john-white The reason the magic works with a zero setTimeout is because
this.searchElement.nativeElement.focus();
is sent to the end of the browser callStack and therefore executed last/later, its not a very nice way of getting it to work and it probably means there is other logic in the code that could be improved on.
Easier way is also to do this.
let elementReference = document.querySelector('<your css, #id selector>');
if (elementReference instanceof HTMLElement) {
elementReference.focus();
}

Add a class to expanded row using vanilla JS

In my angular application I am attempting a workaround because the ag-Grid api getRowClass() is not working at intended. All I need to do is add a css class to an expanded row and remove it when the row is collapsed.
The original method using ag-Grid api that does not work looks as follows:
setRowNode(params) {
this.gridOptions.getRowStyle = (params) => {
if(params.node.expanded) {
return { background: 'red' };
}
}
}
Ideally I would be able to selected the DOM and append a class to it. I tried this with some JQuery and it worked, but for obvious reasons I do not want to have JQuery in this app. I wrote something along these lines:
$('.ag-row[row="'+params.node.id+'"]',self.api.grid.gridPanel.eBodyContainer).addClass('ag-row-focus');
How would I fulfill this req using vanilla JS?
You can do it by creating a custom directive, like this one :
//row-focus.directive.ts
import { Directive, HostBinding, HostListener } from '#angular/core';
#Directive({
selector: '[appRowFocus]' // you can target classes, id etc.: '.grid-Holdings' for example
})
export class RowFocusDirective {
#HostBinding('class.ag-row-focus') isFocused = false;
#HostListener('click') toggleOpen() {
this.isFocused = !this.isFocused;
}
}
Import this directive on your module, and then attach the directive to your elements :
// your-component.component.ts :
<div class="row" appRowFocus>
These elements will toggle the ag-row-focus on click. You can add different #HostListener for other events.

Angular 2/Typescript Delete Object On Button Click

I have an Angular 2 app using Typescript but i am new to this, what i have is a table with a 'Delete' button,
I can pass the object data to my confirmation modal but when i 'Confirm' it, its still in my table.
delete-modal.component
import { Component, OnInit, Inject, Input } from '#angular/core';
import { TestService } from '../../ABC/TestService/TestService.service';
import { MdDialog, MdDialogRef, MD_DIALOG_DATA } from '#angular/material';
import { testModal } from 'models/test';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.css']
})
export class testDeleteModalComponent implements OnInit {
#Input('test') test: testModal;
constructor(private TestService: TestService, private accountService: AccountService,
#Inject(MD_DIALOG_DATA) private dialogData: any) { }
ngOnInit() {
console.log('test', this.dialogData.beneficiary);
this.test = this.dialogData.test;
}
deleteTest() {
if (this.dialogData.test.identifier) {
// this.dialogData.beneficiary.splice(this.dialogData.beneficiary.indexOf(this.beneficiaryAnt), 1);
// this.dialogData.beneficiary.splice(this.beneficiary);
// delete this.beneficiary;
this.dialogData.test.splice(this.dialogData.test.indexOf(this.dialogData.test), 1);
} else {
this.dialogData.test.operation = 'X';
}
}
}
HTML
<button md-icon-button (click)="deleteTest()" name="deleteTestDetails">
<md-icon>delete forever</md-icon>
</button>
All other HTML is in a main component and the 'Delete' button is used as shown below
<app-test-main-page-delete-button [test]="test"></app-test-main-page-delete-button>
The 'deleteTest' method is called when the user click the confirm button.
I have also included above some ways i have tried in the IF but they always come back
... is not a function
It is good that you asked this question, my projects of three peoples also struggling with this. we have found is two ways. what i will show is two ways of doing typescriptdelete.
solution a.
because it is object, it will need identifier. First is
var objectdelete = {
identifier: 'Mydelte',
value: '168%'
}
Next what we need is now service. some people call them directives but from my experience they are the same thing. We have alert so user knows if they did not set identifier that they must go back. I do not see service on your side, i see array being deleted. if you combine the array and the service, this will then be working across whole website.
export class DeleteService
delete(objectToDelete: string) {
if (!objectToDelete.identifier) {
alert('No identifer');
}else {
// Delete from your array here.
}
}
Solution 2.
If the above does not meed your needs, our tema also experimented with interfaces in typescript. You can see them here https://www.typescriptlang.org/docs/handbook/interfaces.html
so it becomes
export class myDeleteService {
deleter: IDeleter
}
export interface IDeleter {
delete: this.delete.delete(deletE);
deleteArray: this.array =[];
}
then simply in your html it will be
<button (click)='delete(dieleter)'>Delete me!</button>
These are all common typescript behaviours for angular2/4/5 so we are hoping to become more used to them when we have hads more time to use them!
The easiest way to delete data object on button click and refresh instantly when it's done :
Your parent html has to call children like this :
<app-component [inputData]="dataTable" (inputDataChange)="resetData()"/>
Add dataTable as class variable and implement the output function :
resetData() { this.dataTable=[] }
Then in children html leave your code (you can use this changes)
<button class="fa fa-delete" (click)="deleteTest()" name="deleteTestDetails">Delete</button>
Finaly in your children ts file set your data object for each change, and implement your input function
myDataTable: any = [];
#Input set inputData(data: DataTable) {
if(data) {
this.myDataTable = data;
}}
#Output() inputDataChange: EventEmitter<any> = new EventEmitter();
deleteTest() {
this.inputDataChange.emit(true);
}
What does this code do ?
It will emit and event to the parent when the delete button is clicked, then your parent will delete the dataTable, and finally, your children input will refresh it, as setter will catch the changes and refresh the variable.
If you want to apply those rules to table changes, then simply emit your dataTable and reassign it instead of reset it.
I am in a project with and our team have struggled on this for a whiles.
First thing I will say is this, Angular has not made this an easy task, so we will attempt to ignore the framework and write pure Java instead to make our lives easyer on ourselves.
SO looking at your button, I can see that you have started on the right track.
If the button is calling your component like the following
Html/Java
<button ng-click="delete()">Click me<button>
Component.ts
function delete = deleteMethod(testIdentifier) {
var abc = this.beneficiary.beneficiaryIdentifier.test.splice(this.beneficiary.beneficiaryIdentifier.test.indexOf(testIdentifier));
component2.deleteFunction();
}
Component2.ts
Then we can pass our identifiers into our parent or child components and remove the beneficiary like so:
deleteMethod(deetle) {
this.beneficiary.removeAtIndex(testIdentifier.splice(1), 1);
}
Nice and easy looking back, but it took our team of threes a long whiles to figure that ones out.

how to mix a decision of with an observer and synced actions? (Angular)

I have six square views in my project, I have to options to click on them:
click on one and and it get selected with some css fade color on top of it to show its selected. this already works.
select couple of them and having the same css fade color to show the squares are selected. this works to some degree but messed with something else.
so to recognize which mode im at I have radio buttom that call multipleSelectionMode.
so this is the code:
this is my square component html file.
<div *ngIf="oneChoosed" layout="row" layout-align="center center" class="one-chose">
</div>
<div *ngIf="multipleChoosed" layout="row" layout-align="center center" class="omult-chose">
</div>
in this component class I have 2 inputs I get from the main screen component:
#Input() oneChoosed: boolean;
#Input() multipleChoosed: boolean;
this is have I update this inputs from the main screen component html file:
[oneChoosed]="isOneChoosed(squares)"
[multipleChoosed]="isMultipleChoosed(squares)"
and in the main screen component class I get the click events and the id's of the squares, it looks like this:
export class mainScreenComp implements OnInit, OnDestroy {
multipleSelectionMode: Observable<boolean>;
oneChoosenIds: string[];
multipleChosenIds: string[];
_oneChoseSquareIdsSubscription: Subscription;
_multipleChoseSquaresIdsSubscription: Subscription;
ngOnInit() {
this.multipleSelectionMode = this.brainComp.curSelectionTyp.map(selecType => selecType === SelectionType.multiple);
this._oneChoseSquareIdsSubscription = this.brainComp.choosingOption.subscribe(
(chos: ChoosingOption) => {
switch (chos.option) {
case ChoosingOption.oneChose:
this.oneChoosenIds = chos.chosedIds.squares;
break;
case ChoosingOption.finishedOneSel:
this.oneChoosenIds = undefined;
break;
}
}
);
this._multipleChoseSquaresIdsSubscription = this.brainComp.choosingOption.subscribe(
(chos: Chosed) => {
this.multipleChosenIds = chos.squares;
}
);
}
isOneChoosed(square: SquaresIQ): boolean {
return (this.oneChoosenIds ? this.oneChoosenIds.includes(square.squareId) : false);
}
isMultipleChoosed(square: SquaresIQ): boolean {
return (this.multipleChosenIds ? this.multipleChosenIds.includes(square.squareId) : false);
}
so basically I add squares id's to the arrays oneChoosenIds and multipleChosenIds and check if they empty or not to decide what to return,
this works, BUT, when im in oneChoosing state where I can
only choose one, when I click on it I see both css square covers that shows that the square is selected...cause they have different design I can notice it.
so it means this *ngIf="oneChoosed" and this *ngIf="multipleChoosed" both returned true...so I thought to put extra condition with my observer multipleSelectionMode in the _multipleChoseSquaresIdsSubscription subscription, but this is async cause its an observer and the other actions are sync so its not working properly. do you have a suggestion for me to solve it?

Categories

Resources