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;
}
Related
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();
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;
}
}
}
I have a ion-searchbar that looks like this
<ion-searchbar [(ngModel)]="searchQuery" (keyup.enter)="search();"></ion-searchbar>
and the searchQuery is defined in my typescript file like this
export class SearchPage {
searchQuery: string;
constructor(){}
search() {
this.service.search(this.searchQuery).subscribe(
data => this.searchResults = data,
error => {
//something
}, () => {
//something
}
);
}
}
The problem is that if I change the value too fast and I press Enter, the value of searchQuery is not updated. For example, if I search "test" and I wait two seconds it will work. If I then search "testing" and I type it fast and press Enter right away, the value will still be "test". Now, I know this sounds weird, but it is really happening!
Any ideas why the value is not changed as soon as I type something?
Thank you
In Html try this event change
<form [ngFormModel]="searchForm" (ngSubmit)="search()">
<ion-searchbar [(ngModel)]="searchQuery" (keyup)="search()"></ion-searchbar>
<button type="submit" block>Submit</button>
</form>
Even check here might this help
In ts trim the value
this.service.search(this.searchQuery.trim()).subscribe(.....)
This is how I did it, based on the documentation for ion-searchbar:
<ion-searchbar #searchBar [debounce]="50" (ionChange)="search(searchBar.value)">
</ion-searchbar>
and in the TS file:
search(value: string) {
// do something with value
}
Explanation:
It's pretty self-explanatory, but here it is. The #searchBar creates a 'hook' for the element (sort of a 'self', or 'this', but named). We then use the property value from ion-searchbar to pass it to our function. The last thing is modifying the [debounce] property to make it update faster (but it will trigger more times when people write fast - use with discretion).
Something you can do to improve the way the search bar is being handled is:
In your page.html:
<ion-searchbar primary hideCancelButton [(ngModel)] = "searchQuery" [ngFormControl]="searchQueryControl"></ion-searchbar>
And in your page.ts:
// Remember to import these things:
import {FORM_DIRECTIVES, Control} from 'angular2/common';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
constructor ( ... ) {
//...
this.searchQuery = '';
this.searchQueryControl = new Control();
//...
this.searchQueryControl
.valueChanges
.debounceTime(1000)
.distinctUntilChanged()
.subscribe(text => {
if(text !== '') {
this.searchQuery = text;
this.search();
}
});
//...
}
By doing that, we would only run the code when the input has changed (this.searchQueryControl.valueChanges), and when there hasn't been another change within 1 second (.debounceTime(1000)). The distinctUntilChanged() allow us to run the code if the value is different to the last time it ran. So if the user typed 'asd', hit the backspace key and then retyped the ending 'd' again, nothing would happen.
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 am using Angular 2 beta and Typescript ("gulp-typescript": "~2.12.1",).
I am writing a component for <div qz-badge num-stars="3" class="qz-badge"></div>
import {Component, Input, ElementRef} from 'angular2/core';
import {CORE_DIRECTIVES} from 'angular2/common';
#Component({
selector: '[qz-badge]',
directives: [CORE_DIRECTIVES],
template: `
<div class="stars">
</div>
`
})
export class Badge {
#Input('num-stars') numStars: number;
constructor() {
console.log(new Array(3));
}
ngOnInit() {
let num = this.numStars;
console.log(num);
console.log('--------');
this.stars = new Array(num);
console.log(this.stars);
console.log(this.stars.length);
num = 3;
console.log('--------');
this.stars = new Array(num);
console.log(this.stars);
console.log(this.stars.length);
}
}
It leads ..
3
--------
["3"]
1
--------
[]
3
In the specification,
Syntax
[element0, element1, ..., elementN]
new Array(element0, element1[, ...[, elementN]])
new Array(arrayLength)
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)
I think the length should be 3.
Is that a bug or is there some righteous reasons?
Actually it's not a bug, it's simply as it works. All #Input()'s are passed as strings. As Rob Wormald's states in this issue.
Types in TS are purely a compile-time construct
So you won't have a number at runtime, but a string, even if you specify number as your Input's type. So you can cast it using parseInt.
// this.numStars is a string, not a number
let num = parseInt(this.numStars);
And that'll make it work correctly.
Here's a plnkr with your case working.
Edit
I missed something in your code. You're passing the value with no binding (no brackets), so it's not getting evaluated. It's a raw string.
The above answer is still valid, but in your case is much easier by passing the value with binding
[num-stars]="3"
Here's another plnkr without the parseInt but using binding.