use #Attribute in custom attribute directive and data -binding attribute directive - javascript

I'm new to Angular, just a question on use #Attribute in attribute directives, below is some code from a book:
#Directive({
selector: "[pa-attr]",
})
export class PaAttrDirective {
constructor(element: ElementRef, #Attribute("pa-attr") bgClass: string) {
element.nativeElement.classList.add(bgClass || "bg-success", "text-white");
}
}
and the template.html:
...
<td pa-attr="bg-warning">{{item.category}}</td>
...
so we can see that use #Attribute we can get the value of the attribute, but if we use data-binding attribute directive as:
<td [pa-attr]="item.category == 'Soccer' ? 'bg-info' : null">...
then the book modify the code as:
export class PaAttrDirective {
constructor(private element: ElementRef) {}
#Input("pa-attr")
bgClass: string;
ngOnInit() {
this.element.nativeElement.classList.add(this.bgClass || "bg-success", "text-white");
}
}
I'm a little bit confused here, can't we use #Attribute to get the value again as:
export class PaAttrDirective {
constructor(element: ElementRef, #Attribute("pa-attr") bgClass: string) {
element.nativeElement.classList.add(bgClass || "bg-success", "text-white");
}
}
why when use the attribute directive with data-binding then we have to create input property in the code and not able to use #Attribute?

They use #Input instead of #Attribute because:
Attributes initialize DOM properties and then they are done. Property
values can change; attribute values can't.
item.category == 'Soccer' ? 'bg-info' : null expression changes attribute value, so your Directive wouldn't get the updated value after it was changed.
I suggest to read about Angular template syntax here.

#Attribute: accepts plain primitive types, eg strings and numbers
#Input: accepts anything/an object, eg your own class object
You pass the string abc to the attribute like this:
<td pa-attr="abc"></td>
You pass the same thing to the input like this:
<td [pa-attr]="'abc'"></td> <!-- note the single quotes -->
Or
in ts
x = 'abc';
in html
<td [pa-attr]="x"></td>
I am not sure if you can have a dash in the input property name.

Related

Conditionally set v-model in Vue

I have a series of inputs that could be either checkboxes or radio buttons, depending on a value in the data of my vue component.
In particular, I have a Question component, and questions may accept only one answer or multiple answers. I have a selected_answers array in my data, and I was thinking I could have the checkboxes target it as their v-model, while the radio buttons could target selected_answers[0]. This way, I don't have to copy-paste che input elements and just change their type and v-model.
So, my solution would look something like this:
<input
:type="question.accepts_multiple answers ? 'checkbox' : 'radio'"
:id="'ans-' + answer.id"
:value="answer.id"
v-model="question.accepts_multiple_answers ? selected_answers : selected_answers[0]"
/>
However, eslint complains about my code:
'v-model' directives require the attribute value which is valid as LHS
What's a way I can accomplish what I'm trying to do?
You cannot use any advanced code inside of v-model (just a basic string), you could export question.accepts_multiple_answers ? selected_answers : selected_answers[0] to a computed and plug the computed to the v-model.
If you need to have a setter, you will need to write a computed setter, this looks like this
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
Meanwhile, since v-model is just some sugar syntax, you could also replace it with usual :value + #input (depending of the type of the field). I do prefer to use those 2 than v-model nowadays, especially for the kind of limitations that you do have right now.
You can't really do it like that.
I recommend you to set up a variable when loading your component like that:
data()
{
return {
model_string: '',
}
}
Then you give your variable some values depending on your own conditions
created() {
if (your.first.condition) {
this.model_string = your.value;
} else if (your.other.condition) {
this.model_string = your.value;
}
}
After this, you can use it in your view as you wish
<input v-model="model_string" ..... // your attributes>
Ended up figuring it out by myself.
<input
:type="question.accepts_multiple_answers ? 'checkbox' : 'radio'"
:id="'ans-' + answer.id"
:value="question.accepts_multiple_answers ? answer.id : [answer.id]"
v-model="selected_answers"
/>

Angular 6 - Use component props as same component attribute value

I have a component that is passing props down to a child component. I would like to take the resulting value of [info] and use it as my condition for [value]
Basically, is the [info] prop equal to the String good-info ? If yes, set value to Happy otherwise set it to Sad
<some-component [info]="row.calculation.anotherCalculation.information"
[value]="info === 'good-info' ? 'Happy' : 'Sad' "
></some-component >
Of course I could use the same calculation for value that I'm using for info but that seems redundant. Also the calculation used in the info prop is much longer than the example one shown above.
You can refer to the child component with a template reference variable (e.g. #child), which will allow you to get the value of the info property. However, the code below causes an ExpressionChangedAfterItHasBeenCheckedError because one property depends on the other being set in the same detection cycle.
<some-component #child
[info]="row.calculation.anotherCalculation.information"
[value]="child.info === 'good-info' ? 'Happy' : 'Sad' ">
</some-component >
See this stackblitz for a demo.
The exception mentioned above can be avoided if you set value when a change event is triggered from the child component after info has been set:
export class SomeComponent {
private _info: string;
#Output() infoChanged = new EventEmitter<string>();
#Input() public get info(): string {
return this._info;
}
public set info(x: string) {
this._info = x;
this.infoChanged.emit(x);
}
#Input() public value: string;
}
<some-component #child
[info]="row.calculation.anotherCalculation.information"
(infoChanged)="child.value = $event === 'good-info' ? 'Happy' : 'Sad'" >
</some-component>
See this stackblitz for a demo.
I would suggest using a method doing your calculations (in this example only returning the nested attribute), something like
class SomeComponent {
calculate(row: any) {
return row.calculation.anotherCalculation.information;
}
}
Then, in your HTML you can do
<div *ngFor="let row of rows">
<some-component [info]="calculate(row)"
[value]="calculate(row) === 'good-info' ? 'Happy' : 'Sad' "
></some-component>
</div>

sign dynemic key.value in ngFor

I'm trying to assign a dynamic object value named destination inside ngFor like this:
<div *ngFor="let obj of object.destination"><p [innerHTML]="cont.disc"></p> </div>
This destination is supposed to change dynamically using property binding (it works)
In my component.ts there is a function which assigns the dynamic value I get to the destination value, and connect it to the object key:
public getDestination() {
this.destination = this.to;
return (this.destination = `${this.content}.${this.destination}`); }
But all I get is that {{ object.destination }} equal to
<p>[object Object].india</p>
How can I make the object value dynamic so it will change depending on the property binding?

formatting ngb-timepicker so it returns as string not an object

I am trying to get ngb-timepicker to return a string instead of an object. At the moment it returns the following:
{
"hour": 13,
"minute": 30
}
but I would like it to return like this:
"13:30"
This is my html:
<div class="calender"> <ngb-datepicker #dp
[(ngModel)]="model"></ngb-datepicker>
<ngb-timepicker [(ngModel)]="time"></ngb-timepicker>
With datepicker I could reformat it from an object to a string with this:
constructor(private dateFormatter: NgbDateParserFormatter) {
const string_date = this.dateFormatter.format(this.model);
}
Is there a version of NgbDateParserFormatter that works with time?
The [(ngModel)] syntax can only set a data-bound property. If you need to do something more or something different, you can write the expanded form.
The property binding [ngModel] takes care of updating the underlying input DOM element. The event binding (ngModelChange) notifies the outside world when there was a change in the DOM.
HTML
<ngb-timepicker [(ngModel)]="time" (ngModelChange)="onTimeChange($event)"></ngb-timepicker>
<hr>
<pre>{{newTime}}</pre>
Component
export class AppComponent {
name = 'Angular 6';
public newTime:string='13:30';
time = {hour: 13, minute: 30};
onTimeChange(value:{hour:string,minute:string})
{
console.log(value)
this.newTime=`${value.hour}:${value.minute}`;
}
}
Live Demo

How can I create a dynamically interpolated string in javascript?

I'm working on creating a reusable UI component and am trying to figure out how to allow the consumer of the component to provide their own template for a particular area of the component.
I'm using typescript and am trying to utilize string interpolation to accomplish this as it seemed the most appropriate course of action.
Here is what I have so far:
export class Pager {
pageNumber: number = 1;
getButtonHtml(buttonContentTemplate?: string, isDisabled?: boolean): string {
buttonContentTemlpate = buttonContentTemplate || '${this.pageNumber}';
isDisabled = isDisabled || false;
return `<button id="button-id" type="button" ${!isDisabled ? '' : disabledAttribute}>
${buttonContentTemplate}
</button>`;
}
}
I have some other methods that will update the page number based off user input/interaction, but I want it to work that when getButtonHtml gets called, the return value would be <button id="button-id" type="button">1</button>, but instead I'm getting <button id="button-id" type="button">${this.pageNumber}</button>.
Is there a way to get javascript to evaluate the string again, and interpolate the remaining place holders?
I've looked at the MDN article on this topic and think that the String.raw method might possibly be what I need to use, but I wasn't sure and no matter what I try, I haven't gotten it to work.
Any help would be greatly appreciated.
The problem is that Template literals are interpreted immediately.
What you want to do is lazy load the template. So it would be best to pass in a function that returns a string.
export class Pager {
pageNumber: number = 1;
getButtonHtml(template?: () => string, isDisabled=false): string {
template = template || function() { return this.pageNumber.toString() };
return `<button id="button-id" type="button" ${!isDisabled ? '' : disabledAttribute}>
${template()}
</button>`;
}
}
Additionally, you can take advantage of default parameters to avoid the || trick.

Categories

Resources