In this tutorial you will learn how to work with data and collections:
you'll display a list of elements on screen by using the *ngFor
directive, apply dynamic styles with ngClass
directives, create custom TypeScript types and remove elements from the list.
User
typengFor
: display a list of usersngClass
: dynamic CSS iconsIn this second part of the tutorial you will learn how to work with data and collections and you'll display a list of elements on screen by using the *ngFor
directive.
You will also apply dynamic styles with ngClass
directive, create custom TypeScript types and remove elements from the list.
An Angular directive is a sort of custom HTML attribute that handle some logic behind the scenes.
For example you can use a directive to hide or show an element:
<div *ngIf="condition">Some content</div>
Dinamically apply CSS classes
<div [ngClass]="{'customCSSclass': condition}">...
Clone a part of the template looping thorgh array
<div *ngFor="let item of items">{{item.prop}}</div>
You can also create your own custom directives.
For example, the following one may hide the HTML element if user is not logged
<button *ifLogged>Some content</button>
User
typeAngular in written in TypeScript and we want that our code is strongly typed too.
We have to display a list of "users" so, first, we have to create a custom type to represent the User
entity.
Create a new src/app/model
folder and add a file named user.ts
into it.
The complete path is src/app/model/user.ts
:
The following interface allow you to create a new custom type that represents the structure of the User
entity:
export interface User {
id: number;
label: string;
gender: string;
age: number;
}
Open app.component.ts
, create an array of users and display its content in the template.
Interpolation allow you to embed expressions into your HTML templates:
<div>{{1+1}}</div> <!-- output: 2 -->
<div>{{yourname}}</div> <!-- output: the value of yourname -->
<div>{{condition ? x : y }}</div> <!-- output: x or y in according with the condition -->
app.component.ts
import { Component } from '@angular/core';
import { User } from './model/user';
@Component({
selector: 'app-root',
template: `
<h4>Users</h4>
<pre>{{users | json}}</pre>
`,
styles: [``]
})
export class AppComponent {
users: User[] = [ // list of users
{ id: 1, label: 'Fabio', gender: 'M', age: 20 },
{ id: 2, label: 'Lorenzo', gender: 'M', age: 37 },
{ id: 3, label: 'Silvia', gender: 'F', age: 70 },
];
}
Angular pipes are sort of formatters. There are several built-in pipes in angular to format dates, numbers, currencies and many others. Furthermore you can also create your own (sync and async) custom pipes.
The json
pipe allow you to display (stringify
) the content of objects and arrays, since you won't be able to see their content by default.
You can also try to remove the json
pipe and see what's happen: <pre>{{users}}</pre>
.
I'm using inline templates, instead of external templates, just to avoid opening too many files during this workshop. You may also create a separated file for your HTML template and another one for your component's CSS but I usually prefer this way in real world projects too in order to decrease the amount of files.
Read more about this in the previous chapter
Open your browser on http://localhost:4200 and see the result:
You should just display the following output:
ngFor
: display a list of usersIn this recipe you display a list of users:
You can use the *ngFor
directive that simply clones the template in which it's applied for each item of an array.
So you can replace the previous HTML template with the following one:
app.component.ts
<div class="container">
<h1>Users</h1>
<ul class="list-group">
<li *ngFor="let u of users" class="list-group-item">
{{u.label}}
</li>
</ul>
</div>
You should see the following result:
Get more info about the ngFor
directive reading the official documentation
ngClass
: dynamic CSS iconsOur goal is now display a different font-awesome icon to represent the user's gender, close to its name.
The syntax to display a font-awesome icon is the following:
<i class="fa fa-[ICON]"> <!-- Any icon-->
<i class="fa fa-google"> <!-- Google icon -->
<i class="fa fa-mars"> <!-- Man Icon -->
<i class="fa fa-venus"> <!-- Woman Icon -->
<i class="fa fa-3x fa-mars"> <!-- Increase size -->
<i class="fa fa-3x fa-venus"> <!-- Increase size -->
So we should simply shown an icon close to the user's name:
fa fa-mars
for men, fa fa-venus
for women and so on...
We might create two HTML elements, for each icon:
app.component.ts
<li *ngFor="let u of users" class="list-group-item">
<i class="fa fa-3x fa-mars" *ngIf="u.gender === 'M'"></i>
<i class="fa fa-3x fa-venus" *ngIf="u.gender === 'F'"></i>
{{u.label}}
</li>
Anyway, you may have noticed that the classes fa fa-3x
are the same for each icon, while the last part changes.
So we can use this trick:
fa fa-3x
to each icon by using the HTML class
attribute;ngClass
directive to add a different class in according with the condition.app.component.ts
<div class="container">
<h1>Users</h1>
<ul class="list-group">
<li *ngFor="let u of users" class="list-group-item">
<i
class="fa fa-3x"
[ngClass]="{
'fa-mars': u.gender === 'M',
'fa-venus': u.gender === 'F'
}"
></i>
{{u.label}}
</li>
</ul>
</div>
Your application should now display the properly icon close to the user's name, as shown in the image above.
Sometimes the CSS frameworks you have included in your projects does not fully meet your needs and you have to create your own custom classes.
In this example we just want to set a different background color for women and men.
I'm sorry for the stereotypes. It's just an example
The main ways to achieve this goal are the following two:
GLOBAL CSS: add your custom classes into src/styles.css
, so your CSS will be globally available throughout your application. Anyway the risk of having conflicts with other classes is very high.
LOCAL CSS (encapsulated): add your classes directly into your components so they will only be available in the component that have defined them.
Whenever possible I would use the second strategy, since it is the least risky.
So add following CSS classes in the styles
property of app.component
:
app.component.ts
styles: [`
.male { background-color: #36caff; }
.female { background-color: pink; }
`]
You may also use an external CSS file by using the following syntax:
styleUrls: [`./app.component.css`]
And add the ngClass
directive to the HTML element that contain the *ngFor
:
app.component.ts
<!-- missing part -->
<li
*ngFor="let u of users" class="list-group-item"
[ngClass]="{
'male': u.gender === 'M',
'female': u.gender === 'F'
}"
>
<!-- missing part -->
So far, your app.component.ts
file should look like the following:
app.component.ts
import { Component } from '@angular/core';
import { User } from './model/user';
@Component({
selector: 'app-root',
template: `
<div class="container">
<h1>Users</h1>
<ul class="list-group">
<li
*ngFor="let u of users" class="list-group-item"
[ngClass]="{
'male': u.gender === 'M',
'female': u.gender === 'F'
}"
>
<i
class="fa fa-3x"
[ngClass]="{
'fa-mars': u.gender === 'M',
'fa-venus': u.gender === 'F'
}"
></i>
{{u.label}}
</li>
</ul>
</div>
`,
styles: [`
.male { background-color: #36caff; }
.female { background-color: pink; }
`]
})
export class AppComponent {
users: User[] = [
{ id: 1, label: 'Fabio', gender: 'M', age: 20 },
{ id: 2, label: 'Lorenzo', gender: 'M', age: 37 },
{ id: 3, label: 'Silvia', gender: 'F', age: 70 },
];
}
In this recipe you will add a "TRASH" icon to each list item in order to delete a specific user.
app.component.ts
in each list item (aligned to the right side)click
event listener to each icon and invoke the (new) deleteHandler
method, passing the user to remove as paremeter.app.component.ts
<!-- missing part -->
{{u.label}}
<i class="fa fa-trash fa-2x pull-right" (click)="deleteHandler(u)"></i>
<!-- missing part -->
You may also pass the id
only instead of the whole User
object
Add the new deleteHandler()
method to app.component
class.
This method simply use the filter
array method to create a new collection with all items except the element to remove.
app.component.ts
deleteHandler(userToRemove: User) {
this.users = this.users.filter(u => u.id !== userToRemove.id);
}
Why did I filter the users
array by creating a new reference?
I may simply use the following strategy to accomplish the same task:
const index = this.users.findIndex(u => u.id === userToRemove.id);
this.users.splice(index, 1); // mutate the array removing an element
I'm using what is called "Immutability".
In fact I don't mutate data (users
, in our example) but I have completely created a new array reference by using the filter
array method (that generates a new array reference)
But why?
Currently there are no differences between the two approaches but you may need to optimize performance in the future or use an Angular feature that requires this approach. So I think it is important to get an idea of its existence in order to be able to explore it independently.
app.component.ts
import { Component } from '@angular/core';
import { User } from './model/user';
@Component({
selector: 'app-root',
template: `
<div class="container">
<h1>Users</h1>
<ul class="list-group">
<li
*ngFor="let u of users" class="list-group-item"
[ngClass]="{
'male': u.gender === 'M',
'female': u.gender === 'F'
}"
>
<i
class="fa fa-3x"
[ngClass]="{
'fa-mars': u.gender === 'M',
'fa-venus': u.gender === 'F'
}"
></i>
{{u.label}}
<i class="fa fa-trash fa-2x pull-right" (click)="deleteHandler(u)"></i>
</li>
</ul>
</div>
`,
styles: [`
.male { background-color: #36caff; }
.female { background-color: pink; }
`]
})
export class AppComponent {
users: User[] = [ // list of users
{ id: 1, label: 'Fabio', gender: 'M', age: 20 },
{ id: 2, label: 'Lorenzo', gender: 'M', age: 37 },
{ id: 3, label: 'Silvia', gender: 'F', age: 70 },
];
deleteHandler(userToRemove: User) {
this.users = this.users.filter(u => u.id !== userToRemove.id);
}
}
You should now able to remove elements from the list.
Try it on http://localhost:4200
or use the interactive code playground below: