Angular, how to use directive based on an boolean - javascript

I use a ngFor to show different button. I need to use a directive on different button but not all.
So I make this code :
<div *ngFor="let btn of btns">
<button {{ btn.useDirective ? 'appMyDirective' : '' }} ></button>
</div>
But I get this error
Error: Template parse errors:
Unexpected closing tag "button". It may happen when the tag has already been closed by another tag.

update you directive to be trigger base on state as an example consider this 👇
#Directive({
selector: '[appHello]'
})
export class HelloDirective {
#Input() appHello:boolean;
constructor(private elem: ElementRef, private renderer: Renderer2) { }
ngAfterViewInit() {
if (this.appHello !== false ) {
this.renderer.setProperty(this.elem.nativeElement, 'innerHTML', 'Hi 😎');
}
}
}
template
<div *ngFor="let btn of btns">
<button [appHello]="btn.useDirective">Hi </button>
</div>
if you set the value to be true the directive will work otherwise nothing will happen
demo 🔥🔥

Your syntax is invalid. To archieve what you want, do something like this:
<div *ngFor="let btn of btns">
<button *ngIf="btn.useDirective" appMyDirective></button>
<button *ngIf="!btn.useDirective"></button>
</div>

Try using below instead.
<div *ngFor="let btn of btns">
<button appMyDirective *ngIf="btn.useDirective"></button>
<button *ngIf="!btn.useDirective"></button>
</div>

Related

Reuse html template in Angular project

I have this html template file, range-details-dialog.tpl.html
<div class="modal-header clearfix text-left">
<h5>Update Range</h5>
</div>
<div class="modal-body">
<form name="form" role="form" class="ng-pristine ng-valid" novalidate ng-submit="updateRange()">
<div class="form-group-attached">
<div class="row">
<div class="col-sm-12">
<div class="form-group form-group-default input-group p-l-10 p-r-10" ng-class="{ 'has-error' : form.$invalid }">
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-sm-8"></div>
<div class="col-sm-4 m-t-10 sm-m-t-10">
<button type="button" class="btn btn-primary btn-block m-t-5"
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
</div>
</div>
</div>
Then I want to have another file forced-range-details-dialog.tpl.html
These two files could be one file instead with dynamically populated placeholders.
These are the places were substitution would be needed:
<h5>Update Range</h5> would become <h5>Update Forced Range</h5>
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
would become:
<p ng-show="form.forcedRangeDaily.$error.min" class="help-block">Forced Daily range more than £5</p>
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
, ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateForcedRange()">Update</button>
Is there a way to avoid having two separate template files for the above? Could you please provide some examples, links, or pointers as to how that can be achieved?
Also, I see in the answers that a solution would be to add a boolean parameter inside the component and then call it twice. I am not sure how to call the component though. I have pasted my component below:
angular.module('app.investment.rangeDetails')
.component('pxForcedLimitAmount', {
templateUrl: '/assets/js/apps/range/range-details-dialog.tpl.html',
bindings: {
amount: '<',
isRequest: '<?',
requestedAt: '<?',
#Input() isForced: boolean //<<----I added this based on answers below
},
controller: [function () {
var ctrl = this;
ctrl.$onInit = function () {
ctrl.isRequest = ctrl.isRequest === true || false;
};
}],
});
Seems like only the placeholders need to change, so you can use a variable to decide what placeholder to display on the template. For example:
isForced: boolean;
ngOnInit() {
this.isForced = true; // decide how you want to toggle this
}
on the template:
<h5 *ngIf="!isForced">Update Range</h5>
<h5 *ngIf="isForced">Update Forced Range</h5>
and
<p *ngIf="!isForced" ng-show="form.rangeDaily.$error.min" class="help-block">
Daily range more than £5</p>
<p *ngIf="isForced" ng-show="form.forcedRangeDaily.$error.min" class="help-block">
Forced Daily range more than £5</p>
you can do the same for other tags as well.
From the comments, one way to "determine" the value for isForced is to introduce an input property to the component i.e.
#Input() isForced: boolean;
and invoke the component from elsewhere like:
<app-user [isForced]="true"></app-user>
You can use inputs.Write a component which takes input, and render it in html. then call this component in desired places with its selector
For events use output
See the doc https://angular.io/guide/inputs-outputs

Angular: Hiding/showing elements by ngIf or Toggling a class?

I'm wondering what the best way to approach this problem is and I'm very new to TypeScript and Angular. Using Angular 5.
Anyway. I have a list of elements on a page via a table.
This is the code that controls said list.
<tbody>
<tr class="text-center" *ngFor="let topic of topics">
<td *ngIf="!editTopicMode">{{ topic.name }}</td>
<td id="{{topic.id}}" *ngIf="editTopicMode">
<form>
<div class="form-group">
<input class="form-control" type="text" name="name" value="{{topic.name}}" />
</div>
</form>
</td>
<td>
<div *ngIf="!editTopicMode" class="btn-group btn-group-sm">
<button class="btn btn-link" (click)="editTopicBtnClick(topic.id)">
<i class="fa fa-pencil fa-2x" aria-hidden="true"></i>
</button>
<button class="btn btn-link">
<i class="fa fa-trash fa-2x" aria-hidden="true"></i>
</button>
</div>
<div *ngIf="editTopicMode" class="btn-group-sm">
<button class="ml-2 btn btn-sm btn-outline-secondary" (click)="cancelEditMode()">Cancel</button>
<button class="ml-2 btn btn-sm btn-outline-primary">Save</button>
</div>
</td>
</tr>
</tbody>
What I'm aiming to do is that if a user clicks on the pencil(edit) icon, then the adjacent div changes from just a regular td to an input and the edit/delete buttons to change to a cancelEdit/save edits button group. (I know that I need to change the html a bit because the buttons aren't in the form element currently, but I'm not there on the wiring it up part).
I've thought of two ways to do this. 1) with ngIf's so that I can conserve the elements that are rendered and the edit/cancel buttons toggle the editMode; or 2) use ngClass and toggle display:none css classes for the button clicked.
Right now, when you click the edit button, regardless of which edit button you click, it flips all the columns to inputs, rather than just the row the user wants to edit.
Here's my component ts:
import { Component, OnInit, TemplateRef, ElementRef, ViewChild, Inject } from '#angular/core';
import { Topic } from '../models/topic';
import { TopicService } from '../services/topicService/topics.service';
import { AlertifyService } from '../services/alertify/alertify.service';
import { ActivatedRoute } from '#angular/router';
import { DOCUMENT } from '#angular/common';
#Component({
selector: 'app-topics',
templateUrl: './topics.component.html',
styleUrls: ['./topics.component.css']
})
export class TopicComponent implements OnInit {
#ViewChild('topicId') topicId: ElementRef;
topics: Topic[];
newTopic: Topic = {
id: 0,
name: '',
};
editTopicMode = false;
constructor(
#Inject(DOCUMENT) document,
private topicsService: TopicService,
private alertify: AlertifyService,
private route: ActivatedRoute
) { }
ngOnInit() {
//this.route.data.subscribe(data => {
// this.topics = data['topics'];
//})
this.getTopics();
}
getTopics() {
this.topicsService.getAllTopics()
.subscribe(data => {
this.topics = data;
}, error => {
this.alertify.error(error);
});
}
addTopic() {
this.topicsService.createTopic(this.newTopic)
.subscribe((topic: Topic) => {
this.topics.push(topic);
this.alertify.success(this.newTopic.name + ' added as a new topic.');
this.newTopic.name = '';
},
(err: any) => {
this.alertify.error(err);
}
)
}
editTopicBtnClick(event) {
console.log(event);
this.editTopicMode = true;
console.log(document.getElementById(event));
}
cancelEditMode() {
this.editTopicMode = !this.editTopicMode;
}
}
Any thoughts on the best (most efficient) way to make this happen?
You've done all the hard work already.
For single item editing, all that's left is: change editTopicMode to something like editTopicId.
Then you can:
Set it to topic.id on edit enabled, and null for example on edit closed
Change your *ngIf to editTopicId === topic.id (or !== as needed)
And that should be all.
If you want to enable multiple editing, just add a property called isInEditMode to each topic.
Your *ngIf check becomes topic.isInEditMode
No isInEditMode property at all is just like false, because undefined is a falsy value
Set topic.isInEditMode to true on editing enabled, false on editing closed
Using *ngIf is fine just make sure that you set the *ngIf variable to point to somethign specific to the particular row of the *ngFor
this example could be cleaner but you could accomplish it as simply as
<button (click)="topic.edit = true" *ngIf="topic.edit === false">edit</button>
<button (click)="topic.edit = false" *ngIf="topic.edit === true">cancel</button>

How to fix <!--bindings={ "ng-reflect-ng-for-of": "" }-->

Im new to Angular. I am creating a button inside game-control component and using event binding and property binding. When i click the button numbers will be entered into an array continuously using setInterval method. I am passing the data between one component to another. The game-control component's selector is called inside the app component. The button worked fine with the click event but when i used the ngFor in order to iterate through the array and display the buttons also did not appear in the dom. Thanks in advance
game-control.component.html
<div class="row">
<div class="col-md-12">
<button
class="btn btn-primary"
type="button"
(click)="gameStart()">Start</button>
<button
class="btn btn-danger"
type="button"
(click)="gameStop()">Stop</button>
<br>
<p>{{element}}</p>
</div>
</div>
game-control.component.ts
export class GameControlComponent implements OnInit {
#Input() element:number;
#Output() createNumber= new EventEmitter<number>();
constructor() { }
ngOnInit() {
}
gameStart()
{
this.createNumber.emit(this.element);
}
}
app.component.html
<div class="container">
<div class="row">
<div class="col-md-12">
<app-game-control
(createNumber)='onStart()'
*ngFor="let myElement of myHoldings"
[element]="myElement"
>
</app-game-control>
</div>
</div>
</div>
app.component.ts
export class AppComponent {
myHoldings=[];
onStart()
{
setInterval(()=>{
this.myHoldings.push(this.myHoldings.length+1);
console.log("hello");
},1000);
}
}
The solution is to have an internal variable in the component that is assigned from the Input() variable
#Input() element:number;
internalNumber : number ;
ngOnInit() {
this.internalNumber = this.number;
}
And then use that variable to do bindings or emit events
For a similar question that might help
Click function not being called for a mat-menu with items generated with ngFor

Hide div onclick in Vue.js

What is the Vue.js equivalent of the following jQuery?
$('.btn').click(function(){ $('.hideMe').hide() });
jQuery works out of the box, Vue.js does not. To initialize Vue.js component or App you must bind that component with its data to one specific HTML tag inside your template.
In this example the specified element is <div id="app"></div> and is targeted through el: #app. This you will know from jQuery.
After you declare some variable that holds the toggle state, in this case been isHidden, the initial state is false and has to be declared inside the data object.
The rest is Vue-specific code like v-on:click="" and v-if="". For better understand please read the documentation of Vue.js:
The Vue Instance
Template Syntax
Event Handling
Conditionals
Note: consider reading the whole or at least longer parts of the documentation for better understanding.
var app = new Vue({
el: '#app',
data: {
isHidden: false
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<button v-on:click="isHidden = true">Hide the text below</button>
<button v-on:click="isHidden = !isHidden">Toggle hide and show</button>
<h1 v-if="!isHidden">Hide me on click event!</h1>
</div>
This is a very basic Vue question. I suggest your read the guide, even the first page will answer your question.
However, if you still need the answer this is how you hide/show elements in Vue.
new Vue({
el: '#app',
data () {
return {
toggle: true
}
},
})
<script src="https://unpkg.com/vue#2.5.3/dist/vue.js"></script>
<div id="app">
<button #click='toggle = !toggle'> click here </button>
<div v-show='toggle'>showing</div>
</div>
<div>
<div>
<button v-on:click="isHidden = !isHidden">Toggle hide and show</button>
<h1 v-if="!isHidden">Hide me on click event!</h1>
</div>
</div>
name: "Modal",
data () {
return {
isHidden: false
}
}
The up-voted answer is definitely a way to do it, but when I was trying to do this it was with a dynamic array instead of a single Div, so a single static Vue variable wouldn't quite cut it.
As #samayo mentions, there isn't a difference between the hide action from jQuery vs Vue, so another way to do this is to trigger the jQuery through the #click function.
The Vue Dev kit will tell you not to mix JS inline with #click events and I had the same problem as #user9046370 trying to put the jQuery command inline with #click, so anyway,
Here's another way to do this:
<tr v-for="Obj1,index in Array1">
<td >{{index}}</td>
<td >
<a #click="ToggleDiv('THEDiv-'+index)">Show/Hide List</a><BR>
<div style='display:none;' :id="'THEDiv-'+index" >
<ul><li v-for="Obj2 in Array2">{{Obj2}}</li></ul>
</div>
</td>
</tr>
Method:
ToggleDiv: function(txtDivID)
{
$("#"+txtDivID).toggle(400);
},
The other perk of this is that if you want to use fancy jQuery transitions you can with this method.
<template>
<button class="btn btn-outline-secondary" type="button"><i class="fas fa-filter" #click="showFilter = !showFilter"></i></button>
</template>
<script>
export default {
methods:{
showFilter() {
eventHub.$emit('show-guest-advanced-filter');
}
}
}
</script>
But it's not worked this method.
<template>
<button class="btn btn-outline-secondary" type="button"><i class="fas fa-filter" #click="filtersMethod"></i></button>
</template>
<script>
export default {
data: () => ({
filter: true,
}),
methods: {
showFilter() {
eventHub.$emit('show-guest-advanced-filter');
this.filter = false;
},
hideFilter() {
eventHub.$emit('hide-guest-advanced-filter');
this.filter = true;
},
filtersMethod() {
return this.filter ? this.showFilter() : this.hideFilter();
}
}
}
</script>
This is worked.

Angular2 ng2-popover hide() is not working

app.module.ts
import { PopoverModule } from 'ng2-popover';
#NgModule({
declarations: [ ...],
imports: [PopoverModule],
providers: []
})
example.html
<a [popover]="customPopover" [popoverOnHover]="true" [popoverCloseOnMouseOutside]="true" href="www.google.com" (click)="$event.stopPropagation()" target="_blank">{{name}}</a>
<!--Popover content -->
<popover-content #customPopover title="{{name}}" placement="right"
[closeOnClickOutside]="true" [closeOnMouseOutside]="true">
<span class="popoverDesc">{{description}}</span><br /><br />
{{websiteLink | formatUrl:'text'}}<br /><br />
<button class="btn btn-secondary popoverBtn" (click)="toggleAddToListModalPopover($event)">Add to list</button>
</popover-content>
example.component.ts
import { PopoverContent } from 'ng2-popover';
#ViewChild('customPopover') customPopover: PopoverContent;
protected toggleAddToListModalPopover(e):void {
this.customPopover.hide();
this.showAddToListModal = !this.showAddToListModal;
e.stopPropagation();
}
I want hide the popover when modal opens. When I call the 'customPopover.hide()' function it gives me error:
error_handler.js:51 TypeError: Cannot read property 'hide' of undefined
at PopoverContent.hide (PopoverContent.js:78)
In 'PopoverContent.js' file there is line this.popover.hide(); but I have no idea how to initialize it. As my understanding is #ViewChild only initializes the class bind to #customPopover i.e. in my case popover-content. Can someone please give me a solution to initialize the 'Popover'?
I resolved it using below code i.e. add 'customPopover' as parameter in the function and call hide() method. I don't know if its a good way to resolve this or not?
example.html
<button class="btn btn-secondary popoverBtn" (click)="toggleAddToListModalPopover(customPopover, $event)">Add to list</button>
example.component.ts:
protected toggleAddToListModalPopover(customPopover, e):void {
customPopover.hide();
this.showAddToListModal = !this.showAddToListModal;
e.stopPropagation();
}
I think in your case, this.customPopover is undefined.
Other way you can hide your popover-content like this-
<div>
<popover-content #myPopover title="this header can be omitted" placement="right" [closeOnClickOutside]="true">
<b>Very</b> <span style="color: #C21F39">Dynamic</span> <span style="color: #00b3ee">Reusable</span>
<b><i><span style="color: #ffc520">Popover With</span></i></b> <small>Html support</small>. Click outside of this popover and it will be dismissed automatically.
<u (click)="myPopover.hide()">Or click here to close it</u>.
</popover-content>
<button [popover]="myPopover">click this button to see a popover</button>
</div>
See if this helps.

Categories

Resources