Angular 2, dynamic bind attribute name - javascript

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.

Related

Creating directive like/dislike in Angular

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';
}

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

Angular2: passing ALL the attributes to the child component

Don't even know the proper terminology to explain this problem
so, imagine this scenario...
There is a form-input-component and capturing some attributes and passing it down to the markup inside
<form-input placeholder="Enter your name" label="First name" [(ngModel)]="name" ngDefaultControl></form-input>
So, this is the markup, hope is pretty self explanatory...
obviously in the ts I have
#Input() label: string = '';
#Input() placeholder: string = '';
and then in the view I have something down these lines
<div class="form-group">
<label>{{label}}
<input type="text" [placeholder]="placeholder" [(ngModel)] = "ngModel">
</div>
Now, all works fine so far...
but let's say I want to add the validation rules around it...
or add other attributes that I don't capture via #Input()
How can I pass down anything else that comes down from <form-input> to my <input> in the view?
For the lazy ones:
export class FormInput implements OnInit {
#ViewChild(NgModel, {read: ElementRef}) inpElementRef: ElementRef;
constructor(
private elementRef: ElementRef
) {}
ngOnInit() {
const attributes = this.elementRef.nativeElement.attributes;
const inpAttributes = this.inpElementRef.nativeElement.attributes;
for (let i = 0; i < attributes.length; ++i) {
let attribute = attributes.item(i);
if (attribute.name === 'ngModel' ||
inpAttributes.getNamedItemNS(attribute.namespaceURI, attribute.name)
) {
continue;
}
this.inpElementRef.nativeElement.setAttributeNS(attribute.namespaceURI, attribute.name, attribute.value);
}
}
}
ngAfterViewInit won't work if you nest multiple components which pass attributes, because ngAfterViewInit will be called for inner elements before outer elements. I.e. the inner component will pass its attributes before it received attributes from the outer component, so the inner most element won't receive the attributes from the outer most element. That's why I'm using ngOnInit here.
You could iterate on the DOM attributes of your component :
import { ElementRef } from '#angular/core';
...
export class FormInput {
constructor(el: ElementRef) {
// Iterate here over el.nativeElement.attributes
// and add them to you child element (input)
}
}

Modify component with Renderer and ElementRef

1 ISSUE
I am trying to implement the following:
I have a container component ContainerComponent and child components ChildComponent. I want to modify the rendering and overall behaviour of the child components via the controlling ContainerComponent.
2 TECHNOLOGIES USED
Angular2, HTML, CSS, Javascript, Typescript, ES6
3 CODE
ContainerComponent.ts
export class ContainerComponent {
children: Array<Child>;
constructor(
private _el: ElementRef,
private _dcl: DynamicComponentLoader,
private _childService: ChildService) {
}
ngOnInit() {
let index = 0; // index of child component in container
this._childService.getChildren().then( // get the children models
(children) => {
this.children = children;
this.children.forEach((child, index) => {
this._dcl.loadIntoLocation(ChildComponent, this._el, 'dynamicChild')
.then(function(el){
el.instance.child = child; // assign child model to child component
el.instance.index = index;
});
});
}
);
}
}
ChildComponent.ts
export class ChildComponent {
child: Child;
index: number;
constructor(private _renderer: Renderer, private _el: ElementRef) {
}
ngOnInit() {
let delay = (this.index + 1) * 0.5; // calculate animation delay
this._renderer.setElementStyle(this._el, '-webkit-animation-delay', delay + 's !important');
this._renderer.setElementStyle(this._el, 'animation-delay', delay + 's !important');
}
}
4 CODE EXPLANATION
In the above code, the ContainerComponent dynamically inserts ChildComponents (granted, this could be done without the DynamicContentLoader).
The ChildComponents should dynamically add css properties, in this case, the animation delay once it is displayed. So based on the index of the child, the animation delay increases.
However the modifications from the renderer do not take effect, the css properties are not there at runtime.
I tried to reproduce your problem. In fact, I have problem to add styles like -webkit-animation-delay and animation-delay.
If I try with another style like color, it works fine and the style is taken into account at runtime.
ngOnInit() {
this._renderer.setElementStyle(this._el, 'color', 'yellow');
}
So it seems to be linked to animation styles... I see these links that could interest you:
How to set CSS3 transition using javascript?
http://www.javascriptkit.com/javatutors/setcss3properties.shtml
Otherwise it seems that there is some support for animation in Angular2 but it's not really documented... See this file: https://github.com/angular/angular/blob/master/modules/angular2/src/animate/animation.ts.
Hope it helps you,
Thierry
This seems to be a bug in angular2 itself. Adding !important to a style will result in an illegal value to the style and it is not applied to the element. The correct way in plain js is to use another parameter which implies if the style is important.
So the correct answer is to use:
this._renderer.setElementStyle(this._el, 'animation-delay', delay + 's'); //sans !important
and if you want to add !important you have to use:
this._el.nativeElement.style.setProperty('animation-delay', delay + 's', 'important');
The -webkit- prefix gets added (or removed) if necessary, so there is no need to add that as well
From here:
https://angular.io/docs/ts/latest/api/core/ElementRef-class.html
You should only use ElementRef as an absolute last resource. The whole idea of Angular 2 is that you don't have to mess with the dom at all. What you are trying to do can be acomplished very easy using a template:
import {NgStyle} from 'angular2/common';
import {Component} from "angular2/core";
#Component({
selector: 'app',
template: `
<div *ngFor="#child of children; #i = index">
<div [ngStyle]="{ 'z-index': i * multiplier,
'-webkit-animation-delay': i * multiplier + 's',
'animation-delay': i * multiplier + 's' }"> {{i}} - {{child}} </div>
</div>
`,
directives: [NgStyle]
})
export class AppComponent{
public children:string[] = [ "Larry", "Moe", "Curly" ];
public multiplier:number = 2;
}
Depending on the browser you might see those css properties or not, that's why I added the z-index which is more common and old so you can see you can render the css value dynamically using the index variable from ngFor inside a template.
I hope this helps !

Categories