Angular How to implement Subject instead of Input and template variable - html

EDIT:
I implemented some logic to hide my icons and how them only when a specific row from parent component is on mouseover. I need to adjust this logic to my project. Changing global styles is unfortunately unacceptable in my project :(
I tried to implement it with Subject, but now instead of current row, all rows are affaected and all icons are shown. Is it possible to use these Subject Operator logic and show icons only on curretnly hovered row?
This is now my parent component HTML:
<ng-container *ngIf="!emptyList else emptyListTemplate">
<tr requestRow *ngFor="let request of requests"
(mouseenter)="emitEventToChild(true)"
(mouseleave)="emitEventToChild(false)"
[isRequestRowHighlighted]="eventsSubject.asObservable()"
[attr.data-cy-request-id]="request.id"
[request]="request"
[actionable]="true"
[isRequest]="isRequestsList"
(delete)="deleteRequest($event)"
appMemoTooltip>
</tr>
</ng-container>
This is the TS part:
isMouseEnter: boolean
eventsSubject: Subject<boolean> = new Subject<boolean>();
emitEventToChild(e: boolean) {
this.eventsSubject.next(e)
}
private filterSubscription: Subscription
This is my child component HTML (its selector is 'tr[requestRow]' )
<td *ngIf="actionable" class="p-auto text-center actions" (mouseenter)="memoControls.pause()" (mouseleave)="memoControls.resume()">
<div class="d-flex align-items-center justify-content-end" [ngClass]="{'invisible': !isMouseEnter}">
<button
*ngIf="preferredEditType"
type="button" class="btn btn-primary bg-transparent border-0"
title="Edit"
data-cy-id="cy-btn-edit"
(click)="load($event, request, isRequest, preferredEditType)">
<i class="icon-edit-request"></i>
</button>
<button *ngIf="hateoas.supports(request, 'details')"
type="button" class="btn btn-success bg-transparent border-0"
title="Reuse"
data-cy-id="cy-btn-reuse"
(click)="load($event, request, isRequest, requestOperationType.REUSE)">
<i class="icon-copy"></i>
</button>
<button *ngIf="hateoas.supports(request, 'delete')"
type="button" class="btn btn-danger bg-transparent border-0"
title="Delete"
data-cy-id="cy-btn-delete"
(click)="deleteRequest($event)">
<i class="icon-trash"></i>
</button>
</div>
This is the TS file:
#Input() isRequestRowHighlighted: Observable<boolean>
isMouseEnter: boolean
ngOnInit(): void {
this.eventsSubscription = this.isRequestRowHighlighted.subscribe(value => this.isMouseEnter = value)}
enter code here
ngOnDestroy(): void {
this.eventsSubscription.unsubscribe();
}
Expected Result
As is now
Please, let me know how can I achieve displaying icons only for current(on mouseenter) row, not for all of them.
Thank you.

the .css to mannage two differents components should be global, so try in styles.css
tr[requestRow] button {
visibility: hidden;
}
tr[requestRow]:hover button{
visibility: visible;
}
A Simple stackblitz
Update
Well, we can use a ViewEncapsulation.None, who apply all the .css in our component in a way global.
#Component({
selector: [requestRow]
...
encapsulation:ViewEncapsulation.None,
})
But be carefull, if in our component we use also e.g. h1{color:red} this style is also propagate to all our aplication
Another way (but is marked as deprecated) is using some like (in parent.css)
:host ::ng-deep tr[hello] button {
visibility:hidden
}
:host ::ng-deep tr[hello]:hover button{
visibility:visible
}

Related

components interaction 2 way angular

I have 2 components - one for a list of items and one for several buttons like save,cancel,summary etc.
In my buttons component I am using inputs and outputs to call the methods I need.
<button type="button" class="btn btn-danger ml-3" (click)="submit(true)" *ngIf="allowUserToFinalize(false)">
Submit
</button>
<button type="button" class="btn btn-danger ml-3" (click)="submit(false)" *ngIf="allowUserToFinalize(true)">
Cancel
</button>
allowUserToFinalize(false) -> this method is on items component and I have to send a parameter to it and use the returned value.
I have tried to do that with Output and Input - I emit an event with the param value to the item component, call the allowUserToFinalize(param) and then the result is send via Input to the button component and it is used in the ngIf directive.
This is working only on init, it sends event=undefined, it returns true and then it is not triggered anymore.
Is there another method to do this interaction?
items html
<app-items-action-buttons>
(allowUserToFinalize)="allowUserToFinalize($event)"
[allowUserToFinalize]="allowUserToFinalize()">
</app-items-action-buttons>
items ts
allowUserToFinalize(submit: boolean) {
if (submit) {
return false;
} else {
return true;
}
}
butons ts
#Output() allowUserToFinalizeEvent: EventEmitter<any> = new EventEmitter();
#Input() allowUserToFinalizeInput;
allowUserToFinalize(submit: boolea) {
this.allowMerchantToFinalizeDealEvent.emit(submit)
}
buttons html
<button type="button" class="btn btn-danger ml-3" (click)="submit(false)" *ngIf="allowUserToFinalizeInput">
Cancel
</button>
That is quite a complicated data flow. Lemme try to propose a simpler one:
<button type="button" class="btn btn-danger ml-3" (click)="submit(true)" *ngIf="showSubmit">
Submit
</button>
<button type="button" class="btn btn-danger ml-3" (click)="submit(false)" *ngIf="showCancel">
Cancel
</button>
<app-items-action-buttons>
[showSubmit]="allowUserToFinalize(true)"
(showCancel)="allowUserToFinalize(false)"
>
</app-items-action-buttons>
And if you still want to keep the data flow you wanted to use originally. This way you need to pass the callback (think of callback hell right away) as in input property:
#Input() allowUserToFinalizeInput: (isSubmit:bool) => bool;
and use it right the way you use it now. there is no event here and no need for #Output property

Add Data Dynamically to HTML Button in HTML page

I have this button, I need to add Pendo Data that is dynamically working based on which button we chose. Mostly this is making the Button unique. When I have a button that is not changing I add like this:
<button mat-button
 data-pendo="pendo-prospects-send-application"
class='round-button'
color='primary'
type='button'
.....>
</button>
But sometime I need to add this data to one button that is changing based on CSS class. I am not sure how check for that.
For example I need to add to a button when :
if [class.fa-pencil] then data-pendo "Something"
if [class.fa-plus] then data-pendo "Something else"
This is the button that changes base on class:
<button mat-button
class='round-button'
type='button'
[class.disabled-button]='GuidId'
color='primary'
(click)='onAssignLoanOfficer()'>
<i class='fal'
[class.fa-pencil]='GuidId'
[class.fa-plus]='!GuidId'></i>
</button>
How I can do that?
Based on your comments I think this is what you want to do:
<button
(click)="onAssignLoanOfficer()"
[class.disabled-button]="GuidId"
[attr.data-pendo]="GuidId ? 'pendo-edit-loan' : 'pendo-add-loan'"
class="round-button"
color="primary"
type="button">
<i [class.fa-pencil]="GuidId"
[class.fa-plus]="!GuidId"
class="fall">
</i>
</button>
ALTERNATIVE:
As a more sophisticated alternative (I really don't know how you intend to use what you're asking for), you can build a directive to add the attribute you want based on a map of class-to-pendo-data conversion information:
#Directive({
selector: '[addPendoData]'
})
export class AddPendoDataDirective implements AfterViewInit {
constructor(private _el: ElementRef, private _renderer: Renderer2) {}
ngAfterViewInit() {
const pendoData: string | null | undefined = this._getPendoValue();
if (!pendoData) { return;}
const $button: HTMLElement = this._el.nativeElement;
this._renderer.setAttribute($button, 'data-pendo', pendoData);
}
private _getPendoValue() {
const $child: HTMLElement = this._el.nativeElement;
if(!$child) { return null; }
const $i: HTMLElement = $child.querySelector('i');
if(!$i) { return null; }
const listOfClasses: string[] = $i.className.split(' ');
if (!(listOfClasses && listOfClasses.length)) { return null; }
for(const className of listOfClasses) {
if(PENDO_MAP[className]) { return PENDO_MAP[className]; }
}
return null;
}
}
const PENDO_MAP: { [className: string]: string } = {
'fa-pencil': 'pendo-edit-loan',
'fa-plus': 'pendo-add-loan'
// add other mappings here...
};
and you can use it like this:
<button
(click)="onAssignLoanOfficer()"
[class.disabled-button]="GuidId"
addPendoData
class="round-button"
color="primary"
type="button">
<i [class.fa-pencil]="GuidId"
[class.fa-plus]="!GuidId"
class="fall">
</i>
</button>
I've put together this stackblitz demo.
You could define a directive, as julianobrasil suggests.
I want to point a different way to achieve this using Angular templates.
Define two buttons, addition and edition, separately.
Put a default where you want to be rendered
<div class="action-button" *ngIf="GuidId">
<!-- Add button -->
<button mat-button
data-pendo="pendo-add-loan"
(click)="add(...)"
class="round-button"
color="primary">
<i class="fal fa-plus"></i>
</button>
</div>
Then, define a ng-template tag with the other button definition. A completely fresh new.
<ng-template #edit-button>
<div class="action-button">
<!-- Edit button -->
<button mat-button
(click)="edit(...)"
data-pendo="pendo-edit-loan"
class="round-button"
color="primary">
<i class="fal fa-pencil"></i>
</button>
</div>
</ng-template>
Finally, just change the *ngIf statement to render either an addition button or an edition one.
<div class="action-button" *ngIf="!GuidId else edit-button">
<!-- Add button here -->
</div>
This is a way that scales in order to keep components isolated. The buttons are not related anymore, so you can implement w/o having to condition every style, action, etc.
Hope it helps.

*ngFor generated mat-button is focused on load

I'm generating a list of buttons using ngFor and when I load the site, the first generated button is already focused until I click anywhere else ...
<div align="center" class="buttonDiv" *ngFor="let tamplates of caseService.availableTaskTemplates">
<button mat-button class="button" *ngIf="tamplates.contentType == 1" (click)="createTask(tamplates.taskType)">
{{ tamplates.headerText }}
</button>
</div>
here's a picture:
https://ibb.co/imG7sy
what can I do to "unfocus" the button?
you can use autofocus property in HTML.
autofocus: focus on the element once the page is loaded
add it to a dummy element in your HTML in order to un focus the meta-button.
for example:
<label autofocus> </label>
---------------- Another Solution -------------------
you can use blur() method in javascript, after the view is rendered.
inside AfterViewInit
#Component({
selector: 'my-cmp',
templateUrl: `my-cmp.html`
})
class MyComponent implements AfterViewInit {
#ViewChild('buttonsList') buttonsList;
ngAfterViewInit() {
// execute blur on the first child of the list.
this.buttonsList.nativeElement.firstChild.blur();
}
}
in your template:
<div #buttonsList *ngFor='let item of items'>
<button meta-button> {{item}} </button>
</div>

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.

Why do bootstrap tooltips not work in bootstrap modals?

My tooltips are working on the mainpage perfectly. In a modal which is generated later by an ajax call the tooltips won't work.
I have included the following code inside the generated modal (result of the ajax call).
To re-ini the tooltips
<script>
$('.tooltips').tooltip();
</script>
In the html of the modal
<button class="btn btn-lg default tooltips blue-madison" type="submit"
name="O" data-container="body" data-placement="top"
data-original-title="THIS TEXT FOR TOOLTIPS">
<i class="fa fa-industry blue-madison"></i> BUTTON1
</button>
<button class="btn btn-lg default tooltips green-jungle" type="submit"
name="P" data-container="body" data-placement="top"
data-original-title="THIS TEXT FOR TOOLTIPS">
<i class="fa fa-user green-jungle "></i> BUTTON2
</button>
Why don't the tooltips show- what I'm doing wrong?
The issue is because of the z-index of modal and tooltip. You can try this,
.tooltip {
z-index: 100000000;
}
Probably it's because you should call $('.tooltips').tooltip(); after the modal's content have been inserted in the document.
Otherwise, please post a fiddle with your current code where we can test it.
Another solution is to bind the tooltip to the modal using the container option:
$('#modal').on('shown.bs.modal', function() {
console.log("modal show");
$('.tooltips').tooltip({
container: $(this)
});
});
Maybe this helps someone: I had a case when needed with ajax to populate & display a bootstrap modal (render view) on click (calling showModal(url, event) below); bootstrap tooltip and also fengyuanchen/datepicker were unresponsive, so I managed to trigger them after detecting modal loading, like this:
function showModal(url, event) {
$.when(
$.ajax({
url: url,
method: 'GET',
success: function (data) {
$('#modal-wrapper').html(data);
}
})
).then(function() {
$('.loaded_modal').modal().on('shown.bs.modal', function() {
$('[data-toggle="datepicker"]').datepicker({
format: "dd/mm/yyyy",
autoclose: true,
todayHighlight: true,
zIndex: 1070,
container: '.modal'
});
$('.modal [data-toggle="tooltip"]').tooltip({trigger: 'hover'});
// could also be on click {trigger: 'hover click'}
});
});
If you are using react I had success with a different answer. All you have to
do is give the parent container a ref and then in the overlayTrigger component you just have to pass in the ref as a param to the container.
import React, { Component } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
class random extends Component {
constructor(props) {
super(props);
this.ref= React.createRef()
}
render() {
return (
<div ref={this.ref}>
<OverlayTrigger
placement="top"
container={this.ref}
overlay={
<Tooltip data-container="body">Some text</Tooltip>
}
>
<span className="d-inline-block">
<i className="mdi mdi-help-circle pointer"></i>
</span>
</OverlayTrigger>
</div>
);
}
}
Best way to fix this behaviour is by adding this prop inside your md-tooltip:
md-z-index="9999"
or a different z-index.
No need to hardcode this in the css.
You can also define the z-index in your scope variable inside your controller as the following
// in controller
$scope.btnOptions = {
isOpen:false,
label: 'test button',
class: 'md-scale',
zIndex: 99999
};
in you html ( normally I would use {{}} to print the variable, but I'm on laravel so I used <% %> instead
<md-button aria-label="Édit" class="md-fab md-raised md-mini">
<md-tooltip md-direction="top" md-z-index="<% btnOptions.zIndex %>">Mode édition</md-tooltip>
<i class="far fa-edit"></i>
</md-button>