Angular 2
Mistakes and Errors
5 Rookie Mistakes
Mistake #1
Binding to the native "hidden" property
Binding to the native "hidden" property
In Angular 1, if you wanted to toggle the visibility of an element, you would likely use one of Angular's built-in directives, like ng-show or ng-hide:
<div ng-show="showGreeting">
Hello, there!
</div>
In Angular 2, template syntax makes it possible to bind to any native property of an element. This is incredibly powerful and opens up a number of possibilities. One such possibility is to bind to the native hidden property, which, similar to ng-show, sets display to "none".
<!-- Angular 2 [hidden] example (not recommended) -->
<div [hidden]="!showGreeting">
Hello, there!
</div>
Binding to the native "hidden" property
For this reason, it's often better to toggle an element's presence using *ngIf.
<!-- Angular 2 *ngIf example (recommended) -->
<div *ngIf="showGreeting">
Hello, there!
</div>
Unlike the hidden property, Angular's *ngIf directive is not subject to style specificity constraints. It's always safe to use regardless of your stylesheet.
However, it's worth noting that it's not functionally equivalent.
Rather than toggling the display property, it works by adding and removing template elements from the DOM.
Mistake #2
Calling DOM APIs directly
Calling DOM APIs directly
There are very few circumstances where manipulating the DOM directly is necessary.
Angular 2 provides a set of powerful, high-level APIs like queries that one can use instead.
Leveraging these APIs confers a few distinct advantages:
- It's possible to unit test your application without touching the DOM, which removes complexity from your testing and helps your tests run faster.
- It decouples your code from the browser, allowing you to run your application in any rendering context, such as web workers or outside of the browser completely (for example, on the server or in Electron).
Calling DOM APIs directly
Scenario 1
You need a reference to an element in your component's template
@Component({
selector: 'my-comp',
template: `
<input type="text" />
<div> Some other content </div>
`
})
export class MyComp {
constructor(el: ElementRef) {
el.nativeElement.querySelector('input').focus();
}
}
Working with the ElementRef directly (not recommended)
Calling DOM APIs directly
Solution: ViewChild + local template variable
@Component({
selector: 'my-comp',
template: `
<input #myInput type="text" />
<div> Some other content </div>
`
})
export class MyComp implements AfterViewInit {
@ViewChild('myInput') input: ElementRef;
constructor(private renderer: Renderer) {}
ngAfterViewInit() {
this.renderer.invokeElementMethod(
this.input.nativeElement,
'focus');
}
}
Calling DOM APIs directly
Scenario 2
You need a reference to an element a user projects into your component
// user code
<my-list>
<li *ngFor="let item of items" #list-item> {{item}} </li>
</my-list>
// component code
@Component({
selector: 'my-list',
template: `
<ul>
<ng-content></ng-content>
</ul>
`
})
export class MyList implements AfterContentInit {
@ContentChildren('list-item') items: QueryList<ElementRef>;
ngAfterContentInit() {
// do something with list items
}
}
Calling DOM APIs directly
Solution: ContentChildren + directive with li selector
// user code
<my-list>
<li *ngFor="let item of items"> {{item}} </li>
</my-list>
@Directive({ selector: 'li' })
export class ListItem {}
// component code
@Component({
selector: 'my-list'
})
export class MyList implements AfterContentInit {
@ContentChildren(ListItem) items: QueryList<ListItem>;
ngAfterContentInit() {
// do something with list items
}
}
Mistake #3
Checking for query results in the constructor
Checking for query results in the constructor
Logging query in constructor (broken)
@Component({...})
export class MyComp {
@ViewChild(SomeDir) someDir: SomeDir;
constructor() {
console.log(this.someDir); // undefined
}
}
Checking for query results in the constructor
Luckily, Angular's new lifecycle hooks make it easy to puzzle out when you should check for each type of query.
- If you're conducting a view query, the results will become available after the view is initialized. Use the handy ngAfterViewInit lifecycle hook.
- If you're conducting a content query, the results become available after the content is initialized. Use the ngAfterContentInit lifecycle hook.
Checking for query results in the constructor
@Component({...})
export class MyComp implements AfterViewInit {
@ViewChild(SomeDir) someDir: SomeDir;
ngAfterViewInit() {
console.log(this.someDir); // SomeDir {...}
}
}
Mistake #4
Using ngOnChanges to detect query list changes
Using ngOnChanges to detect query list changes
Angular 1
Set a $scope.$watch and manually check for changes each digest cycle
Angular 2
ngOnChanges hook greatly simplifies this process but the method executes only when the component's inputs change
It will not be called when items are added or removed from @ViewChildren or @ContentChildren query lists.
Using ngOnChanges to detect query list changes
@Component({ selector: 'my-list' })
export class MyList implements AfterContentInit {
@ContentChildren(ListItem) items: QueryList<ListItem>;
ngAfterContentInit() {
this.items.changes.subscribe(() => {
// will be called every time an item is added/removed
});
}
}
Mistake #5
Constructing ngFor incorrectly
Constructing ngFor incorrectly
// a:
<div *ngFor="#item in items">
<p> {{ item }} </p>
</div>
// b:
<template *ngFor let-item [ngForOf]="items">
<p> {{ item }} </p>
</template>
// c:
<div *ngFor="let item of items; trackBy=myTrackBy; let i=index">
<p>{{i}}: {{item}} </p>
</div>
Constructing ngFor incorrectly
// incorrect
<div *ngFor="#item in items">
<p> {{ item }} </p>
</div>
// correct
<div *ngFor="let item of items">
<p> {{ item }} </p>
</div>
Using outdated syntax
Constructing ngFor incorrectly
// incorrect
<template *ngFor let-item [ngForOf]="items">
<p> {{ item }} </p>
</template>
// correct
<div *ngFor="let item of items">
<p> {{ item }} </p>
</div>
Mixing sugared and de-sugared template syntax
Constructing ngFor incorrectly
<div *ngFor="let item of items">
is treated just like
<div template="ngFor let item of items">
When you use both, it's like writing:
<template template="ngFor" let-item [ngForOf]="items">
// correct
<template ngFor let-item [ngForOf]="items">
<p> {{ item }} </p>
</template>
// correct
<p *ngFor="let item of items">
{{ item }}
</p>
Constructing ngFor incorrectly
Using the wrong operator in * syntax
// incorrect
<div *ngFor="let item of items; trackBy=myTrackBy; let i=index">
<p>{{i}}: {{item}} </p>
</div>
// correct
<template
ngFor
let-item [ngForOf]="items"
[ngForTrackBy]="myTrackBy"
let-i="index">
<p> {{i}}: {{item}} </p>
</template>
Constructing ngFor incorrectly
When we shorten the code to use asterisk syntax, we have to follow certain rules that the parser will understand:
- All configuration must happen within the string value of the *ngFor attribute
- We set local variables using an = operator
- We set input properties using a : operator
- We strip the directive name prefix from input properties ("ngForOf" → "of")
- We separate statements with semicolons
// correct
<p *ngFor="let item of items; trackBy:myTrackBy; let i=index">
{{i}}: {{item}}
</p>
Errors
Errors, Errors, Everywhere…
General Advice
- If you get an error in the console, scroll to the top! The later errors are not very helpful. Look at the first one.
- Some of these can be really tricky to track down. As a general strategy, make small changes and test as you go. At least that way it’ll be easier to figure out which bit of code broke (like when the stack traces are not so helpful).
- Styles don’t seem to cascade to subcomponents by default… (this can be controlled by the encapsulation property of the @Component decorator. See the docs for more info.)
Errors, Errors, Everywhere…
EXCEPTION: TypeError: allStyles.map is not a function
‘styles’ needs to be an array of strings, not a single string.
Errors, Errors, Everywhere…
EXCEPTION: Unexpected directive value ‘undefined’ on the View of component ‘AppComponent’
I defined a component as class Something when it should’ve been export class Something – forgot to export it from the file it was defined in. The import didn’t fail, but the directives: [Something] did…
This is a problem with polyfilling ES Modules. Something outside of Angular, and in fact a well known issue with all ES module polyfills. Once we have a native implementation you will get an error, but in the meantime if you use the TypeScript compiler you’ll get a compile time error.
Errors, Errors, Everywhere…
EXCEPTION: Cannot resolve all parameters for UserList(?). Make sure they all have valid type or annotations.
I got this when I was trying to inject the Http service.
Put @Injectable() above the class (don’t forget the parens!)
Errors, Errors, Everywhere…
EXCEPTION: Error during instantiation of UsersCmp
TypeError: this.http.get(…).map is not a function
I thought TypeScript was supposed to save us from TypeErrors!
Removing .map fixed the error… but it seems like it should work. I don’t know too much about this new Http service so, maybe the request failed?
Nope: You have to explicitly include the map operator from RxJS. Awesome. Add this line to app.ts (or whatever your main file is):
import 'rxjs/add/operator/map';
Errors, Errors, Everywhere…
EXCEPTION: Cannot find a differ supporting object ‘[object Object]’ in [users in UsersCmp@2:14]
I’m pretty sure this means that a non-array was passed to ngFor. Make sure the thing you pass is an array.
Errors, Errors, Everywhere…
Compile error: “Type ‘Observable' is not assignable to type 'any[]'."
Got this when I was trying to return this.http.get() as any[] – it’s not an array, so that doesn’t work. Changing to any works better (or the actual type, for that matter).
Angular 2: Errors
By Pavel Nasovich
Angular 2: Errors
Here's a compilation of common beginner mistakes to avoid when you're writing your first application using Angular 2. Based on http://angularjs.blogspot.com.by/2016/04/5-rookie-mistakes-to-avoid-with-angular.html and https://daveceddia.com/angular-2-errors/
- 1,669