components interaction 2 way angular - html

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

Related

Angular How to implement Subject instead of Input and template variable

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
}

Buttons not disabled when initializing component

I'm using the [disabled] property to decide if button elements should be or not disabled, depending if an array.length returns 0 or not.
This is working fine after the user interacts with the component, however the buttons aren't disabled when the component is initialized and the array is empty.
typescript
constructor() {
this.macrosSelected = [];
}
html
<button
type="button"
class="btn btn-secondary"
[disabled]="!macrosSelected.length"
>
As I said, this code is enabling the button when the array has an object inside it, and disabling it once the array returns to an empty state.
As you have assigned macrosSelected = [] in your constructor then this will be the initial value of your variable. If you are assigning this variable using #Input() decorator or by a service, those will update this variable after the constructor call means second assignment. So you can simply check the length of macrosSelected variable like below.
Your template code must be like:
<button type="button" class="btn btn-secondary" [disabled]="macrosSelected.length === 0"></button>

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.

Toggle button based on json response in angular 2

I am trying to toggle button based on backend response.If the response is Yes, toggle button should be ON, if the response is No, toggle button should be off.
Below is my html code
<div class="btn-group btn-toggle">
<button class="btn btn-sm btn-default">ON</button>
<button class="btn btn-sm btn-primary ">OFF</button>
</div>
Below is my component code, where i calling a get service which returns response either Yes or No.I am calling the service in ngOnit() because i want to check whether the response is Yes /No when the page loads.Upon research i came to know we can achieve this using ngclass, but i am not sure how to do that.
ngOnInit() {
this.service.getFlagStatus().toPromise().then((flagStatus => {
this.flagStatus= flagStatus;
//if(flagStatus == 'Yes'){
//toggle right
// }else if (flagStatus == 'No') {
//toggle left
// }
}));
}
flagStatus variable has the response(Yes/No).
Let me know if i am doing in the right way.
Set right text in the component's code:
ngOnInit() {
this.service.getFlagStatus().toPromise().then((flagStatus => {
if(flagStatus == 'Yes'){
this.text = 'ON';
} else if (flagStatus == 'No') {
this.text = 'OFF';
}
}));
}
And pass it into the component's template (Angular/Interpolation):
<div class="btn-group btn-toggle">
<button class="btn btn-sm btn-primary">{{text}}</button>
</div>
Also you may play with classes (Angular/Class binding):
<button
class="btn btn-sm"
[class.btn-default]="text == 'ON'"
[class.btn-primary]="text != 'ON'">{{text}}</button>

Disable button after form submit

Sounds easy and a well known question, right? I thought so as well. How do I do this in angularJS.
CSHTML
#using (Html.BeginForm("Order", "Shop", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<div class="container" ng-app="order" ng-controller="orderController">
<button type="submit" ng-disabled="orderButtonClicked" ng-click="orderClicked()" class="btn btn-primary btn-block tf-btn btn-lg">Place Order</button>
</div>
}
AngularJS
angular.module("order", [])
.controller("orderController", ['$scope', '$http','$filter', function ($scope, $http, $filter) {
$scope.orderButtonClicked = false;
$scope.orderClicked = function () {
$scope.orderButtonClicked = true;
}
}]);
As many others reported as well, the form is not submitting when disabling or removing the button. this answer did the same, he claims it is working, but for me is a no go.
You can assume that angular is setup correctly, disabling the button works fine.
I've never had much luck with disabling the submit button in any circumstances - even if it doesn't prevent the form from submitting, the server can get confused because it expects the name/value combination from the submit button.
Instead, I generally hide the submit button, and replace it with something appropriate:
<button type="submit" ng-show="!orderButtonClicked" ng-click="orderClicked()" class="btn btn-primary btn-block tf-btn btn-lg">Place Order</button>
<button ng-show="orderButtonClicked" disabled class="btn btn-primary btn-block tf-btn btn-lg">Place Order</button>
Keep in mind that even in this case, the user may be able to re-submit by hitting enter in a textbox.
Try this way:
<div ng-app="myApp" ng-controller="myCtrl">
<form>
<input type="submit" ng-disabled="orderButtonClicked" ng-click="orderClicked()">
</form>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.orderButtonClicked = false;
$scope.orderClicked = function () {
$scope.orderButtonClicked = true;
}
});
</script>
I will put a break point there and see if orderButtonClicked is set to true when orderClicked() is triggered. Just another thought, I have experience with this issue before when I have an ng-if somewhere inside the controller scope in html. This is because angular seems to create a new scope inside that ng-if dom. The best way to avoid that is to use controllerAs and then access the scope property using controllerName.propertyName.
Does the form submit if you don't disable or remove the button? The angular documentation states that, "For this reason, Angular prevents the default action (form submission to the server) unless the <form> element has an action attribute specified."
So, depending on what you're trying to accomplish, you would have to add javascript in your .orderClicked method to make an ajax call, for example, or whatever you're trying to accomplish.