Using 'this' keyword in Angular component's template - html

Let's say we have a prop variable in the component class and we use it via interpolation in the template (stackblitz demo):
component class:
#Component({...})
export class AppComponent {
prop = 'Test';
...
}
template:
<p>{{ this.prop }}</p>
<p>{{ prop }}</p>
Why in Angular it's possible to use this keyword in templates without any warnings/error (even in AOT mode)? What's behind it?
Edit
According to the remark in the answer: this refers to the component itself for which the template was rendered. But I can also create a template variable and access to it using this:
<input #inp> {{ this.inp.value }}
In this case we don't have an inp variable in the component class and I still get the access to it using {{this.inp...}}. Magic?

I don't think somebody can give a very much exact answer here (maybe somebody from Angular CLI team), however the outcome I came to is that the component renderer fully ignores this keyword in the places where it seems valid (with some exceptions).
Proof
<input #heroInput value="0">
This prints the component JSON without heroInput: {{ this | json }}
<input #heroInput value="0">
This prints 0: {{ this.heroInput.value }}
<div *ngFor="let val of [1,2,3]">
<input #heroInput [value]="val">
Overrides heroInput with current value: {{ this.heroInput.value }}
</div>
This prints 0: {{ this.heroInput.value }}
One can assume from the above that this is similar to AngularJS (angular 1) scope, where the scope contains the component properties.
It does not explain why heroInput is not listed in this | json still.
However the following is totally broken:
{{ this['heroInput'].value }}
It gives an error: cannot get value of undefined. It should, not, it must work, unless (the only explanation) this is just ignored in every case but
{{ this | json }}
where it refers to the component, because this is the only way to debug the whole component object from the template. Maybe there are some other exceptions, still.
Updated stackblitz

this refers to the component itself for which the template was rendered. On the template you can access only members of the component. This means that this is implicitly added to each property which you use in the template.
This two accesses are the same - the 2nd one implicitly use this in front of it.
<p>{{ this.prop }}</p>
<p>{{ prop }}</p>
The same is when you use this in the component. When you want to access prop in the component you need to prefix it with this.prop to inform that you are accessing property of the component, not a local variable.

I felt we can't get a proper explanation on this but,
I went through a case where i will be creating the members of the component dynamically.
In here it might go wrong, if i don't use this.member (In my case it was actually this[member]).
Create a member in component like,
this.members(prop=> this[prop]={})
Usage in template will be like,
{{this[prop]}} will give expected result.
{{prop}} will not give expected result because
it will print value of list.

Related

Getting a route not defined error in Laravel Blade

I am a Laravel/web newbie, and am working with a Laravel application inherited from someone else. In the blades, I see two ways in which forms are written.
<form action="{{ route('getdata') }}"
{!! Form::open(array('url'=>'getdata','
I think in the first case, the form starts in HTML format and route alone is defined in laravel format whereas in the second case, even form is defined in Laravel format. Which is preferred and what are the differences? I tried replacing the second format with the first and got a Route [/getdata] not defined error. Laravel Version is 6.
In the first example:
<form action="{{ route('getdata') }}"
You are writing plain html, except for the {{ route('getdata') }} part, that you can read similar to <?php route('getData') ?>. Basically, just the action (the url) of the form is a PHP call to a function that will echo the url in that position, while the rest is plain html (an hardcoded text).
In the second example, {!! Form::open(array('url'=>'getdata',' you are using a Facade to access a class that will generate an html output (similar to the code from the first example ) and you are passing to the method Open() an url, that will be placed inside the generated html in the action field.
The problem that you have using the second method, is that your are not passing your route, but a string. Change it like this:
{!! Form::open(array('url'=>route('getdata'),'
To fix the error of the undefined route, you should just call name()function at the end of your route:
Route::('your_route_url','controller#method')->name('getdata');
You didn't define any route named getdata acctually.
Change your code route to url :
<form action="{{ url('getdata') }}

How can I set choice values in twig

When I put this code I have an error.
Impossible to access an attribute ("value") on a string variable ("Etudiant").
CODE:
{{ form_widget(registrationForm.typePerso,
{'choices': {'Etudiant': 'Etudiant','Enseignant' : 'Enseignant'}})
}}
This is from symfony Github:
"you cannot, because choices impact the building of the form. They are not passed as is to the template (they main goal of the Form component is not to handle the rendering, but to handle the form binding)"
https://github.com/symfony/symfony/issues/18950

Is there a way to obtain a number emitted by Observable<number> directly in html?

I'm looking for a way to have a number inside an Observable directly in html without subscribing Observable in ts file.
Searching on web I've found no conclusive answer.
number$: Subject<number> = new Subject<number>();
submitRandomNumber() {
this.number$.next(Math.floor(Math.random() * 1000));
}
<p>{{ number$ }}</p>
<button (click)="submitRandomNumber()">SubmitRandomNumber</button>
As expected result I want to see number changes on the page when button in pressed. Now I've only [object Object] printed on screen over button. Is there an html fix can I apply to html part to obtain number instead object?
Thanks a lot
You need to use async
Try <p>{{ number$ | async}}</p>
See: Demo

Tooltip not working inside a tag with ngFor attribute

Could someone explain to me why the tooltip in this piece of code using Angular 4 templates doesn't work?
<template ngFor let-channel [ngForOf]="channels">
<td>
<em *ngFor="let color of findBallsColor()" class="fa fa-circle {{ color }}" [tooltip]="id"></em>
</td>
</template>
<ng-template #id>
test
</ng-template>
If I remove the *ngFor inside the <em> tag it works fine (showing just one element obviously). I'm quite new to Angular so probably I'm missing some understanding of how it really works here.
EDIT
I found out the problem comes from the type returned from the Typescript function. In the code above, the list returned by findBallsColor() is actually an object that contains 4 fields. When I change it to just return a string it works. So the code looks similar to this:
HTML:
<template ngFor let-channel [ngForOf]="channels">
<td>
<em *ngFor="let color of findBallsColor()" class="fa fa-circle {{ status.color }}" [tooltip]="id"></em>
</td>
</template>
<ng-template #id>
test
</ng-template>
TS:
findBallsColor():any[] {
return [{"statut":"ERROR","name":"name","otherField":"value","anotherField":"value"}];
}
Does anyone know the why of this behaviour?
I had a similar problem, here's an excerpt from this Github issue, that details what is wrong with the way you get your data for your *ngFor.
Calling a function from the template is not a good practice and this is an example of why this recommendation exists.
When the tooltip is fired in the first case, it starts an angular detection cycle, which in turn calls items() again and it tries to show the tooltip again and it starts another angular detection cycle and it goes on and on and on repeatedly.
At first, the recommendation exists to avoid performance issues, but it has other consequences like in your case.
If you put a console.log in items(), you'll see it keeps being called again and again...
If calling a function is mandatory, use a pipe instead.
So, in your code, if you used a pipe or an observable or array of some sort, then your tooltip would work, but currently it just keeps calling the function.
This worked for me - add data-toggle="tooltip" to the html tag - aka the tooltip.
Then target the tooltip via the body $('body').tooltip({selector: '[data-toggle="tooltip"]'});

Angular4 ng-content gets built when ngIf is false

I have a problem with the new ng-content transclusion.
Let's say I have a component my-component that, in its ngOnInit() function does some heavy operation on load (for now, just a console.log()).
I have a wrapper, that displays the content via transclusion (my-wrapper.component.html).
<ng-content></ng-content>
If I set the surroundings up like this, the log statement doesn't show:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
I assume, the my-wrapper component does not get built, so the content is ignored.
But if I try to move the logic into the my-wrapper component like this (my-wrapper.component.html):
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
I always see the console.log() output. I guess, the my-component gets built and then stored away until the *ngIf becomes true inside my-wrapper.
The intention was to build a generic "list-item + detail" component. Say I have a list of N overview-elements (my-wrapper), that get rendered in a *ngFor loop. Every of those elements has its own detail component (my-component) that is supposed to load its own data, once I decide to show more infos to a specific item.
overview.html:
<ng-container *ngFor="let item of items">
<my-wrapper>
<my-component id="item.id"></my-component>
</my-wrapper>
</ng-container>
my-wrapper.component.html:
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
<ng-content></ng-content>
</div>
Is there a way to tell Angular, to ignore the transcluded content until it is necessary to be added to the page? Analogously to how it was in AngularJS.
Based on the comment of #nsinreal I found an answer. I find it to be a bit abstruse, so I'm trying to post it here:
The answer is to work with ng-template and *ngTemplateOutlet.
In the my-wrapper component, set up the template like this (my-wrapper.component.html):
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail" [hidden]="!isInitialized">
<ng-container *ngTemplateOutlet="detailRef"></ng-container>
</div>
Note, that the [hidden] there is not really necessary, it hides the "raw" template of the child until it decides it is done loading. Just make sure, not to put it in a *ngIf, otherwise the *ngTemplateOutlet will never get triggered, leading to nothing happening at all.
To set the detailRef, put this in the component code (my-wrapper.component.ts):
import { ContentChild, TemplateRef } from '#angular/core';
#Component({ ... })
export class MyWrapperComponent {
#ContentChild(TemplateRef) detailRef;
...
}
Now, you can use the wrapper like this:
<my-wrapper>
<ng-template>
<my-component></my-component>
</ng-template>
</my-wrapper>
I am not sure, why it needs such complicated "workarounds", when it used to be so easy to do this in AngularJS.
By doing this:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
You are not calling MyComponent component, because the *ngIf is false. that means, that not calling it you are not instancing it and, therefore, not passing through its ngOnInit. And that's why you are not getting the console log.
By doing this:
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
You are inside the component, you are just limiting what to render in your template, but you already instanced your component and, therefore, you passed through your ngOnInit and you get your console log done.
If, you want to limit something (component call with selector or a ng-content or even a div) until you have some data available, you can do the following:
datasLoaded: Promise<boolean>;
this.getData().subscribe(
(data) => {
this.datasLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data
}
);
And in your template:
<ng-container *ngIf="datasLoaded | async">
// stuff here
</ng-container>
Or:
<my-component *ngIf="datasLoaded | async">
// Didn't test this one, but should follow the same logic. If it doesn't, wrap it and add the ngIf to the wrapper
</my-component>
It’s because Ng content happens at the build time and when you pass the content it is actually not removed or recreated with the ngIf directive. It is only moved and the component is instantiated .
I encountered this problem recently as well but settled on a different solution than the currently accepted one.
Solution (TL;DR)
(Solution is for AngularDart; I figure it's similar in Angular though)
Use a structural directive; tutorials linked below.
Instead of:
<my-wrapper>
<my-contents></my-contents>
</my-wrapper>
your usage becomes:
<div *myWrapper>
<my-contents></my-contents>
</div>
which is shorthand for the following (in AngularDart; I think Angular uses <ng-template>)
<template myWrapper>
<div>
<my-contents></my-contents>
</div>
</template>
The MyWrapper directive logic is similar to NgIf except it has its own logic to compute the condition. Both of the following tutorials explain how to create an NgIf-like directive and how to pass it your own inputs using the special microsyntax (e.g. *myWrapper="myInput: expression"). Note that the microsyntax doesn't support outputs (#Output), but you can mimic an output by using an input that is a function.
Tutorial for Angular
Tutorial for AngularDart
Caveat: Since this is just a directive, it shouldn't do anything more complicated than instantiating a template ref at the appropriate time and maybe specifying some DI providers. For example, I would avoid trying to apply styles or instantiating a complex tree of components in the directive. If I wanted to create a list component, I would probably take the #ContentChild(TemplateRef) approach described in another answer; you would lose the asterisk shorthand for creating <template> but you would gain the full power of components.
My problem
My team owns an app that's part of a larger web application with other apps owned by other teams. Our components assume they can inject a MyAppConfiguration object, but this object can only be injected after it is loaded with an asynchronous request. In our app this is not a problem: we have a "shell" component that hides everything behind an ngIf until the configuration is loaded.
The problem is when other teams want to reference our components. We don't want them to duplicate the "wait until configuration is loaded" logic every time, so I tried creating a wrapper component that can be used like so:
<my-app-wrapper>
<my-app-component></my-app-component>
</my-app-wrapper>
The wrapper injects a service object and hides its contents behind an ngIf until the service says that the configuration is loaded.
Like the question poster, I discovered that the ng-content approach doesn't work as intended: while the contents are correctly hidden from the DOM, Angular still instantiates the components causing dependency injection to fail.
The solution that I settled on was to rewrite the wrapper component as a structural directive.