Arbitrarily run method during ngFor loop (Angular 5) - html

I have an angular page, where, during an *ngFor loop, I want to update a variable, then write it to the HTML during each iteration of the loop.
Like so:
HTML:
<table *ngFor="let data of Dataset">
somehowRunThis(data)
<div>{{methodResult}}</div>
</table>
TS:
...
methodResult: any;
...
somehowRunThis(data): {
let a;
...
this.methodResult = a;
}
etc etc.
Is there any way this can work? Attempting to add a method that returns within the curly brackets seems to not work, and there appears to be no effective way to run arbitrary methods from the HTML in Angular.
Thank you for any assistance you can provide.

Is there any particular reason why you want to trigger this update in HTML?
Depending on your needs you can use pipe (https://angular.io/guide/pipes) or transform the data to desired format in your component.
I would say it's not a good idea to have a method with side-call effects invoked in HTML.

There are a lot of ways to do this. A general advice: sometimes we are looking for an answer in the wrong places, be open :)
Instead of forcing ngFor, just run a simple array.map on your data before sending it to the template.
displayData = this.data.map(el => this.somehowRunThis(el))
this way you'll avoid having terrible performance.
If you don't care and still want to do this thing for some reason you can make your function return it and actually call in template:
{{ myFunctionReturnsText() }}
This is a bad idea because the function calls will run on each change detection so something like Pipes/Directives will be better.

Related

JS method not defined

I want to use a java-script method in a polymer Template. I am using Vaadin with Polymer Elements. In my Project I have a Vaadin-Grid of Objects that can be of different type. I want to render these types with different Templates.
This problem can be solved with a dom-if template, as described by ollitietavainen in this answer
This works perfectly, but there is a problem. When using more than two different Types of Objects in the Grid, one would need to use the same amount of booleans to set that up. Suppose we have a fictional shop that displays PC-Parts, and each type of PC-Part needs to be rendered with its own template, then we would need something like the fallowing. This is quite cumbersome.
private boolean isMemory(AbstractPcPart pcPart) {
return pcPart.getClass().equals(Memory.class);
}
private boolean isGraphicsCard(AbstractPcPart pcPart) {
return pcPart.getClass().equals(GraphicsCard.class);
}
private boolean isCPU(AbstractPcPart pcPart) {
return pcPart.getClass().equals(CPU.class);
}
// … is-checker for all other types of pcParts.
private void initColumn() {
addColumn(Objects.requireNonNull(CardFactory.getTemplate())
.withProperty("partCard", CardFactory::create)
.withProperty("isMemory", this::isMemory)
.withProperty("isGraphicsCard", this::isGraphicsCard)
.withProperty("isCPU", this::isCPU)
// add all other properties
);
}
The corresponding Templates would look something like this.
<template is='dom-if' if='[[item.isMemory]]'>"
<memory-card part-card='[[item.partCard]]'>
</memory-card>"
</template>
<template is='dom-if' if='[[item.isGraphicsCard]]'>"
<graphics-card part-card='[[item.partCard]]'>
</graphics-card-card>"
</template>
<template is='dom-if' if='[[item.isCPU]]'>"
<cpu-card part-card='[[item.partCard]]'>
</cpu-card>"
</template>
<!-- one additional template for every type of part -->
The question now is, if there is any other way, that would not be needing all these Properties.
Luckily there is, as Kuba Šimonovský explained in an answer to another question.
Using this method we could rewrite the code from above to something like the fallowing.
private String type(AbstractPcPart pcPart) {
return pcPart.getClass().getSimpleName();
}
private void initColumn() {
addColumn(Objects.requireNonNull(CardFactory.getTemplate())
.withProperty("partCard", CardFactory::create)
.withProperty("type", this::type));
}
This time we use a java-script method to conditionally select the corresponding template.
<template is='dom-if' if='[[_isEqualTo(item.type, "Memory")]]'>"
<memory-card part-card='[[item.partCard]]'>
</memory-card>"
</template>
<template is='dom-if' if='[[_isEqualTo(item.type, "GraphicsCard")]]'>"
<graphics-card part-card='[[item.partCard]]'>
</graphics-card-card>"
</template>
<template is='dom-if' if='[[_isEqualTo(item.type, "CPU")]]'>"
<cpu-card part-card='[[item.partCard]]'>
</cpu-card>"
</template>
<!-- one additional template for every type of part -->
The Polymer Template is a bit more complicated now, but on the java side, the code is much shorter, and possibly easier to maintain. There is probably still some overhead, as every template gets added to the dom. But in addition to that only the content from the templates that we want to see gets added to the dom.
I don’t think there is a better way to do this though.
So using this method, we need a java-script method called _isEqualTo. This method is not a standard method so we need to implement it ourselves. The implementation for this method is straightforward.
function _isEqualTo(one, other) {
return one == other;
}
But the answer from Kuba does not specify where to implement this method. I have tried to put the method in different places with no luck. The js console in my browser always complains that it can not find the method.
Digging a little bit deeper I found this Link. So maybe what i want to have is a global variable.
window._isEqualTo = function(one, other) {
return one == other;
}
But even with this change the same warning persists. What’s weird is that the function is visible in the interactive console in the developer tools. Setting a breakpoint in the java-script file that i have added the function; and calling the function in the console reveals that it is really the correct function that get’s called, leading me to beleave that the function gets initialized too late in the lifecycle of the application. Although I am not sure at all.
And because the function is not found, the grid in the view will be empty. It still shows the rows, but they don’t show content.
I really hope someone can help me out.
Here is a Git-Repository to reproduce my problem. The concerning views are the PartsDomIfView and the PartsDomIfElegantView.
Instead of using the deprecated TemplateRenderer, you could create a LitRenderer (v22+) and create a custom lit component that can be used there as your column's content. In there you could create complex logic based templates as a separate component, that can be better maintained.

Component selector in variable - Angular 2

I have written my own table module. Calling it in HTML code looks like this:
<my-table [data]="variableWithArr"></my-table>
Now, pretty nice table is being displayed. Cool. But what if I want to have a progress bar in some column of table? I thought that I could put a HTML code with component selector as value, for example bootstrap progressBar, like this:
for(let record of variableWithArr) {
record[0] = '<ngb-progressbar type="danger" [value]="100"></ngb-progressbar>';
}
Unfortunatelly, Angular displays only a HTML code but dooes not interpret it as component selector, so I receive something like that in DOM:
<td><ngb-progressbar type="danger" [value]="100"></ngb-progressbar></td>
How to fix it?
This is not how Angular works - you can't insert arbitrary HTML (innerHTML or otherwise) and expect that directives will be picked up & applied. Making Angular work this way would require shipping entire compiler to a browser and would defeat the whole purpose of all the great optimizations that can be done with the ahead-of-time (AOT) compilation.
tl;dr; nope, you can't do this and this has nothing to do with the ng-bootstrap project, but rather with design decisions behind Angular.
By looking at the docs you need to use the property [innerHTML], but to be clear only use it when you trust the code!!
So should be something like this:
<td [innerHTML]="record"></td>

Binding dynamically within an ng-repeat expression

For a TV Guide, I am trying to create a dynamic expression within an ng-repeat directive as follows:
<div ng-repeat="programme in programmes['{{channel}}-wed-jan-14']" alt="{{channel}}">
{{channel}} in my controller should evaluate to something like "eTV". The binding is working fine with the alt="{{channel}}" instance but not with the array instance. Angular simply serves up the line of code commented out. If I hardcode the "eTV" string in place of the {{channel}}, it works fine.
Am I trying to ask Angular to do what it is not designed for, or is it possibly my array handling which is dodgy?
Okay, not sure if I just asked a dumb question, but in the absence of responses, I managed to figure out a solution by writing a filter as follows:
Template:
<div ng-repeat="programme in programmes | getChannelDay:channel:dayString" alt="{{channel}}">
Controller filter:
app.filter('getChannelDay', function() {
return function(programmes, channel, dayString) {
return programmes[channel + dayString];
};
});
The issue with my initial problem
<div ng-repeat="programme in programmes['{{channel}}-wed-jan-14']" alt="{{channel}}">
is that I was trying to put {{channel}} inside the expression, but that is the format for markup.
I tried to use the following instead:
<div ng-repeat="programme in programmes['channel + daystring']" alt="{{channel}}">
but I am doing something wrong here. I am pretty sure there is a way to get this to work - if anyone knows, please comment.

data-bind: value - parenthesis - observable

I started working with knockout a few months ago and so far it is being a very good road. Today when I was working with some inputs in my html I came across a very boring issue that took me a while to figure out. Here is my code:
<div class="add-box" style="display:none;" id="new-user">
<textarea placeholder="Name" data-bind="value : name"></textarea>
</div>
<script>
function UserViewModel() {
var self = this;
self.name= ko.observable('');
}
$(document).ready(function () {
ko.applyBindings(new UserViewModel(), document.getElementById('new-user'));
})
</script>
This code works fine, but the first time that I did was like this:
<textarea placeholder="Name" data-bind="value : name()"></textarea>
The only difference between them are the parenthesis () at the end of the name property. Since this is a observable one I thought that the parenthesis would be necessary in order to make the 2-way-binding. But with them, whenever I change the value of the textarea the viewmodel is not update accordingly, if I remove everything works.
Could you explain why on this case I have to remove the parenthesis, and why in other scenarios, like when I used data-bind="text: I have to put them??
Here is the magic with KO: special "Observable" function-objects.
When you use parenthesis, you evaluate the observable (which is just a special function) which results in a value that breaks "live" data-binding: in this case the underlying value (say, a string) is bound, but not the observable from which the value was obtained.
The underylying bindings are (usually) smart enough to deal with both observables and non-observable values. However, bindings can only update observables and can only detect Model changes through observables.
So, usually, do not include parenthesis when using obervables with declarative data-binding.
Passing the observable will make sure the Magic Just Works and allow the View and Model to stay in sync. Changes to said bound observable will trigger the appropriate binding update (e.g. so that it can update the HTML) even if the binding does not itself need to update the observable/Model.
However, in some rarer cases, you just want the value right then and you never want the binding to update from/to the Model. In these rarer cases, using parenthesis - to force value extraction and not bind the observable itself - is correct.
In my case I was using jquery.tmpl ,
and knockout 2.2.0 works with jquery.tmpl, when I upgrade to knockout 3.0, I got this problem
when I use this one, It somehow get conflict with Knockoutjs builtin template/
Removing jquery.tmpl.js resolves my problem.

MVC Razor view make image invisible based on model item condition

I have some razor code like:
foreach (var item in projectGroup) {
<tr>
...
<td>
<label id="#( "fieldapprovallabel" + #item.InvoiceLineId)">#item.FieldUserName</label>
#if(item.FieldApproved != null) {
<img src="../../Content/Images/stock_lock.gif" alt="locked" class="lockicon" />
}
</td>
...
}
So basically the lock image is only visible based on a condition. I understand it is not good to have logic like this in the view. Can anyone suggest a better way of doing this?
I don't believe there is anything wrong with simple boolean logic or null checking in the view.
The only alternative would be to hide it all in some helper class that would return the html (or an empty string), which I think adds unnecessary complexity in a simple case like this.
You could create a view model with a property called IsLocked. This abstracts the business logic used to make the "IsLocked" determination, and doesn't state how the view should behave, just informs the view that the item is in a particular state.
The subsequent binding code would be similar, but doesn't require the view to know that item.FieldApproved equals a locked condition.
Of course, in the context of your application item.FieldApproved may already be a clean separation of concerns, in which case I think it's fine as is.