I've got a pretty simple directive, which should update the input value on blur.
The blur handler is getting called, but I can't manage to set the input value.
import {NgModel} from 'ng-metadata/common';
import {Directive, Inject, Self, HostListener} from 'ng-metadata/core';
#Directive({
selector: 'foo'
})
export class FooDirective {
constructor(
private #Self() ngModel: NgModel,
private #Inject('$element') $element: ng.IAugmentedJQuery
) {}
#HostListener('blur')
onBlur() {
this.ngModel.$viewValue = "FOO";
this.ngModel.$modelValue = "FOO";
this.ngModel.$setViewValue("FOO");
this.ngModel.$commitViewValue();
// I guess, I just need one of these, but none did work
}
}
HTML Code:
<input type="text" foo ng-model="$ctrl.abc"></input>
I can read the current value using console.log(this.ngModel.$modelValue), but I'm not able to set a new value.
What did i miss?
Got it!
It's as simple as:
this.ngModel.$setViewValue("FOO");
this.ngModel.$render();
Related
Why do I need to place $viewValue in a key-value pair in the example below (which uses Angular 1.6.2 and Typescript)?
export class PageHeaderComponent implements ng.IComponentOptions {
public template: string = `
// ... other markup omitted
<input type="text"
name="search"
class="form-control text-field"
placeholder="{{$ctrl.searchPlaceholder}}"
ng-model="$ctrl.search"
uib-typeahead="item for item in $ctrl.getItems({value: $viewValue})"
/>
...
public bindings: any = {
headerTitle: "<",
showBackButton: "<",
entityName: "<",
addAction: "&",
adding: "<",
search: "=",
searchPlaceholder: "<",
getItems: "&"
};
}
This PageHeader component is contained within a LandingPage component. getItems is a method on the LandingPage controller.
export class LandingPageComponent implements ng.IComponentOptions {
public template: string = `
<div class="landing-page">
<page-header
header-title="'Businesses'"
add-action="$ctrl.showAddBusiness()"
entity-name="'Business'"
search-placeholder="'Search for a business'"
search="$ctrl.search.name"
adding="$ctrl.adding",
get-items="$ctrl.getItems({value})"
></page-header>
// ... other markup omitted
`;
public controller: any = LandingPageComponentController;
}
export class LandingPageComponentController {
// ... Irrelevant details omitted
public getItems(value: {value}): Promise<string[]> {
return new Promise((resolve, reject) => {
resolve(["one", "two", "three", "four"]);
});
}
}
If I simply pass $viewValue by itself (and change the signature of getItems to getItems(value: string), I get this error:
TypeError: Cannot use 'in' operator to search for '$ctrl' in o.
Obviously, was able to fix the error, I just would like to understand why my fix worked.
This isn't to do with uib-typeahead, but with the use of the Output Event binding ('&') between LandingPageComponent and PageHeaderComponent.
You can actually simplify your code a little bit:
In LandingPageComponent template:
get-items="$ctrl.getItems(value)"
In LandingPageComponentController:
public getItems(value: string): Promise<string[]> {
return new Promise((resolve, reject) => {
resolve(["one", "two", "three", "four"]);
});
}
Why?
When passing an expression to a child component with the '&' binding, the expression doesnt get evaluated until the child component calls it.
Although the expression gets called by the child component, it gets evaluated on the parent scope.
This expression has no idea about whats going on in the child component - so it has no idea of the variables/values that are present.
How does AngularJS get around this? Think of it as AngularJS using the object you pass into the binding (when you call it) to create 'local variables' to evaluate the expression with.
So in PageHeaderComponent, when you go:
$ctrl.getItems({value: $viewValue})
Think of AngularJS doing this for LandingPageComponent:
let value = $viewValue;
$ctrl.getItems(value);
This isn't actually the case, but I think it should help you in understanding why we need to pass an object back (rather than the values directly)
This post does a really good job of describing why we need to deal with Output Events in this way:
http://www.codelord.net/2016/05/13/understanding-angulars-and-binding/
I would like to bind an input value to the model using getter and setters. In this way i can prevent and/or manipulate the input's value while writing inside it.
For example i want the prevent numbers inside an input box. So, if write 'abc' all is ok, then if I start writing a number nothing should happen (to the model and to the input's value). The issue is that with the following code i'm able to write anything inside the input box (but the model it's correct). This means that the input box value is not really representing my model.
NOTE: The reason beyond this questions is that I want to use my models to validate forms, preventing for example specific characters. I would like to not use reactive forms as i want to keep my validations inside my models not components. Also note that in a real scenario i would have a UserModel class with inside name and other fields with their validations.
#Component({
selector: 'my-app',
template: `
<div>
<h2><input type="text" [(ngModel)]="name"> {{name}}</h2>
</div>
`,
})
export class App {
_name:string = 'ss';
constructor() {
}
// In real scenario those 2 methods are in a separate class UserModel
get name() {
return this._name;
}
set name() {
if ((new RegExp(/^[a-zA-Z]*$/).test(val))) {
this._name = val;
}
}
}
If you manipulate the value in the setter, this can cause issues with change detection, so that ngModel doesn't pick up the changes and doesn't update the <input>
To work around you can use
export class App {
_name:string = 'ss';
constructor(private cdRef:ChangeDetectorRef) {}
get name() {
return this._name;
}
set name(value:String) {
this._name = value + 'x';
this.cdRef.detectChanges()
}
}
if you reset the value to the previous value, you might need to pass an artificial different value first, otherwise change detection won't detect a change and even detectChanges() won't update the input.
set name(value:String) {
var oldVal = this._name;
this._name = null;
this.cdRef.detectChanges()
this._name = oldVal;
this.cdRef.detectChanges()
}
Based on #Günter Zöchbauer answer i made a workaround. It's not definitive and could be more abstract, but for now it's ok.
export class App implements OnInit {
#Input() userModel: UserModel = null;
public _vm;
constructor(private _changeDetectionRef: ChangeDetectorRef) {
}
/**
* Initalize view model, it's important to keep names specular
*/
ngOnInit() {
this._vm = {
name: this.userModel.name,
surname: this.userModel.surname,
};
}
/**
* Helper for avoid detectchanges inside the modal, and reduce boilerplate. We could also ad an interface/type of the possibile field value, ie type fieldT= 'name' | 'surname';
* #param field
* #param val
*/
protected updateModel(field, val: string): void {
this._vm[field] = null;
this._changeDetectionRef.detectChanges();
this.userModel[field] = val;
this._vm[field] = this.userModel[field];
this._changeDetectionRef.detectChanges();
}
}
In userModel:
....
public get name(): string {
return this.name';
}
public set name(val: string) {
if ((new RegExp(/^[a-zA-Z]*$/).test(val))) {
this.name = val;
}
}
In template:
<input type="text" name="userName" [ngModel]="_vm.name" (ngModelChange)="updateModel('name', $event)">
You can use (ngModelChange) and [ngModel] to test the content of your model upon change.
As you can see in this Plunker the model wont change if it is not valid.
#Component({
selector: 'my-app',
template: `
<div>
<h2><input #input type="text" [ngModel]="name" (ngModelChange)='valid(input.value)'> {{name}}</h2>
</div>
`,
})
export class App {
name:string = 'ss';
constructor() {
}
valid(value){
if(value){ //<--- Your test here
this.name = value;
}
}
}
This might be answered, but damn if I can find it . I am creating a module, and its working, the issue is I want to assign a property to another property on my module.
so in angular 2 (with ng-module) I have created a simple panel
<simple-panel name="MyPanel"></simple-panel>
I have it working great, the issue is I want to assign a property to the name Property now and I have no idea what is the best way to do this.
so I would like to return {{MyPanel.thisProperty}} for use on the page where I am calling the tag.
here is a sample of what I am doing, stripped down for this question
here is my simple-panel.ts
import {Component,NgModule,ModuleWithProviders, Directive, Input} from '#angular/core';
/**
* Content of the edit panel.
*/
#Directive({
selector: 'simple-panel-edit-panel-content'
})
export class SimplePanelEditPanelContent {}
#Component({
selector: 'simple-panel',
templateUrl: 'simple-panel.html',
styleUrls: ['simple-panel.css'],
encapsulation: ViewEncapsulation.None
})
export class SimplePanel{
private _name: string;
private _announceedit: boolean = false;
private _buttonname: string = 'edit';
/** This sets the Name as a variable that can be used. */
#Input()
get name(): string { return this._name; }
set name(value) { this._name = value; }
/**Sets the Edit Announcement */
#Input()
get editannounce(): boolean { return this._announceedit; }
set editannounce(value: boolean) {
if (!value) {
this._announceedit = true;
this._buttonname = 'search';
}else{
this._announceedit = false;
this._buttonname = 'edit';
}
}
}
#NgModule({
exports: [SimplePanel,SimplePanelEditPanelContent],
declarations: [SimplePanel,SimplePanelEditPanelContent],
})
export class SimplePanelComponent {
static forRoot(): ModuleWithProviders {
return {
ngModule: SimplePanelComponent,
providers: []
};
}
}
here is the simple-panel.html
<md-card>
<md-card-title-group>
<button md-raised-button (click)="editannounce=editannounce;"><md-icon>{{ _buttonname }}</md-icon></button>
</md-card-title-group>
<md-card-content>
<ng-content select="simple-panel-edit-panel-content"></ng-content>
</md-card-content>
<md-card-actions>
<button md-raised-button (click)="editannounce = editannounce"><md-icon>save</md-icon> SAVE</button>
</md-card-actions>
</md-card>
when someone uses the module, a panel is created with a button
when someone clicks the button I can access the variable within the template above, but what I want to do is actually access a variable that is used on the page itself where they call the module to use. it would be nice to have it named MyPanel.announceedit or MyPanel.editable as an example, but the main thing is that a variable is created, and watched, when it changes it passes it back up to where the module is bieng used and allows user the ability to access it within the content area, so if they added an input and wanted to see if the button was clicked to set the readOnly attribute they could. Hopefully this makes more sense.
If you write it like
<simple-panel [name]="MyPanel"></simple-panel>
in the component that includes this html, you can access/set MyPanel with a simple this.MyPanel.
And in your SimplePanel component
#Input() name;
...
this.name = "something";
is again all you need to set and get that field.
I'm trying to do exactly what is described in this post, but in Angular2.
Basically use the javascript function .setSelectionRange(start, end); in an input after a user clicks on a trigger. I can't find any way to replicate this behaviour using Typescript.
Thanks in advance.
I can't find any way to replicate this behaviour using Typescript.
TypeScript is just JavaScript. I suspect you mean to say Angular2 (that post is Angular1).
Angular2
You need to get a hold of the dom element (which is what you seem to be struggling with). In your controller you need to inject ElementRef. E.g. #Inject(ElementRef) elementRef: ElementRef,
Once you have the element you can traverse it and do whatever dom access / manual manipulation you need to do.
More
Docs : https://angular.io/docs/js/latest/api/core/ElementRef-class.html
Example
Sample : https://stackoverflow.com/a/32709672/390330
import {Component, ElementRef} from 'angular2/core';
#Component({
selector:'display',
template:`
<input #myname (input) = "updateName(myname.value)"/>
<p> My name : {{myName}}</p>
`
})
class DisplayComponent implements OnInit {
constructor(public element: ElementRef) {
this.element.nativeElement // <- your direct element reference
}
ngOnInit() {
}
}
This example line of code shows the essence of selecting the text (with .name being an ElementRef reference):
this.name.nativeElement.setSelectionRange(0, 999);
Here are all the necessary pieces put together (as well as putting focus on the input) for a "name" field:
View:
<input name="name" formControlName="name" type="text" [(ngModel)]="data.name">
Component:
export class MyComponent {
#ViewChild('name') name: ElementRef; // * see security comment below for ElementRef
#Input() data: {name: 'Foo Baz'};
myForm: FormGroup;
constructor() {
this.myForm = new FormGroup({
name: new FormControl()
});
}
// call this to give the field focus and select its text
focusAndSelectNameFieldText(){
if (!this.name) return;
this.name.nativeElement.focus();
setTimeout(() => {
this.name.nativeElement.setSelectionRange(0, 999);
});
}
}
*Please be sure your use of ElementRef does not pose a security risk:
https://stackoverflow.com/a/44509202/442665
I'm writing a component that takes a String and converts it into a list of <span>s. Each <span> gets a String character and is assigned a random color.
The component invocation looks like this:
<span ng-repeat="char in ctrl.chars" style="color: {{ctrl.randomColor}};">
{{char}}
</span>
And the component definition looks like this:
import 'package:angular/angular.dart';
import 'dart:math';
#NgComponent(
selector: 'tokens',
templateUrl: './component.html',
cssUrl: './component.css',
publishAs: 'ctrl',
map: const {
'text' : '#text',
}
)
class TokensComponent {
String text;
List<String> get chars => text.split('');
String get randomColor {
var colors = ['red', 'green', 'yellow', 'blue'];
return colors[new Random().nextInt(colors.length)];
}
}
This works, but generates an error:
5 $digest() iterations reached. Aborting!
Watchers fired in the last 3 iterations:
...
It isn't clear to me just what is being watched here, beyond the getter I am defining.
If I leave the code involving the Random in the getter, but simply return a hard-coded String, the error message goes away.
Any idea what is wrong here?
Binding a getter method with random return value seems like calling for all kind of weird stuff to happen.
From what I see, your intention seems to be to show all characters of a string in a different, random color - but not changing colors (meaning that the characters shouldn't constantly change their color) - is that right?
Unfortunately, the <span> isn't just created with the current return value of randomColor and then forgotten about - binding a property has the advantage that changes to the property are reflected in the view. Of course, since the "property" is a getter and has a random return value, it constantly changes, resulting in constant updates to the view.
If this error wouldn't occur to prevent this endless loop, all characters probably would have the same (rapidly changing) color.
EDIT
This question has a good answer to the problem:
AngularDart custom filter call() method required to be idempotent?
And alternative approach:
ORIGINAL
I'm not sure what you try to achieve but maybe it helps anyway
Screenshot of the result:
library main;
import 'dart:math';
import 'package:angular/angular.dart';
import 'package:di/di.dart';
class MyAppModule extends Module {
MyAppModule() {
type(TokensComponent);
}
}
void main() {
ngBootstrap(module: new MyAppModule());
}
#NgComponent(
selector: 'tokens',
template: '''<span ng-repeat="item in ctrl.items" style="color: {{item.color}};">
{{item.char}}
</span>''',
//cssUrl: './component.css',
publishAs: 'ctrl',
map: const {
'text' : '#text',
}
)
class TokensComponent {
String text = 'abcdefg';
List<Item> items = new List<Item>();
TokensComponent() {
text.split('').forEach((e) => items.add(new Item(e, randomColor)));
}
var colors = \['red', 'green', 'yellow', 'blue'\];
String get randomColor {
return colors\[new Random().nextInt(colors.length)\];
}
}
class Item {
Item(this.char, this.color);
String char;
String color;
}