What is the meaning of the syntax: <element #foo /> - html

I am reading a tutorial and I have found a sample code with a line looking like this:
<input #foo />
I think that it's an input with id="foo". But isn't the correct syntax this one:
<input id="foo" />

It's called a template reference variable in Angular. it can refer to a DOM element,Directive or web component etc. According to the official documentation -
A template reference variable is often a reference to a DOM element within a template. It can also be a reference to an Angular component or directive or a web component.
Source : https://angular.io/guide/template-syntax#ref-vars
Example : We can use #ViewChild() with Template Variable using ElementRef to access Native Element. #ViewChild() can instantiate ElementRef corresponding to a given template reference variable. The template variable name will be passed in #ViewChild() as an argument.
HTML :
<div>
Name: <input type="text" #name> <br/>
City: <input type="text" #city>
</div>
Component code :
import { Component, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
#Component({
selector: 'app-theme',
templateUrl: './apptheme.component.html'
})
export class AppThemeComponent implements AfterViewInit {
#ViewChild('name')
private elName : ElementRef;
#ViewChild('city')
private elCity : ElementRef;
ngAfterViewInit() {
this.elName.nativeElement.style.backgroundColor = 'cyan';
this.elName.nativeElement.style.color = 'red';
this.elCity.nativeElement.style.backgroundColor = 'cyan';
this.elCity.nativeElement.style.color = 'red';
}
}
using #name and #city above we can access the native elements style properties.

Related

How to pass Angular directive by reference?

In an existing component template I have this (simplified) element:
<input type="button" #refreshPrice />
This is picked up (I don't know the correct term) by this property so we can subscribe to it's click event and call a function when the input element is clicked.
I want to replace this input element with a component I've developed, which would make it look (simplified) like this:
<spinner-button #refreshPrice></spinner-button>
This child component has this as its (simplified) template:
<button>
<mat-spinner></mat-spinner>
</button>
So now the button element, in the child component template, needs to have the #refreshPrice hash attribute (?) attached.
To do this, perhaps the spinner-button element should take the name of the hash attribute as an attribute value. Here is the complete spinner component class file:
import { Component, Input, OnInit } from "#angular/core";
#Component({
selector: "spinner-button",
templateUrl: "./spinner-button.component.html",
styleUrls: ["./spinner-button.component.css"]
})
export class SpinnerButtonComponent implements OnInit {
constructor() {}
#Input() targetElement: string;
ngOnInit() {
}
}
In theory, the targetElement property can then be attached to the button element as a hash attribute - but how is this done?
the #Input() attribute here allows you to bind a value to a variable on your component, if you want to have the parent do something based on your components data, you might want to use #Output() and emit a custom event. If the requirement is just listen to a click event then adding a (click)=functionToBeCalled() should help your cause here.
You can refer to the official docs as well:
https://angular.io/guide/inputs-outputs

How to make material <input matInput> become readonly with custom directive?

I want to make <input> which using material matInput to become readonly using my custom directive. The directive isControlReadonly will be used to set readonly based on security criteria. The problem is it works on <input> but has no effect on <input matInput>
So.. this first input works, the second doesn't:
<!-- this works -->
<input type="input" formControlName="test_field" isControlReadonly>
<!-- this doesn't works -->
<mat-form-field appearance="standard">
<mat-label>Name</mat-label>
<input matInput type="input" formControlName="customer_name" isControlReadonly>
</mat-form-field>
This is the directive:
import { Directive, ElementRef, OnInit, ViewChildren } from '#angular/core';
import { SecurityService } from './security.service';
#Directive({selector: '[isControlReadonly]'})
export class IsReadonlyDirective implements OnInit {
constructor(
private elementRef: ElementRef,
private securityService: SecurityService
) { }
ngOnInit(): void {
var ro = !this.securityService.user.canEdit
this.elementRef.nativeElement.setAttribute('readonly', ro)
}
Please help how to make that custom directive works with matInput?
Edit Note: I don't want to set readonly from reactive form since directive is much cleaner code. I don't want to add logic to my component, I want the logic in the directive since it is centralized and will be used on all forms.
Thanks
EDIT: provided disable solution before.
To make a input readonly move your logic to "ngAfterViewInit"
Here is a working example for your directive:
#Directive({selector: '[isControlReadonly]'})
export class IsReadonlyDirective implements OnInit, AfterViewInit {
constructor(
private elementRef: ElementRef,
private control: NgControl,
private securityService: SecurityService
) { }
ngAfterViewInit(): void {
var ro = !this.securityService.user.canEdit
this.elementRef.nativeElement.setAttribute('readonly', ro)
}
}
Your directive works fine for all kind of inputs but in case of mat-input, the readonly attribute is set by your directive is overridden by Angular Material's own readonly #Input.
Refer Mat-Input's source code here : Mat-Input Readonly #Input for more info
So what you can do is get your code execution of setting attribute out of the stack and hand it over to event loop. This way your code shall be executed after Angular's manipulations. And the most common way to accomplish that is setTimeout with 0 sec delay
setTimeout(() => {
this.elementRef.nativeElement.setAttribute('readonly', ro)
});
Here is a working BLITZ

Angular interpolate inside a component like mat-checkbox

So I want to have a mat-checkbox component with a HTML string inside the label.
I tried the following:
<mat-checkbox class="check">
{{ someHtml }}
</mat-checkbox>
But it prints the HTML string as a string and doesn't render it.
Using the following doesn't work either:
<mat-checkbox class="check" [innerHtml]="someHtml">
</mat-checkbox>
This just replaces the whole content, including the checkbox that gets generated at runtime. Is there any way to inject the html into the label?
You could use Angular Directives
The idea here is to fetch the element from the HTML, then append some raw HTML dynamically.
Supose this scenario
app.component.html
<mat-checkbox class="check" [appendHtml]="innerHtml"></mat-checkbox>
app.component.ts
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
innerHtml = `<div style="border: 1px solid red;"> Text inside </div>`;
constructor() {}
}
As you can see, I added a appendHtml attribute to the mat-checkbox element. This is a custom directive that expects a string as "raw" HTML.
append-html.directive.ts
#Directive({
selector: '[appendHtml]'
})
export class AppendHtmlDirective implements AfterViewInit {
#Input('appendHtml') html: string
constructor(private element: ElementRef) {
}
ngAfterViewInit() {
const d = this.element.nativeElement.querySelector('label');
d.insertAdjacentHTML('beforeend', this.html);
}
}
The AppendHtmlDirective expects an html property of type string and implements AfterViewInit interface (from Angular) to fetch the element once it is rendered. By injection, Angular provides us the element which is being applied; so, the ElementRef from the constructor is our MatCheckbox element, in that case.
We can use the insertAdjacentHTML function to append childs to the element. I just fetched the label element from the MatCheckbox to fit inside of it. In every case, you should see where to append the HTML.
I mean, label here works, bc MatCheckbox has a tag whitin matching that. If you want to reuse this Directive for other elements, you should be passing the literal to find inside.
i.e.:
append-hmtl.directive.ts
// ...
#Input() innerSelector: string
// ...
ngAfterViewInit() {
const d = this.element.nativeElement.querySelector(this.innerSelector);
d.insertAdjacentHTML('beforeend', this.html);
}
app.component.hmtl
<mat-checkbox class="check" [appendHtml]="innerHtml" innerSelector="label"></mat-checkbox>
Moreover, you can pass as many inputs as you need to customize the styling or behavior of your directive.
Cheers
I think you should just wrap everything in a div and put it on the outside.
<div>
<mat-checkbox class="check"> </mat-checkbox>
{{ someHtml }}
</div>

Access host component in a structural directive

I'm using Angular 5.2.10. Suppose we have the following template:
<mat-form-field *appEntityValidate>
<input
matInput
type="text"
placeholder="Some input">
</mat-form-field>
In EntityValidateDirective, we need to get an instance of MatFormField it's apllied to.
I've tried a solution suggested here, i.e. simply inject MatFormField:
#Directive({
selector: "[appEntityValidate]"
})
export class EntityValidateDirective {
constructor(
private readonly matFormField: MatFormField
) {
const dfg = 0;
}
}
, but I'm getting an exception from Angular:
No provider for MatFormField!
Here is Stackblitz.
I suspect that this doesn't work because my directive is structural one rather than an attribute one.
So, how to access host component in a structural directive then?
UPDATE: In attempt to address concern raised by #yurzui in comments. Specifically, that MatFormField isn't really a host component in this case, since the above markup gets de-sugarized into something like this:
<ng-template appEntityValidate>
<mat-form-field>
<input matInput type="text" placeholder="Some input">
</mat-form-field>
</ng-template>
Thus, MatFormField becomes a child of the element which EntityValidateDirective is applied to.
To cover this, I've also tried the following:
#Directive({
selector: "[appEntityValidate]"
})
export class EntityValidateDirective implements AfterContentInit, AfterViewInit {
#ContentChild(MatFormField) content;
#ViewChild(MatFormField) view;
public ngAfterViewInit() {
const view = this.view;
}
public ngAfterContentInit() {
const content = this.content;
}
}
But both content and view are undefined in the corresponding lifecycle hook methods.

Binding a value from custom control to html control using angular 2 directives in typescript

I have created a custom control directive as "my-text-box", inside this directive I am using html control <input>, I need to pass values from my custom control to html input control. In angular1 simply I am using like <my-text-box id="1" name="Box 1"> using my custom attributes of scope variables in directive, i assigned the values to html control like <input type="text" id="{{id}}" name={{name}} > how can i use this scenario in angular 2.
Here is my sample code:
AppComponent.ts
import {Component} from '#angular/core';
import {TextBoxComponent} from './TextBoxComponentDirective';
#Component({
selector: 'my-app',
template: '<h1>{{title}}</h1> <my-text-box id="66" name="MyText1"></my-text-box>',
directives:[TextBoxComponent]
})
export class AppComponent {
title="Sample TextBox";
}
TextBoxComponentDirective.ts
import {Component,Input,Directive} from '#angular/core';
#Component({
selector:'my-text-box',
templateUrl: './TextBoxTemplate.html',
})
export class TextBoxComponent {
#Input() id;
}
TextBoxTemplate.html
<input type="text" [name]="name" [id]="id"/>
You can use #Input() for this:
#Component({
selector: 'my-text-box',
template: `
<input type="text" [id]="id" [name]="name" >
`
})
export class MyTextBoxComponent {
#Input() id;
#Input() name;
}
In your parent component you now can use it like so:
<my-text-box id="1" name="Box 1">
Or if you need to bind to variables:
<my-text-box [id]="someId" [name]="theBoxName">
Working Plunker for example usage