In Angular 1 we avoided calling functions from within template expressions in excess, e.g. ng-repeat="item in vm.getFilteredItems()" because property changes unrelated to the result of getFilteredItems would cause the function to recompute repeatedly and unnecessarily on digests, which commonly causes performance problems at scale. Instead we bound to objects, and compute based on events (e.g. ng-repeat="item in vm.filteredItems).
In Angular 2, the dirty checking process has been optimized but functions called in component templates will still be called when any properties at the component level change, regardless of whether the function is dependent on those properties. I expect this could lead to the same performance issues if used improperly.
Here is a simplified example of the differing approaches in Angular 2:
// function binding in template
#Component({
selector: 'func',
template: `
<input [(ngModel)]="searchTerm" placeholder="searchTerm" />
<div *ngFor="let name of filteredNames(searchTerm)">{{name}}</div>
`
})
export class FuncComponent {
#Input() names:string[];
filteredNames(searchTerm) {
if (!searchTerm) return this.names;
let filteredNames = [];
return this.names.filter((name) => {
return name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1;
});
}
}
-
// no function binding
#Component({
selector: 'nofunc',
template: `
<input [(ngModel)]="searchTerm" (ngModelChange)="search($event)" placeholder="searchTerm" />
<div *ngFor="let name of filteredNames">{{name}}</div>
`
})
export class NoFuncComponent implements OnInit {
#Input() names:string[];
searchTerm: string;
ngOnInit() {
this.search(this.searchTerm);
}
search() {
if (!this.searchTerm) {
this.filteredNames = this.names;
return;
}
this.filteredNames = this.names.filter((name) => {
return name.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1;
});
}
}
http://plnkr.co/edit/AAFknlJgso3D8F1w3QC1?p=preview
Is this still a concern in Angular 2? Which approach is preferred, and why? Thanks!
You could create a pipe. Pipes (if pure) are only called when depending values change.
#Pipe({
name: 'search',
// pure: 'false'
})
class SearchPipe {
transform(names, searchTerm) {
if (!this.searchTerm) {
this.filteredNames = names;
return;
}
return names.filter((name) => {
return name.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1;
});
}
}
#Component({
selector: 'func',
pipes: [SearchPipe],
template: `
<input [(ngModel)]="searchTerm" placeholder="searchTerm" />
<div *ngFor="let name of names | search:searchTerm">{{name}}</div>
`
})
export class FuncComponent {
#Input() names:string[];
}
If the pipe needs to recognize changes in names then pure needs to be disabled.
If names is replaced by a different instance instead of just members being added or removed, then pure works fine and the pipe is only executed when names or searchTerm changes.
Hint
In devMode (default) change detection runs twice and the pipe will be called twice as much as expected.
Related
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 have an Angular 2 app where I need to create child components dynamically.
Is it mandatory to call 'detectChanges()' and 'detach()' method on the component reference variable 'componentRef.changeDetectorRef' ?
I see things work properly even if I dont use them.
Are these methods are actually meant component injection performance improvement ?
#Component({
selector: 'container',
template: '<template #content></template>'
})
export class ContainerComponet implements AfterViewInit {
contentComponentRef:any;
#ViewChild('content', {read: ViewContainerRef}) contentHandle;
constructor(private componentResolver:ComponentResolver) {
super();
}
ngAfterViewInit() {
if (this.contentComponentRef)
this.contentComponentRef.destroy();
this.componentResolver.resolveComponent(ChildComponent)
.then((factory:ComponentFactory<any>) => {
let componentRef = this.contentHandle.createComponent(factory);
componentRef.instance['child_component_property'] = 'dummy value for child';
componentRef.changeDetectorRef.detectChanges();
componentRef.onDestroy(() => {
componentRef.changeDetectorRef.detach();
});
this.contentComponentRef = componentRef;
return componentRef;
});
}
}
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 have an Angular 2 component that displays a list of Items, and that registers to a service that publishes events. The problem is that even if I don't do anything when I receive an event, Angular updates the view (or at least do something when, I guess, it shouldn't).
Here is a plunker.
As you can see in the console, the "getTitle()" method of my items is called each time my service publishes a message.
Even if I don't register to my service and if my component doesn't implement the MyServiceListener interface, getTitle is called each time the service gets a message. If I don't give the service to my component in its constructor, everything is fine. So, something seems wrong with my dependency injection, but what?
Here is the relevant code of the plunker:
My service and its listeners interface:
export interface MyServiceListener {
onMessage(_message: any);
}
export class MyService {
private m_listener: MyServiceListener;
constructor() {
window.setInterval(() => {
if (this.m_listener !== undefined) {
this.m_listener.onMessage("Hi");
}
}, 500);
}
setListener(_listener: MyServiceListener) { this.m_listener = _listener; }
}
The Item class:
export class Item {
m_title: string;
constructor(_title: string) {
this.m_title = _title;
}
getTitle(): string { console.log("getTitle"); return this.m_title; }
}
My component:
#Component({
selector: 'my-app',
template : `
<div>
<ul>
<li *ng-for="#item of m_items">
{{item.getTitle()}}
</li>
</ul>
</div>
`
})
export class App implements TestBugAngularServiceListener {
private m_items: Array<Item> = new Array<Item>();
constructor(_communicationService: MyService) {
this.m_items.push(new Item("A"));
this.m_items.push(new Item("B"));
this.m_items.push(new Item("C"));
_communicationService.setListener(this);
}
onMessage(_message: any) {
}
}
bootstrap(App, [MyService]).catch(err => console.error(err));
Both articles : Change detection and Angular immutability explain a lot of thing about how Angular 2 detect changes of object, and how the tree of components in angular 2 is traversed to perform data binding...
In your sample, I think your component "my-app" can be considered to be "Immutable", so changing its "change detection strategy" to OnPush solve your problem.
You can write this :
#Component({
selector: 'my-app',
changeDetection: ChangeDetectionStrategy.OnPush,
template : `
<div>
<ul>
<li *ng-for="#item of m_items">
{{item.getTitle()}}
</li>
</ul>
</div>
`
})
And after adding the import to ChangeDetectionStrategy, databinding of "my-app" will not be computed after each browser event, but only when its input change, so never...