I need to know if there is a way to create HTML local variables programmatically.
I am developing a web app where I have an NgFor loop and I want to be able to assign a local variable to each sub element created by the NgFor.
ie :
<div *ngFor="#elt of eltList" >
<span #setLocalVariable(elt.title)></span>
</div>
setLocalVariable(_title : string){
let var = do some stuff to _title;
return var;
}
The exemple above shows you what I am trying to accomplish and obviously does not work.
Is there a way to achieve this ?
Thank you in advance.
Edit:
After seeing the answers I got (and i thank everyone who took the time to read my question and tried to answer it) i'll explain a bit more why i want it that way.
I will be using : loadIntoLocation() from the DynamicComponentLoader.
That function got as a 3rd parameter a string that refers to an anchors (ie : #test in an html element). Thats why i need to create those local variables with a name equal to the one of my elt.title.
I think local variables (defined with the # character) don't apply for your use case.
In fact, when you define a local variable on an HTML element it corresponds to the component if any. When there is no component on the element, the variable refers to the element itself.
Specifying a value for a local variable allows you to select a specific directive associated with the current element. For example:
<input #name="ngForm" ngControl="name" [(ngModel)]="company.name"/>
will set the instance of the ngForm directive associated with the current in the name variable.
So local variables don't target what you want, i.e. setting a value created for the current element of a loop.
If you try to do something like that:
<div *ngFor="#elt of eltList" >
<span #localVariable="elt.title"></span>
{{localVariable}}
</div>
You will have this following error:
Error: Template parse errors:
There is no directive with "exportAs" set to "elt.title" ("
<div *ngFor="#elt of eltList" >
<span [ERROR ->]#localVariable="elt.title"></span>
{{localVariable}}
</div>
"): AppComponent#2:10
Angular2 actually looks for a directive matching the provided name elt.title here)... See this plunkr to reproduce the error: https://plnkr.co/edit/qcMGr9FS7yQD8LbX18uY?p=preview
See this link: http://victorsavkin.com/post/119943127151/angular-2-template-syntax, section "Local variables" for more details.
In addition to the current element of the iteration, ngForm only provides a set of exported values that can be aliased to local variables: index, last, even and odd.
See this link: https://angular.io/docs/ts/latest/api/common/NgFor-directive.html
What you could do is to create a sub component to display elements in the loop. It will accept the current element as parameter and create your "local variable" as attribute of the component. You will be able then to use this attribute in the template of the component so it will be created once per element in the loop. Here is a sample:
#Component({
selector: 'elt',
template: `
<div>{{attr}}</div>
`
})
export class ElementComponent {
#Input() element;
constructor() {
// Your old "localVariable"
this.attr = createAttribute(element.title);
}
createAttribute(_title:string) {
// Do some processing
return somethingFromTitle;
}
}
and the way to use it:
<div *ngFor="#elt of eltList" >
<elt [element]="elt"></elt>
</div>
Edit
After your comment, I think that you try the approach described in this answer. Here are more details: create dynamic anchorName/Components with ComponentResolver and ngFor in Angular2.
Hope it helps you,
Thierry
You could stick it into the template interpolation since it handles expressions.
<div *ngFor="#elt of eltList" >
<span>{{setLocalVariable(#elt)}}</span>
</div>
setLocalVariable(_title : string){
let var = do some stuff to _title;
return var;
}
Related
I am iterating through a LARGE list of objects all of which will open the same modal window that will be loaded with dynamic information. To make this work, I create a counter called MenuCounter that I know increments just fine.
That said, I am attempting to wrap a hyperlink around the icons I need to use and the injection of the method keeps pointing to the last value of the MenuCounter.
I first tried this:
...
When I ran into the issue, I tried reducing the code to the following but then the page somehow activates the hyperlink and the modal window appears and will not go away.
...
Can somebody please help me out?
Thank you!
You should apply a lambda expression to the Blazor #onclick directive instead of using the onclick Html attribute, in which case it should call a JS function, which you did not mean.
Note that I've introduced a new directive to prevent the default action of the anchor element: #onclick:preventDefault
Test this code:
#page "/"
<a href="#" #onclick:preventDefault #onclick="#(() => SetupChangeName(MenuCounter))" >Click me...</a>
<div>Counter is #output</div>
#code
{
private int MenuCounter = 10;
private int output;
private void SetupChangeName (int counter)
{
output = counter;
}
}
Note: If you use a for loop to render a list of anchor elements, you must define a variable local to the loop, and provide it as the input to your lambda expression, something like this:
#for(int MenuCounter = 0; MenuCounter < 10; MenuCounter++)
{
int local= MenuCounter;
<a href="#" #onclick:preventDefault #onclick="#(() =>
SetupChangeName(local))" >Click me...</a>
}
otherwise, all the lambda expressions will have the the same value for MenuCounter, which is the value incremented for the last iteration. See For loop not returning expected value - C# - Blazor explaining the issue.
I'm not a fan of onclick attributes, but if you're set on this method, I believe you just need to santize the C# and JS in the same line like this:
...
Adding the quotes will ensure at least an empty string is present for JS, and then you can process it.
Alternative method
Since mixing languages like that is quite frustrating, I find it easier to use data tags, for example
...
And then in your JS file:
var links = document.querySelectorAll('[data-menu-counter]');
links.forEach(x => x.addEventListener('click', /* your function code here */);
I have an Angular component that generates mat-checkbox dynamically at runtime and I need to change the individual background of each checkbox differently with different color and I don't (won't) have the information before hand, only available at runtime.
I have the following ng-template for the checkboxes:
<ng-template #renderCheckbox let-id="id" let-attr="attr">
<mat-checkbox
[checked]="attr.show"
[color]="'custom-' + id"
(change)="onChange($event.checked, attr)">
{{attr.name}}
</mat-checkbox>
</ng-template>
where, attr in the template has the following interface type, these infomation are pulled from Highcharts' series and I didn't want to hardcode the color.
interface LinkedSeriesAttributes {
id: string;
name: string;
index: number;
color: string;
checked: boolean;
}
Since there is no way to create css classes before hand and there is no way to directly apply color to the mat-checkbox, I could only generate the <style>...</style> right at the beginning of my template.
In my component, I have code that will generate the style which would give me something like this:
.mat-checkbox.mat-custom-hello.mat-checkbox-checked .mat-checkbox-background::before {
color: #6E8BC3 !important;
}
.mat-checkbox.mat-custom-world.mat-checkbox-checked .mat-checkbox-background::before {
color: #9ED6F2 !important;
}
...
However, I tried various ways to dump it inside <style> without success. I tried:
<style>{{ dynamicCSSStyles }}</style>
Which, my IDE shows that's an error with the curly braces, although it compiled fine and ran without errors, I got nothing, can't even see the <style> tag.
I also tried to include <style> inside my dynamicCSSStyles variable, and angular just dumped the whole thing out as text...
What's the correct way to generate a <style> in Angular.
I've found a REALLY dirty way of "making this work" but it causes Angular to keep adding the <style> back into the DOM.
First, set encapsulation to ViewEncapsulation.None.
Second, create a function to generate the <style> tag the old fashion way with an id:
updateDynsmicStyleNode() {
const id = 'dynamic-css-styles';
const nativeElm = this.elmRef.nativeElement;
const existing = nativeElm.querySelector(`style#${id}`);
if (!existing) {
const styleTag = document.createElement('style');
styleTag.setAttribute('id', id);
styleTag.innerHTML = this.dynamicCSSStyles;
nativeElm.prepend(styleTag);
} else {
existing.innerHTML = this.dynamicCSSStyles;
}
}
Third, call our function in ngAfterViewChecked:
ngAfterViewChecked() {
this.updateDynsmicStyleNode();
}
I mean while this worked, it is really bad, since moving the mouse around the screen would cause Angular to just continuously reinsert the <style> tag.
Does anyone know some other way more legit to archive this? LOL
You can use ngClass or [class] attribute. Since you can have the styles ready from the component.ts file.
You can do something like this:
Way 1: If you already know what the dynamic ids might be, (like if it always will be 'hello' and 'world')
let dynamicClasses = {};
// Once you get some classes from your logic, you can add them to the object above
dynamicClasses['hello'] = 'custom-hello';
dynamicClasses['world'] = 'custom-world';
// Then in HTML
<mat-checkbox [ngClass]="dynamicClasses"></mat-checkbox>
Way 2: If you dont know what the classes also might be, like if its not always be hello or world, then create a method and call it where required, you might need to do something similar to #codenamezero said.
I have 20+ elements, which all should use the same class (animate.css)
It is super annoying to change all classes if I want to edit the animation, so I saved the animation class in my service in a variable:
animClassSecond = "animate__animated animate__bounceInUp";
But I cant figure out how to add it to [ngClass], this does not work:
[ngClass]="{'select_elem':true, 'btn_2':true, 'dataService.animClassSecond':true}"
[ngClass]="{'select_elem':true, 'btn_2':true, 'this.dataService.animClassSecond':true}"
[ngClass]="{'select_elem':true, 'btn_2':true, this.dataService.animClassSecond:true}"
[ngClass]="{'select_elem':true, 'btn_2':true, dataService.animClassSecond:true}"
Its either a template error or it does not resolve to the variable. Any ideas?
P.S.: Adding a second [ngClass] attribute also does not work, because the first one is ignored.
is:
[ngClass]="dataService.animClassSecond"
But remember that you need declare the service public in the constructor
constructor(public dataService:DataService){}
NOTE you can use class and [ngClass] in the same tag:
class="select_elem btn_2" [ngClass]="dataService.animClassSecond"
This is probably not achievable in the template since the Angular template language is quite limited.
Just move the logic of ngClass object into your component.ts. There you can use all TypeScript's power
ngOnInit() {
this.ngClassObj = { [dataService.animClassSecond]: true };
}
or if you need it to be dynamic (use this one carefully because it might become a performance issue)
get ngClassObj() {
return { [dataService.animClassSecond]: true };
}
and then
[ngClass]="ngClassObj"
I'm wondering, is there a possibility to have databindings "out of" a template? Say I have a <template/>-Tag somewhere which I put into the slot of a different component - that component stamps it to its context. Then I want to bind data from the root element to the <template/>-Tag. Also, event bindings (on-x-changed) don't work, because you can't assign a function which is defined in the hosting component. Any ideas?
Example:
... host
{{boundData}}
<binding-component>
<template>
{{boundData}}
</template>
</binding-component>
I don't see changes when I observe boundData in the hosting component. Is there a way to get around this? Or is firing a custom event my only chance?
If you are looking for binding a property outside of polymer something like from index.html you may bind value with element. an example ; index.html
<dom-bind>
<template>
<binding-component bound-data="{{boundData}}"></binding-component>
</template>
</dom-bind>
<script>
// set a value a string, Number or Object etc.
// Optionally wrap this code into a listener ie;
// window.addEventListener('load', e=> { ...below code ... })
var boundData= document.querySelector('dom-bind');
boundData = {} //
</script>
Now in your binding-component element has a property as boundData
hope its helps or provide more code to understand better.
I've made it work the way dom-if does it, too. Like in dom-if (reference), I'm creating a Templatize-instance which then uses forwardHostProp to handle the "inside"-properties
this.__ctor = Templatize.templatize(template, this, {
mutableData: true,
forwardHostProp(prop, value) {
// handling item updates, item being the only property
// from within the binding component
// everything else is automatically bound by templatize
this.set(prop, value);
this.update(this.item);
},
});
this.__instance = new this.__ctor();
this.root.appendChild(this.__instance.root);
This all happens in connectedCallback.
Because the Templatize-instance is passed this, it's bound to the current context as well.
Good luck!
I have a Vue component that I want to appear sometimes in one spot, sometimes in another. So I give it a property which I test in a v-if directive on the root of template. I have two custom tags in my markup, and assume that I'll get two distinct instances of my component. To my surprise, the component renders twice, even when one of the properties is false. What is going on here?
markup
<div id='vueRoot'>
<now-you-see-me show-me='true'></now-you-see-me>
<now-you-see-me show-me='false'></now-you-see-me>
</div>
js
Vue.component('now-you-see-me',{
template : `<div v-if='showMe'>I only want ONE **{{showMe}}**</div`,
props : ['showMe']
})
vm = new Vue({
el : '#vueRoot'
})
running here
For true and false bindings, you have to use v-bind:show-me instead of just show-me, because the attribute's value will otherwise be evaluated as a string instead of a boolean exprsesion, e.g.:
<now-you-see-me v-bind:show-me='true'></now-you-see-me>
...or if you're more comfortable with shorthands:
<now-you-see-me :show-me='true'></now-you-see-me>
See proof-of-concept below:
Vue.component('now-you-see-me',{
template : `<div v-if='showMe'>I only want ONE **{{showMe}}**</div>`,
props : ['showMe']
})
vm = new Vue({
el : '#vueRoot'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id='vueRoot'>
<now-you-see-me :show-me='true'></now-you-see-me>
<now-you-see-me :show-me='false'></now-you-see-me>
</div>
You are passing the show-me property as a string, when you actually want to pass it as a boolean. Instead of show-me="..." write v-bind:show-me="..." (or :show-me="..."). Everything inside the " then will be interpreted as Javascript instead of a string.
https://jsfiddle.net/aw3bup8u/1/