In svelte 3 is the $: reactive statement declaration slower or faster than HTML element class attribute? How can I measure it effectively? - performance

Since I am building a series of components that will be updated several times per second and in large quantities I was wondering if there was a difference in FPS between the first solution below and the second:
First one (HTML element class)
<script>
export let className = undefined
export let something = false
export let somethingelse = false
</script>
<div class={`myCustomClass1 myCustomClass2 ${something ? "myCustomClass3" : "myCustomClass4"}${somethingelse ? "myCustomClass5" : "myCustomClass6"}${className ? ` ${className}` : ""}`}
>
Hello!
</div>
Second one ($: reactive statement):
<script>
export let className = undefined
export let something = false
export let somethingelse = false
$: classes = "myCustomClass1 myCustomClass2${something ? "myCustomClass3" : "myCustomClass4"}${somethingelse ? "myCustomClass5" : "myCustomClass6"}${className ? ` ${className}` : ""}
</script>
<div class={classes}>Hello!</div>
Basically: is the $: reactive statement declaration slower than HTML element class attribute?
How can I measure it effectively?

I wanted to comment because this will not directly answer your question, but I don't have enough rep
to bind a class to a variable, svelte has this, it might be more practical for you :
<script>
export let classIsActive = false;
export let evenShorter = false;
</script>
<div
class:activateClass={classIsActive}
class:evenShorter
class:combinationClass={classIsActive && evenShorter}
/>

In my opinion, I think that there is no difference in performance on both methods because for both methods every time the props get updated the class attribute need to be recalculated.
However, I prefer using $: classes = ... cause it increases the readability of the code, also you may need to declare a function that handles the logic of building class attribute, for example:
export function classNames(...args){
return args.map(arg=>{
if(Array.isArray(arg)){
if(arg.length === 1){
return arg[0]
}
return arg[0] ?arg[1] :arg[2]||''
}
return arg
}).join(' ')
}
App.svelte
<script>
import {classNames} from './classNames'
export let className = undefined
export let something = false
export let somethingelse = false
$:classnames = classNames("myCustomClass1 myCustomClass2", [something, "myCustomClass3", "myCustomClass4"], [somethingelse, "myCustomClass5","myCustomClass6"], [className])
</script>
<div class={classnames}>
Hello!
</div>

Related

Mutating data in Alpine JS

I am trying to achieve something that seems trivial.
<main x-data='x'>
<p x-text='foo'>
</main>
The foo needs to be changed by some external event (callback from a library etc.)
This
window.x = {
foo: 'bar',
setFoo: foo => this.foo = foo
}
// late, after `alpine:init`
window.x.foo = 'boo' // doesn't work
window.x.setFoo('boo') // doesn't work
The same goes for the $store.
I can try and declare Alpine.data('x'), but then there is no (documented) way to call a setter.
In your example x is now an Alpine.js component, so you have to use the Alpine.js way to mutate the reactive data. First, instead of p-text, you have to use x-text:
<main x-data='x'>
<p x-text='foo'>
</main>
And to mutate data, you can access the reactive properties in $data object:
x.$data.foo = '42'
For the store you can use the global Alpine.store() method:
// Create number1 property in $store:
document.addEventListener('alpine:init', () => {
Alpine.store('number1', '0')
})
// Set number1 to 42 externally:
Alpine.store('number1', 42)

MatPaginator gets undefined

I have replicated thsi case: Angular 5 Material Table not getting data from service
But when I try access any property from paginator I get undefined error from this objet.
Any ideas?
Thanks
I got the same issue. Placing mat-paginator tag outside *ngIf resolved my issue. Make sure it is available to component class without any conditions.
Some issues that may cause mat-paginator is undefined:
You forgot to import in app.module.ts import { MatPaginatorModule } from '#angular/material'; and then declare the import in the imports array inside ngModule.
#NgModule({ declarations: [ somestuff], imports: [ MatPaginatorModule]});
Import MatPaginator inside the component you are using:
import {MatPaginator, MatSort, MatTableDataSource} from '#angular/material';
set the MatDataSource to be none. (only need to do this if you are going to be getting async data e.g. from a server)
this.dataSource = new MatTableDataSource([]);
Make sure you set the length property of the mat-paginator to the length of the data rows returned.
Set the paginator inside NgAfterViewInit method or if that doesn't work try:
private paginator: MatPaginator;
private sort: MatSort;
#ViewChild(MatSort) set matSort(ms: MatSort) {
this.sort = ms;
this.setDataSourceAttributes();
}
#ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
this.paginator = mp;
this.setDataSourceAttributes();
}
setDataSourceAttributes() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
if (this.paginator && this.sort) {
this.applyFilter('');
}
}
In some cases, the issue is related to conditional outer div. Ex:
<div *ngIf="condition">
...
<mat-paginator ....>
</div>
For a such scenario just replace *ngIf="condition" with [hidden]="!condition" and it'll work.
Please refer to https://github.com/angular/components/issues/10205 for more details
I had a similar problem and this is how I got it working:
My Initial Code Setup
component.html
<div class="chart-wrapper" *ngIf="viewType === 'chart'; else table;">
// Load Chart Here
</div>
<ng-template #table>
// Load Table Here
<mat-paginator
#paginator
[length]="tableDataSource.data ? tableDataSource.data.length : 0"
[pageSize]="pageSize"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)"
></mat-paginator>
</ng-template>
component.ts
columns: string[] = [];
tableDataSource: MatTableDataSource<any[]> = new MatTableDataSource([]);
#ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
pageIndex = 0;
pageSize = 10;
pageSizeOptions = [10, 15, 20];
ngOnInit() {
this.getTableData();
}
ngAfterViewInit() {
this.tableDataSource.paginator = this.paginator;
}
getTableData() {
// After getting data from API
this.tableDataSource.data = apiResponse;
}
The Solution
Put static: false while declaring Mat Paginator
#ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
and then set paginator onto tableDataSource after data gets loaded into it
this.tableDataSource.data = apiResponse;
this.tableDataSource.paginator = this.paginator;
Thanks to the solution by cisco336 on this thread
To my surprise nobody appraised that solution.
For me it was broken after angular upgrade from 7 to any version(although i checked in only angular 12).
In Angular 8 they introduced a parameter where we are supposed to give {static: true} if we need the value in ngOninit. From Angular 9 it was set to false by default if we are not mentioning it explicitly.
So changing code from this
#ViewChild(MatPaginator) paginator: MatPaginator;
To this
#ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
Fixed my issue.
"Inserting an *ngIf means that its contents cannot be queried until the view is initialized with change detection. This means the query for sort is empty at ngOnInit.
This can be resolved by changing the setup from ngOnInit to ngAfterViewInit. https://stackblitz.com/edit/angular-mewfek?file=src/app/app.component.ts "
reference
https://github.com/angular/components/issues/15966
In my case, paginator object was getting initiated in ngOnInit() and giving exception paginator is undefined.
Initial code i.e. not working code:
ngOnInit() {
this.dataSource = new StoreServiceDataSource<User>(this.userService, this.snackBar);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
After correction i.e. working code:
ngAfterViewInit() {
this.dataSource = new StoreServiceDataSource<User>(this.userService, this.snackBar);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
Just changed initializing life cycle hook. Earlier paginator was getting initialized in hgOnInit(), now it's initialized in ngAfterViewInit().
[length]="dataSource.filteredData.length" over "mat-paginator" worked for me.
<mat-paginator [pageSize]="10" [pageSizeOptions]="[10,25,50]" #paginator [length]="dataSource.filteredData.length"[showFirstLastButtons]="true"></mat-paginator>
MatPaginator being undefined most likely means you do not have the module imported. Sometimes angular mat does not explicitly tell you these things that you are missing. But always check the API tab under their documentation before using a component. At your module level, you should have the following in your app.module.ts file.
the import
import { MatButtonModule, MatTableModule, MatPaginatorModule, MatProgressSpinnerModule, MatTableDataSource } from '#angular/material';
Your component imported that used paginator of course
import { TableComponent } from './table/table.component';
Those modules imported in your imports array
imports: [
BrowserAnimationsModule,
NgbModule.forRoot(),
FormsModule,
RouterModule,
AppRoutingModule,
CommonModule,
MatButtonModule,
MatTableModule,
BrowserAnimationsModule,
MatPaginatorModule,
MatProgressSpinnerModule,
HttpClientModule
],
And those modules exported if necessary (different topic so I wont discuss here).
exports: [
MatButtonModule,
MatTableModule,
MatPaginatorModule
],
This is all happening in my App.Module
export class AppModule { }
This assuming you do bot have your project structured as feature modules. In that case you would really only need everything I talked about in the module in which your component lives. But in this case, where everything is under the app module, this works just fine.
<div class=" fixed-form-bottom" [hidden]="changeState">
<div class="team-footer">
<mat-paginator #paginator [pageSize]="pageSize" [showFirstLastButtons]="true" [length]="totalSize"
[pageIndex]="currentPage" (page)="pageEvent = handlePage($event)" [hidePageSize]="true">
</mat-paginator>
</div>
</div>
And in .ts file , you compare your condition and pass true/ false from there
if(this.totalSize>3){
this.changeState=true;
}
its worked for me
Check if you have *ngIf that wrap your mat-paginator or any top component, if you need hide you should use [hidden] attribute in your component
Another reason for the delay in pagination injection is one or more missing end tag such as within your html markup, which seems to impact the timing of the pagination markup DOM injection.
In this type of problem scenario a 2nd+ load of the table will show the pagination components.
There are two solutions to this problem
Solution 1:
If mat-paginator is inside *ngIf then either take it outside or use [hidden] attribute.
<div [hidden]="condition">
</div>
Solution 2:
Make sure you are not using static: true while initializing the paginator
#ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
change it to
#ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;

Dynamic pattern validation in Angular 2 and 4

Input validation works fine with a fixed pattern, e.g.
<input type="number"
[(ngModel)]="info.sortOrder"
pattern="[0-9][0-9]"
id="sortOrder" name="sortOrder" #sortOrder="ngModel"/>
When changing the pattern to be evaluated through a function, validation always fails. The function "customPattern()" is called, though.
<input type="number"
[(ngModel)]="info.sortOrder"
[pattern]="customPattern()"
id="sortOrder" name="sortOrder" #sortOrder="ngModel"/>
With
customPattern() { return "[1-9][0-9]"; }
Is this a bug or is this not supposed to work this way?
I guess you are dong wrong, #black
I would recommend you to use Reactive Forms approach to achieve the desire result.
create a reactive form.
Add the control name for eg('number_validation').
(optional) Register the HTML input element with the formControlName same as above ('number_validation').
create a field 'regex' = '[0-9][0-9]' in the component.ts file.
Bind the [pattern] = regex in the HTML HTML input element tag.
Listen the change and change the regex pattern according to the requirement.
In ts file.
someForm: FormGroup;
this.someForm= new FormGroup({
'some_name': new FormControl('', [
Validators.required])});
regex = /[0-9][0-9]/;
In HTML:
<input type="number"
[pattern]=regex
id="sortOrder"
formControlName=some_name
name="sortOrder"/>
Logic :
It depends on the requirement how you are going to change the regex value, dynamically.
eg.
ngAfterViewInit() {
this.someForm.get('some_name').valueChanges.subscribe(val => {
if (val === 'anything') {
this.regex = /change the regex/;
} else if (val === 'anything_other') {
this.regex = /change the regex/;
}
});
}
Hope it help you, or other devs! :)
In you component, simply define a member variable like this:
export class AppComponent {
customPattern = '[1-9][0-9]';
In your html, use interpolation like this:
pattern = "{{customPattern}}"
That should work.

Angular2 how to call a method only after subscribed data is completely bounded to a table using ng-for? [duplicate]

In Angular 1 I have written a custom directive ("repeater-ready") to use with ng-repeat to invoke a callback method when the iteration has been completed:
if ($scope.$last === true)
{
$timeout(() =>
{
$scope.$parent.$parent.$eval(someCallbackMethod);
});
}
Usage in markup:
<li ng-repeat="item in vm.Items track by item.Identifier"
repeater-ready="vm.CallThisWhenNgRepeatHasFinished()">
How can I achieve a similar functionality with ngFor in Angular 2?
You can use #ViewChildren for that purpose
#Component({
selector: 'my-app',
template: `
<ul *ngIf="!isHidden">
<li #allTheseThings *ngFor="let i of items; let last = last">{{i}}</li>
</ul>
<br>
<button (click)="items.push('another')">Add Another</button>
<button (click)="isHidden = !isHidden">{{isHidden ? 'Show' : 'Hide'}}</button>
`,
})
export class App {
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
#ViewChildren('allTheseThings') things: QueryList<any>;
ngAfterViewInit() {
this.things.changes.subscribe(t => {
this.ngForRendred();
})
}
ngForRendred() {
console.log('NgFor is Rendered');
}
}
origional Answer is here
https://stackoverflow.com/a/37088348/5700401
You can use something like this (ngFor local variables):
<li *ngFor="#item in Items; #last = last" [ready]="last ? false : true">
Then you can Intercept input property changes with a setter
#Input()
set ready(isReady: boolean) {
if (isReady) someCallbackMethod();
}
For me works in Angular2 using Typescript.
<li *ngFor="let item in Items; let last = last">
...
<span *ngIf="last">{{ngForCallback()}}</span>
</li>
Then you can handle using this function
public ngForCallback() {
...
}
The solution is quite trivial. If you need to know when ngFor completes printing all the DOM elements to the browser window, do the following:
1. Add a placeholder
Add a placeholder for the content being printed:
<div *ngIf="!contentPrinted">Rendering content...</div>
2. Add a container
Create a container with display: none for the content. When all items are printed, do display: block. contentPrinted is a component flag property, which defaults to false:
<ul [class.visible]="contentPrinted">
...items
</ul>
3. Create a callback method
Add onContentPrinted() to the component, which disables itself after ngFor completes:
onContentPrinted() {
this.contentPrinted = true;
this.changeDetector.detectChanges();
}
And don't forget to use ChangeDetectorRef to avoid ExpressionChangedAfterItHasBeenCheckedError.
4. Use ngFor last value
Declare last variable on ngFor. Use it inside li to run a method when this item is the last one:
<li *ngFor="let item of items; let last = last">
...
<ng-container *ngIf="last && !contentPrinted">
{{ onContentPrinted() }}
</ng-container>
<li>
Use contentPrinted component flag property to run onContentPrinted() only once.
Use ng-container to make no impact on the layout.
Instead of [ready], use [attr.ready] like below
<li *ngFor="#item in Items; #last = last" [attr.ready]="last ? false : true">
I found in RC3 the accepted answer doesn't work. However, I have found a way to deal with this. For me, I need to know when ngFor has finished to run the MDL componentHandler to upgrade the components.
First you will need a directive.
upgradeComponents.directive.ts
import { Directive, ElementRef, Input } from '#angular/core';
declare var componentHandler : any;
#Directive({ selector: '[upgrade-components]' })
export class UpgradeComponentsDirective{
#Input('upgrade-components')
set upgradeComponents(upgrade : boolean){
if(upgrade) componentHandler.upgradeAllRegistered();
}
}
Next import this into your component and add it to the directives
import {UpgradeComponentsDirective} from './upgradeComponents.directive';
#Component({
templateUrl: 'templates/mytemplate.html',
directives: [UpgradeComponentsDirective]
})
Now in the HTML set the "upgrade-components" attribute to true.
<div *ngFor='let item of items;let last=last' [upgrade-components]="last ? true : false">
When this attribute is set to true, it will run the method under the #Input() declaration. In my case it runs componentHandler.upgradeAllRegistered(). However, it could be used for anything of your choosing. By binding to the 'last' property of the ngFor statement, this will run when it is finished.
You will not need to use [attr.upgrade-components] even though this is not a native attribute due to it now being a bonafide directive.
I write a demo for this issue. The theory is based on the accepted answer but this answer is not complete because the li should be a custom component which can accept a ready input.
I write a complete demo for this issue.
Define a new component:
import {Component, Input, OnInit} from '#angular/core';
#Component({
selector: 'app-li-ready',
templateUrl: './li-ready.component.html',
styleUrls: ['./li-ready.component.css']
})
export class LiReadyComponent implements OnInit {
items: string[] = [];
#Input() item;
constructor() { }
ngOnInit(): void {
console.log('LiReadyComponent');
}
#Input()
set ready(isReady: boolean) {
if (isReady) {
console.log('===isReady!');
}
}
}
template
{{item}}
usage in the app component
<app-li-ready *ngFor="let item of items; let last1 = last;" [ready]="last1" [item]="item"></app-li-ready>
You will see the log in the console will print all the item string and then print the isReady.
I haven't yet looked in depth of how ngFor renders elements under the hood. But from observation, I've noticed it often tends to evaluate expressions more than once per each item it's iterating.
This causes any typescript method call made when checking ngFor 'last' variable to get, sometimes, triggered more than once.
To guarantee a one call to your typescript method by ngFor when it properly finishes iterating through items, you need to add a small protection against the multiple expression re-evaluation that ngFor does under the hood.
Here is one way to do it (via a directive), hope it helps:
The directive code
import { Directive, OnDestroy, Input, AfterViewInit } from '#angular/core';
#Directive({
selector: '[callback]'
})
export class CallbackDirective implements AfterViewInit, OnDestroy {
is_init:boolean = false;
called:boolean = false;
#Input('callback') callback:()=>any;
constructor() { }
ngAfterViewInit():void{
this.is_init = true;
}
ngOnDestroy():void {
this.is_init = false;
this.called = false;
}
#Input('callback-condition')
set condition(value: any) {
if (value==false || this.called) return;
// in case callback-condition is set prior ngAfterViewInit is called
if (!this.is_init) {
setTimeout(()=>this.condition = value, 50);
return;
}
if (this.callback) {
this.callback();
this.called = true;
}
else console.error("callback is null");
}
}
After declaring the above directive in your module (assuming you know how to do so, if not, ask and I'll hopefully update this with a code snippet), here is how to use the directive with ngFor:
<li *ngFor="let item of some_list;let last = last;" [callback]="doSomething" [callback-condition]="last">{{item}}</li>
'doSomething' is the method name in your TypeScript file that you want to call when ngFor finishes iterating through items.
Note: 'doSomething' doesn't have brackets '()' here as we're just passing a reference to the typescript method and not actually calling it here.
And finally here is how 'doSomething' method looks like in your typescript file:
public doSomething=()=> {
console.log("triggered from the directive's parent component when ngFor finishes iterating");
}

How to declare a variable with razor syntax within javascript block for a razor view?

I have created a razor view (Demo.cshtml) and I tried to declare razor variable within javascript block as shown below:
Code:
#{
ViewBag.Title = "Demo";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Demo</h2>
<script type="text/javascript">
$(function () {
#{
bool test = true;
}
});
</script>
I am getting few warnings as mentioned below:
Warning 1 Invalid character Demo.cshtml 10 10
Warning 2 Expected ';' Demo.cshtml 11 18
Can anyone guide me in resolving the above warning?
Well its not so easy to explain. For different needs you can use different solutions.
The easiest way is to use 'text' tag:
<script type="text/javascript">
#{
<text>
alert(#myVar);
</text>
}
</script>
And for example for String values you can do like
<script type="text/javascript">
var url = '#Url.RouteUrl("admin", new { controller = "users"})';
</script>
For boolean types you can do like in this example, but it looks like a stupid hack :)
var bool = '#ViewData.ModelState.IsValid' == "True";
And for collections you need to implement some helper method to be able to call
var myArray = #Html.ToJson(MyCSharpCollectionObject)

Resources