Creating directive like/dislike in Angular - javascript

I have something like this, like and dislike button,with font-awesome icons
<ng-container *ngFor="let answer of question.answers">
<p class="answers">{{answer.text}} <i class="fa fa-hand-o-left" (click)="likeDislike($event,answer.id,'fa-thumbs-up')"></i></p>
</ng-container>
And some function
likeDislike(event: any, answerId: string, haveClass: string) {
const hasClass = event.target.classList.contains(haveClass);
if (hasClass) {
this.renderer.removeClass(event.target, 'fa-thumbs-up');
this.renderer.addClass(event.target, 'fa-thumbs-down');
} else {
this.renderer.removeClass(event.target, 'fa-thumbs-down');
this.renderer.addClass(event.target, 'fa-thumbs-up');
}
}
I dont think this is good example, can somebody help me maybe to make a directive?

You could put it in a Component. The two-way binding is a nice extra.
A live demo
Check this stackblitz demo.
Call it like this
<app-fa-like [(liked)]='liked'></app-fa-like>
Component code
Note: You won't need the styles or the __, it's just in here for demo purposes. Font-awesome should take care of that in your app.
import {Component, Input, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'app-fa-like',
template: `
<i
class='fa'
[class.fa-thumbs-up]='liked'
[class.fa-thumbs-down]='!liked'
(click)='toggle()'
>__</i>`,
styles: [`
.fa.fa-thumbs-up{background: green;}
.fa.fa-thumbs-down{background: red;}
`]
})
export class LikeComponent{
#Input('liked') liked = true;
#Output() likedChange: EventEmitter<boolean> = new EventEmitter();
toggle(): void {
this.liked = !this.liked;
this.likedChange.emit(this.liked);
}
}

since you are using angular, you have lots of options. I'm not sure any is better or worse than the others. Here's some ideas
Use ngClass
Use *ngIf
Use angular variable. something like this would work fine:
<i class="fa {{answer.faFont}}" (click)="toggleIcon(answer)"></I>
toggleIcon(answer:any) {
answer.faFontFlg = !answer.faFontFlg;
answer.faFont = (answer.faFontFlg)?'fa-thumbs-up':'fa-thumbs-down';
}

Related

ngAfterViewInit not fired within ng-content

The ngAfterViewInit lifecycle hook is not being called for a Component that is transcluded into another component using <ng-content> like this:
<app-container [showContent]="showContentContainer">
<app-input></app-input>
</app-container>
However, it works fine without <ng-content>:
<app-input *ngIf="showContent"></app-input>
The container component is defined as:
#Component({
selector: 'app-container',
template: `
<ng-container *ngIf="showContent">
<ng-content></ng-content>
</ng-container>
`
})
export class AppContainerComponent {
#Input()
showContentContainer = false;
#Input()
showContent = false;
}
The input component is defined as:
#Component({
selector: 'app-input',
template: `<input type=text #inputElem />`
})
export class AppInputComponent implements AfterViewInit {
#ViewChild("inputElem")
inputElem: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
console.info("ngAfterViewInit fired!");
this.inputElem.nativeElement.focus();
}
}
See a live example here: https://stackblitz.com/edit/angular-playground-vqhjuh
There are two issues at hand here:
Child components are instantiated along with the parent component, not when <ng-content> is instantiated to include them. (see https://github.com/angular/angular/issues/13921)
ngAfterViewInit does not indicate that the component has been attached to the DOM, just that the view has been instantiated. (see https://github.com/angular/angular/issues/13925)
In this case, the problem can be solved be addressing either one of them:
The container directive can be re-written as a structural directive that instantiates the content only when appropriate. See an example here: https://stackblitz.com/edit/angular-playground-mrcokp
The input directive can be re-written to react to actually being attached to the DOM. One way to do this is by writing a directive to handle this. See an example here: https://stackblitz.com/edit/angular-playground-sthnbr
In many cases, it's probably appropriate to do both.
However, option #2 is quite easy to handle with a custom directive, which I will include here for completeness:
#Directive({
selector: "[attachedToDom],[detachedFromDom]"
})
export class AppDomAttachedDirective implements AfterViewChecked, OnDestroy {
#Output()
attachedToDom = new EventEmitter();
#Output()
detachedFromDom = new EventEmitter();
constructor(
private elemRef: ElementRef<HTMLElement>
) { }
private wasAttached = false;
private update() {
const isAttached = document.contains(this.elemRef.nativeElement);
if (this.wasAttached !== isAttached) {
this.wasAttached = isAttached;
if (isAttached) {
this.attachedToDom.emit();
} else {
this.detachedFromDom.emit();
}
}
}
ngAfterViewChecked() { this.update(); }
ngOnDestroy() { this.update(); }
}
It can be used like this:
<input type=text
(attachedToDom)="inputElem.focus()"
#inputElem />
If you check the console of your stackblitz, you see that the event is fired before pressing any button.
I can only think of that everything projected as will be initialized/constructed where you declare it.
So in your example right between these lines
<app-container [showContent]="showContentContainer">
{{test()}}
<app-input></app-input>
</app-container>
If you add a test function inside the app-container, it will get called immediatly. So <app-input> will also be constructed immediatly. Since ngAfterVieWInit will only get called once (https://angular.io/guide/lifecycle-hooks), this is where it will be called already.
adding the following inside AppInputComponent is a bit weird however
ngOnDestroy() {
console.log('destroy')
}
the component will actually be destroyed right away and never initialized again (add constructor or onInit log to check).

How to change display by document.getElementById inAngular

I have a scenario where I am generating dynamic elements with the data from backend some what like
<tr *ngFor="let data of datas" (click)="display(data.id)">
<div id="'full'+data.id" [style.display]=" active? 'block': 'none'">
</div>
</tr>
My Ts File
export class Component{
active=false;
display(id)
{
document.getElementById(`full${id}`).display="block";
}
}
What I want to do is something like above. I tried something like below but that doesn't work it throws error
Property 'display' does not exist on type 'HTMLInputElement'
import { DOCUMENT } from '#angular/common';
import { Inject } from '#angular/core';
export class Component{
active=false;
constructor(#Inject(DOCUMENT) document) {
}
display(id)
{
document.getElementById(`full${id}`).display="block";
}
}
any suggestions ,how to do this .Thanks
Property 'display' does not exist on type 'HTMLInputElement'
That's because HTML elements do not have a property display. What you're looking for is:
document.getElementById(`full${id}`).style.display='block';
Rather than directly manipulating the DOM, the more Angular way of doing this would be to track the visibility state of each row and drive visibility through NgIf
NgIf: A structural directive that conditionally includes a template based on the value of an expression coerced to Boolean. When the expression evaluates to true, Angular renders the template provided in a then clause, and when false or null, Angular renders the template provided in an optional else clause. The default template for the else clause is blank.
Here is an example with a single boolean driving the toggle of a single div, but you could do something similar with a map on your data.
#Component({
selector: 'ng-if-simple',
template: `
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
show = {{show}}
<br>
<div *ngIf="show">Text to show</div>
`
})
export class NgIfSimple {
show: boolean = true;
}
I've solve problem like this:
let fullId = document.getElementById(`full${id}`) as HTMLElement;
fullId.style.display='block';

Angular 6 Dropdown inside Material Tabs Error

I have an input drop-down component which is used in multiple places in the same app but in different tabs.
The issue I am facing is when I select a value from the drop-down in Tab 1 and an API call is done with the value, the same component in Tab 2 also does that with the selected value in Tab1.
How do I fix this as I am subscribing to the same service in the different tabs?
<ng-select
[items]="Groups"
[virtualScroll]="true"
bindLabel="bg_desc"
bindValue="bg_desc"
placeholder="Groups"
[(ngModel)]="selectedGroup"
[clearable]="false"
(change)="selectGroups()">
<ng-template
ng-notfound-tmp
let-searchTerm="searchTerm">
<div class="ng-option disabled">
No data found for "{{searchTerm}}"
</div>
</ng-template>
<ng-template
ng-option-tmp
let-item="item"
let-search="searchTerm">
<div
[ngOptionHighlight]="search"
class="text-uppercase">
{{item.bg_desc}}
</div>
</ng-template>
</ng-select>
This is in my component:
selectGroups() {
this._data.changeGroup(this.selectedGroup);
}
This is my service:
changeGroup(bg: string) {
this.changeGroupData.next(bg);
}
private changeGroupData = new BehaviorSubject<string>('');
currentChangeGroupData = this.changeGroupData.asObservable();
This is my stackbliz example: https://stackblitz.com/edit/angular-1oucud
I want individual calls on these tabs. Should I create three instances of same component with different names to achieve this?
Think about the architecture of your program? Should DropDownComponent really be updating a service after a model change or is more like a more specific input control and any binding or application logic should occur outside of it?
It seems to me that the second case is more appropriate, especially if you feel the need to reuse it. You can easily modify the DropDownComponent to have an Input and Output and have the outer component bind to it. Or you can go the extra mile and have your component extend NgModelAccessor, so you can use it properly in forms.
I'll give an example of the simpler approach below.
DropDownComponent is to changed to be completely standalone. It has an input and output that other components will bind to.
AppComponent has a model and the properties of the model are bound to instances of dropdown in the view. For no particular reason I also bound to the change event just to show you what happens. It really isn't necessary as doing the banana-in-a-box syntax will cause the Output to be bound to by convention - the Output having the same name as the input with Change appended to the end.
DropDownComponent.ts
export class DropdownComponent {
colors = colors;
#Input() selectedColor;
#Output() selectedColorChange = new EventEmitter<string>();
changeColor(e) {
this.selectedColorChange.emit(this.selectedColor);
}
}
AppComponent.ts
declare type ColorType = { color: string, value: string };
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
colors: { first?: ColorType, second?: ColorType, third?: ColorType } = {};
doSomething(colorKey: string) {
console.log(`The color changed was ${colorKey} with a value of ${this.colors[colorKey].value}.`)
}
}
AppComponent.html
<mat-tab-group>
<mat-tab label="First">
<dropdown [(selectedColor)]="colors.first" (selectedColorChange)="doSomething('first')"></dropdown>
<p>Selected Color is {{colors.first?.color}}</p>
</mat-tab>
<mat-tab label="Second">
<dropdown [(selectedColor)]="colors.second" (selectedColorChange)="doSomething('second')"></dropdown>
<p>Selected Color is {{colors.second?.color}}</p>
</mat-tab>
<mat-tab label="Third">
<dropdown [(selectedColor)]="colors.third" (selectedColorChange)="doSomething('third')"></dropdown>
<p>Selected Color is {{colors.third?.color}}</p>
</mat-tab>
</mat-tab-group>
You just need to use output to communicate to outer component. Thats it
https://stackblitz.com/edit/angular-1oucud

Use variable in style tag in angular template?

I am working on angular 5 application, and I have requirement of applying dynamic css in style tag in template.
I have tried some solutions but they are not working.
app.component.ts
customCss : any;
constructor(){}
ngOnInit(){
this.customCss['color'] = "red";
}
app.component.html
<div>
<span class="custom_css">This is angular 5 application</span>
</div>
<style>
.custom_css{
color: {{customCss.color}};
}
</style>
When I inspect the custom_css class in browser then in style it shows
.custom_css{
color: {{customCss.color}};
}
Any help is appreciated.
You can use [ngStyle] directive:
<span [ngStyle]="{'color': 'red'}">
This is angular 5 application
</span>
Or like so:
<span [ngStyle]="applyStyles()">
This is angular 5 application
</span>
And in component:
applyStyles() {
const styles = {'color' : 'red'};
return styles;
}
By the way if you set the color like this:
<div [style.color]="color"></div>
where color='var(--cssValue)' it would not work!
However, this works correctly:
<div [ngStyle]="{color: color}"></div>
The given answer works if you have few elements to change in a given component, if you need to change the full overall look and feel of your app based on user's choice (and on the fly), the only way i found so far is to override css in the javascript like the following:
this.stylesService.get().subscribe(({ customStyles }) => {
const style = document.createElement('style');
style.innerHTML =
`.picture {
background-image: url(${customStyles.backgroundUrl});
}
h1, h2 {
color: ${customStyles.primaryColor};
}`;
document.body.appendChild(style);
});
You can use [style.customClass]=“methodInComponent()”...
This will apply the class if the method in your component returns true.

Angular 2, dynamic bind attribute name

I have a code block angular 2 like that:
<div *ngFor="let elm of elms; let i = index" [attr.name]="name" text-center>
{{elm }}
</div>
It works fine.
But when i want to dynamic set attribute name base on index like name-1="name" name-2="name"i dont know how to do it.
I tried [attr.name-{{i}}]="name"or [attr.name- + i]="name" but it does not work. Is there any way to do it?
Many thanks.
To start off thanks to OP for the question. I eventually learnt new things by solving this answer. Below explanation on how i achieved.
Important: One catch you cannot bind the dynamic attribute to your component scope. To achieve this you need to make each div as a dynamic component and compile it. Kinda hacky i guess.
Template:
<div #looped *ngFor="let elm of elms; let i = index;" text-center>
{{elm }}
</div>
and in the component import implements AfterViewInit and ViewChildren decorator to get children elements and its changes on rendering:
import { Component, ViewChildren, QueryList, AfterViewInit } from '#angular/core';
component:
export class ExamplePage implements AfterViewInit {
elms : Array<string> = ["d1", "d2", "d3"]
#ViewChildren('looped') things: QueryList<any>;
constructor() { }
ngAfterViewInit() {
this.things.forEach((t, index) => {
let el : HTMLDivElement = t.nativeElement;
el.setAttribute("name-" + index , "dynamicAttrString");
})
}
}
Output:
I don't know weather it is possible or not but I've alternate solution
<div *ngFor="let elm of elms; let i = index" [attr.name]="{id : index, data : name}">{{item}}</div>
then you'll get object as avalue with id and data keys, hope this helps.

Categories