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>
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.
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:
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)
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');
}
}
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
}
}
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
}
}
Logging query in constructor (broken)
@Component({...})
export class MyComp {
@ViewChild(SomeDir) someDir: SomeDir;
constructor() {
console.log(this.someDir); // undefined
}
}
Luckily, Angular's new lifecycle hooks make it easy to puzzle out when you should check for each type of query.
@Component({...})
export class MyComp implements AfterViewInit {
@ViewChild(SomeDir) someDir: SomeDir;
ngAfterViewInit() {
console.log(this.someDir); // SomeDir {...}
}
}
Set a $scope.$watch and manually check for changes each digest cycle
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.
@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
});
}
}
// 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>
// incorrect
<div *ngFor="#item in items">
<p> {{ item }} </p>
</div>
// correct
<div *ngFor="let item of items">
<p> {{ item }} </p>
</div>
Using outdated syntax
// 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
<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>
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>
When we shorten the code to use asterisk syntax, we have to follow certain rules that the parser will understand:
// correct
<p *ngFor="let item of items; trackBy:myTrackBy; let i=index">
{{i}}: {{item}}
</p>
EXCEPTION: TypeError: allStyles.map is not a function
‘styles’ needs to be an array of strings, not a single string.
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.
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!)
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';
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.
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).