In this article, I will create simple sample user management application.
Ignite UI for Angular
Ignite UI for Angular is released from Infragistics. It is free of charge if you use it for community effort.Ignite UI for Angular
Even though it works very well as mobile application, I will create desktop size application first.
The environment I use in this article
- Node.js v8.7.0 and npm v5.5.1
- Visual Studio Code
Create an Angular Project
Follow the steps below to create new Angular project.
1. Install Angular CLI via npm
npm install -g @angular/cli
2. Run the Angular CLI command to create a project. In this case, I specify --routing option to enable routing for the project.
ng new ignite-ui-app --routing
cd ignite-ui-app
3. Then install Ignite UI component and dependencies. hammer.js is library to support touch and gesture.
npm install igniteui-angular hammerjs @types/hammerjs
npm install
4. Launch the project via Visual Studio Code.
code .
5. Then include css for styles in .angular-cli.json.
6. Try to run it at this moment to make sure all works.
ng server --open
It opens a browser and you shall see the basic application like below.
Right start developing the application using Ignite UI!
Create an application shell
Navigation Bar
1. First of all, let's add navigation on top of the application. To use Ignite UI controls, you need to register appropriate modules in src/app/app.module.ts. I also added hammer.js at this point as dependency module.
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule } from 'igniteui-angular/main';
import "hammerjs";
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
IgxNavbarModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Add component to .html file and logics to .ts file. In this case, I use "menu" icon, but you can pick up whatever you want from https://material.io/icons/.
src/app/app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<igx-navbar [title]="title"
actionButtonIcon="menu"
(onAction)="onClickMenu()">
</igx-navbar>
<router-outlet></router-outlet>
src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styles: []
})
export class AppComponent {
title = 'Ignite Ui App';
onClickMenu(){
window.alert("menu clicked");
}
}
3. Once you saved, the browser automatically reload the contents.
4. Click the menu icon on upper left corner to make sure it shows an alert.
Icon
There are many ways to utilize icons. Let's add them inside the navigation bar.
1. Add a module first.
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule } from 'igniteui-angular/main';
import "hammerjs";
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
IgxNavbarModule,
IgxIconModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Then add "Add" icon to igx-icon element.
src/app/app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<igx-navbar [title]="title"
actionButtonIcon="menu"
(onAction)="onClickMenu()">
<igx-icon name="add" (click)="onClickAdd()"></igx-icon>
<igx-icon name="refresh" (click)="onClickRefresh()"></igx-icon>
</igx-navbar>
<router-outlet></router-outlet>
src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Ignite Ui App';
onClickMenu(){
window.alert("menu clicked");
}
onClickAdd(){
window.alert("add clicked");
}
onClickRefresh(){
window.alert("refresh clicked");
}
}
3. Once saved, try clicking each buttons.
List
Now the application has navigation bar on top, let's add list on the left pane.
Scroll List
Ignite UI provide "List" control and "Scroll" control. List provides more capabilities, and scroll gives use scrolling capability. I use scroll control in this application.
1. Add module first.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule } from 'igniteui-angular/main';
import "hammerjs";
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Then add Angular Component to the application. You can keep adding the features inside main module, but to simply it, you can create separate module for each feature.
ng generate component list
3. Modify app.component.html to load the added module and update app.component.css for styling. To bi-directional binding, you can combine [] and () like [(selectedUser)].
src/app/app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<igx-navbar [title]="title" actionButtonIcon="menu" (onAction)="onClickMenu()">
<igx-icon name="add" (click)="onClickAdd()"></igx-icon>
<igx-icon name="refresh" (click)="onClickRefresh()"></igx-icon>
</igx-navbar>
<app-list [(selectedUser)]="selectedUser"></app-list>
<router-outlet></router-outlet>
<div>{{selectedUser.name}}</div>
src/app/app.component.css
app-list, div {
float: left;
}
src/app/app.component.ts
import { Component } from '@angular/core';
import { User } from './models/user';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Ignite Ui App';
public selectedUser: User = new User("", "", 0);
onClickMenu() {
window.alert("menu clicked");
}
onClickAdd() {
window.alert("add clicked");
}
onClickRefresh() {
window.alert("refresh clicked");
}
}
3. Next, add a User model in Models folder. You can do so by running Angular CLI command.
ng generate class models/user
src/app/models/user.ts
export class User {
public image: string
public name: string
public id: number
constructor(image: string, name: string, id: number) {
this.image = image;
this.name = name;
this.id = id;
}
}
4. Now, modify the List component you just added. First of all, import modules, and update UI. The modules I use this time are IxgScroll and IgxScrollEvent, which support scrolling and related event. To bind selectedUser, you need to add @Input and @Output property, and return the value in OnItemSelect function. Once you get used to how Angular works with binding, it shouldn't be too difficult.
src/app/list/list.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { IgxScroll, IgxScrollEvent } from "igniteui-angular/main";
import { User } from '../models/user';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
@Input() selectedUser: User;
@Output() selectedUserChange: EventEmitter<User> = new EventEmitter();
constructor() {
for (let i = 1; i <= 22; i++) {
this.users.push(new User(
`http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
"User: " + i,
i
));
}
this.visibleUsers = this.users.slice(0, this.visibleUsersCount);
}
public users: User[] = new Array<User>();
public visibleUsers: User[];
public visibleUsersCount: number = 8;
ngOnInit() {
}
private onItemSelect(user: User): void {
this.selectedUserChange.emit(user);
}
private updateList($event: IgxScrollEvent): void {
this.visibleUsers = this.users.slice($event.currentTop, $event.currentTop + this.visibleUsersCount);
}
}
5. Modify UI components.
src/app/list/list.component.html
<igx-scroll #scroll (onScroll)="updateList($event)"
[visibleItemsCount]="visibleUsersCount"
[itemHeight]="70"
[totalItemsCount]="users.length">
<ul class="list">
<li class="list-item" *ngFor="let user of visibleUsers" (click)="onItemSelect(user)">
<h5 class="list-item-value">{{user.name}}</h5>
</li>
</ul>
</igx-scroll>
src/app/list/list.component.css
.list {
width: 250px;
}
6. Save and confirm the change. You can click any item in the List to see how it is passed to main.
Avatar
The image in the list doesn't look cool, as every profile image supposed to be rounded these days. Avatar control solves it.
1. Add module as usual.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule } from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
@NgModule({
declarations: [
AppComponent,
ListComponent
],
imports: [
BrowserModule,
AppRoutingModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule, ],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Import IgxAvatar to list.component.ts, too so that you can use it in related html.
src/app/list/list.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { IgxScroll, IgxScrollEvent, IgxAvatar } from "igniteui-angular/main";
import { User } from '../models/user';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
@Input() selectedUser: User;
@Output() selectedUserChange: EventEmitter<User> = new EventEmitter();
constructor() {
for (let i = 1; i <= 22; i++) {
this.users.push(new User(
`http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
"User: " + i,
i
));
}
this.visibleUsers = this.users.slice(0, this.visibleUsersCount);
}
public users: User[] = new Array<User>();
public visibleUsers: User[];
public visibleUsersCount: number = 8;
ngOnInit() {
}
private onItemSelect(user: User): void {
this.selectedUserChange.emit(user);
}
private updateList($event: IgxScrollEvent): void {
this.visibleUsers = this.users.slice($event.currentTop, $event.currentTop + this.visibleUsersCount);
}
}
3. Lastly, update html and css.
src/app/list/list.component.html
<igx-scroll #scroll (onScroll)="updateList($event)"
[visibleItemsCount]="visibleUsersCount"
[itemHeight]="70"
[totalItemsCount]="users.length">
<ul class="list">
<li class="list-item" *ngFor="let user of visibleUsers" (click)="onItemSelect(user)">
<igx-avatar class="list-item-image" roundShape="true" src="{{user.image}}"></igx-avatar>
<h5 class="list-item-value">{{user.name}}</h5>
</li>
</ul>
</igx-scroll>
src/app/list/list.component.css
.list {
width: 250px;
}
.list-item {
list-style: none;
height: 64px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.list-item-image, .list-item-value {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
}
Detail Screen
Now the application has navigation and list, let's add another component to display user detail. I also add service which feeds user information to the application as data source.
Angular 2 Routing and Detail Component.
Before implementing detail screen with Ignite UI, I restructure the application to utilize routing capability.
1. Run the command below to add detail component.
ng generate component detail
2. Add the component to app-routing.module.ts so that Angular recognize the module as part of routing. It expects "id" as parameter for detail component.
src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DetailComponent } from './detail/detail.component';
const routes: Routes = [
{ path: 'detail/:id', component: DetailComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
3. Update app component to delete test data as well as SelectedUser as we don't need it anymore with routing feature.
src/app/app.component.ts
import { Component } from '@angular/core';
import { User } from './models/user';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Ignite Ui App';
onClickMenu() {
window.alert("menu clicked");
}
onClickAdd() {
window.alert("add clicked");
}
onClickRefresh() {
window.alert("refresh clicked");
}
}
src/app/app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<igx-navbar [title]="title" actionButtonIcon="menu" (onAction)="onClickMenu()">
<igx-icon name="add" (click)="onClickAdd()"></igx-icon>
<igx-icon name="refresh" (click)="onClickRefresh()"></igx-icon>
</igx-navbar>
<app-list></app-list>
<router-outlet></router-outlet>
src/app/app.component.css
app-list, router-outlet {
float: left;
}
4. Update detail.component.ts and html to receive the user id parameter.
src/app/detail/detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.css']
})
export class DetailComponent implements OnInit {
public id: string;
constructor( private route: ActivatedRoute ) {
}
ngOnInit() {
// Monitoring the parameter change.
this.route.params.subscribe(params => {
this.id = params.id;
});
}
}
src/app/detail/detail.component.html
<p>
{{id}}
</p>
5. Next, modify list component to adopt the routing. Router is passed as constructor and you may wonder where it comes from. This is an injection feature of Angular that you can easily inject any service which registered to the application. Also delete Input/Output for SelectedUser.
src/app/list/list.component.ts
import { Component, OnInit, EventEmitter } from '@angular/core';
import { IgxScroll, IgxScrollEvent, IgxAvatar } from "igniteui-angular/main";
import { User } from '../models/user';
import { Router } from '@angular/router';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
constructor(private router: Router) {
for (let i = 1; i <= 22; i++) {
this.users.push(new User(
`http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
"User: " + i,
i
));
}
this.visibleUsers = this.users.slice(0, this.visibleUsersCount);
}
public users: User[] = new Array<User>();
public visibleUsers: User[];
public visibleUsersCount: number = 8;
ngOnInit() {
}
private onItemSelect(user: User): void {
this.router.navigate([`/detail/${user.id}`]);
}
private updateList($event: IgxScrollEvent): void {
this.visibleUsers = this.users.slice($event.currentTop, $event.currentTop + this.visibleUsersCount);
}
}
6. Save and confirm if the application works as expected.
Serivce in Angular
Okay!, the routing part is completed. Let's move on to service.
1. Run the command below to create service. Unlike component, service files will be created under default app module. I specify --flat false so that it lives under its own folder.
ng generate service user --module=app --flat false
2. Specify the service as provider in app.module.ts.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule } from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
@NgModule({
declarations: [
AppComponent,
ListComponent,
DetailComponent
],
imports: [
BrowserModule,
AppRoutingModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule,
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
3. Implement the service to return users. Even though this service returns user information immediately, in real world, it should be asynchronous call. Thus I use Observe and of to make it asynchronous call.
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { User } from '../models/user'
@Injectable()
export class UserService {
private users: Array<User>;
constructor() {
this.users = new Array<User>();
for (let i = 1; i <= 22; i++) {
this.users.push(new User(
`http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
"User: " + i,
i
));
}
}
getUsers(): Observable<User[]>{
return of(this.users)
}
getUser(id: number): Observable<User>{
return of(this.users.find(x=>x.id === +id));
}
}
4. Replace the hardcoded user generation part in list.component.ts to user the service.
src/app/list/list.component.ts
import { Component, OnInit, EventEmitter } from '@angular/core';
import { IgxScroll, IgxScrollEvent, IgxAvatar } from "igniteui-angular/main";
import { User } from '../models/user';
import { UserService } from '../user/user.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
constructor(private userService: UserService, private router: Router) {
// Fetch user information as async service.
this.userService.getUsers().subscribe(
(users) => {
this.users = users;
this.visibleUsers = this.users.slice(0, this.visibleUsersCount);
}
);
}
public users: User[] = new Array<User>();
public visibleUsers: User[];
public visibleUsersCount: number = 8;
ngOnInit() {
}
private onItemSelect(user: User): void {
this.router.navigate([`/detail/${user.id}`]);
}
private updateList($event: IgxScrollEvent): void {
this.visibleUsers = this.users.slice($event.currentTop, $event.currentTop + this.visibleUsersCount);
}
}
5. Update detail.component.ts and html, too
src/app/detail/detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UserService } from '../user/user.service';
import { User } from '../models/user';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.css']
})
export class DetailComponent implements OnInit {
public user: User;
constructor( private route: ActivatedRoute, private userService: UserService ) {
}
ngOnInit() {
this.route.params.subscribe(params => {
this.userService.getUser(params.id).subscribe(
(user) => {this.user = user;}
);
});
}
}
src/app/detail/detail.component.html
<p>
{{user.name}}
</p>
6. Save and check if it works as expected.
Display Detail
Great work, the re-structure of the application has been done! Now let's implement the user detail component.
Input and Label
Ignite UI provides Input and Label controls which are useful for display and modify the record.
1. Add modules first. By some reason, the Input module doesn't have "module" as part of it's name. So be careful not to forget to add it. I also added FormsModule of Angular to support form.
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule,
IgxLabelModule,
IgxLabel, IgxInput} from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
@NgModule({
declarations: [
AppComponent,
ListComponent,
DetailComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule,
IgxLabelModule,
IgxInput,
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Update detail.component next.
src/app/detail/detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { IgxLabel, IgxInput, IgxAvatar } from 'igniteui-angular/main';
import { UserService } from '../user/user.service';
import { User } from '../models/user';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.css']
})
export class DetailComponent implements OnInit {
public user: User;
constructor( private route: ActivatedRoute, private userService: UserService ) {
}
ngOnInit() {
this.route.params.subscribe(params => {
this.userService.getUser(params.id).subscribe(
(user) => {this.user = user;}
);
});
}
}
src/app/detail/detail.component.html
<div class="detail">
<div>
<h2 igxLabel>User ID: {{user.id}}</h2>
<igx-avatar size="large" roundShape="true" src="{{user.image}}"></igx-avatar>
</div>
<div class="igx-form-group">
<input [(ngModel)]="user.name" igxInput type="text" />
<label igxLabel>Name</label>
</div>
</div>
src/app/detail/detail.component.css
.detail {
float: left;
margin: 30px;
}
3. Save them all to confirm the behavior.
DateTime picker
As you may know, datetime format is a tedious yet troublesome from developer point of view, as we have no idea which format each user uses. DateTime picker is handy solution and Ignite UI provides the control.
1. Add birthday for the user model.
src/app/models/user.ts
export class User {
public image: string
public name: string
public id: number
public birthdate: Date
constructor(image: string, name: string, id: number, birthdate: Date) {
this.image = image;
this.name = name;
this.id = id;
this.birthdate = birthdate;
}
}
2. Update the user service. I also added a function to save the user.
src/app/user/user.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { User } from '../models/user'
@Injectable()
export class UserService {
private users: Array<User>;
constructor() {
this.users = new Array<User>();
for (let i = 1; i <= 22; i++) {
let birthdate = new Date(2018, 0, i);
this.users.push(new User(
`http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
"User: " + i,
i,
birthdate
));
}
}
getUsers(): Observable<User[]>{
return of(this.users)
}
getUser(id: number): Observable<User>{
return of(this.users.find(x=>x.id === +id));
}
save(user: User): Observable<boolean> {
let index = this.users.indexOf(user);
if (index !== -1) {
this.users[index] = user;
return of(true);
}
else {
return of(false);
}
}
}
3. Add module for DateTime picker. It depends on BrowserAnimation module, so I added it as well.
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule,
IgxLabelModule, IgxLabel, IgxInput, IgxDatePickerModule } from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
@NgModule({
declarations: [
AppComponent,
ListComponent,
DetailComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
FormsModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule,
IgxLabelModule,
IgxInput,
IgxDatePickerModule
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
4. Add DatePicker to detail.component.html. The control provides many features and I use "close", "select today", "data bind" and "select locale" features here.
src/app/detail/detail.component.html
<div class="detail">
<div>
<h2 igxLabel>User ID: {{user.id}}</h2>
<igx-avatar size="large" roundShape="true" src="{{user.image}}"></igx-avatar>
</div>
<div class="igx-form-group">
<input [(ngModel)]="user.name" igxInput type="text" />
<label igxLabel>Name</label>
</div>
<div class="igx-form-group">
<input style="display: none" [(ngModel)]="user.birthdate" igxInput type="text" />
<igx-datePicker [cancelButtonLabel]="'Close'" [todayButtonLabel]="'Today'" [locale]="'ja-JP'" [(ngModel)]="user.birthdate"></igx-datePicker>
<label igxLabel>Birthday</label>
</div>
</div>
5. Save them all and try the DatePicker. It works beautifully.
Save the record
So far, the application simply displays information. Now add the feature to save the modification.
Buttons
Of course buttons are essential to any application and Ignite UI provides buttons control.
1. Add module first. I added Ripple in addition to Button. Ripple module provides animation effect when you click the button and its nice to have.
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule,
IgxLabelModule, IgxLabel, IgxInput, IgxDatePickerModule, IgxButtonModule, IgxRippleModule } from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
@NgModule({
declarations: [
AppComponent,
ListComponent,
DetailComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
FormsModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule,
IgxLabelModule,
IgxInput,
IgxDatePickerModule,
IgxButtonModule,
IgxRippleModule,
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Update detail Component to add a button.
src/app/detail/detail.component.ts
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { IgxLabel, IgxInput, IgxAvatar } from 'igniteui-angular/main';
import { UserService } from '../user/user.service';
import { User } from '../models/user';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.css']
})
export class DetailComponent implements OnInit {
public user: User;
constructor( private route: ActivatedRoute, private userService: UserService ) {
}
ngOnInit() {
this.route.params.subscribe(params => {
this.userService.getUser(params.id).subscribe(
(user) => {this.user = user;}
);
});
}
public save(){
this.userService.save(this.user);
}
}
src/app/detail/detail.component.html
<div class="detail">
<div>
<h2 igxLabel>User ID: {{user.id}}</h2>
<igx-avatar size="large" roundShape="true" src="{{user.image}}"></igx-avatar>
</div>
<div class="igx-form-group">
<input [(ngModel)]="user.name" igxInput type="text" />
<label igxLabel>Name</label>
</div>
<div class="igx-form-group">
<input style="display: none" [(ngModel)]="user.birthdate" igxInput type="text" />
<igx-datePicker [cancelButtonLabel]="'Close'" [todayButtonLabel]="'Today'" [locale]="'ja-JP'" [(ngModel)]="user.birthdate"></igx-datePicker>
<label igxLabel>Birthday</label>
</div>
<div>
<span igxButton="raised" igxRipple (click)="save()">Save</span>
</div>
</div>
3. Save them all and check the behavior. Select a user and modify the birthday, then click Save. Then select another user and come back to the original user to see if the birthday remains changed.
Toast Notification
It works. Yes it does. But from user point of view, it is a bit difficult to see if it actually did save or not. So let's add toast notification control.
1. Add module first.
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule,
IgxLabelModule, IgxLabel, IgxInput, IgxDatePickerModule, IgxButtonModule, IgxRippleModule,
IgxToastModule } from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
@NgModule({
declarations: [
AppComponent,
ListComponent,
DetailComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
FormsModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule,
IgxLabelModule,
IgxInput,
IgxDatePickerModule,
IgxButtonModule,
IgxRippleModule,
IgxToastModule,
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Then update detail component. To reference the element inside HTML, you can use ViewChild feature. If you directly reference a component inside HTML, you can use (#) to specify the name.
src/app/detail/detail.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { IgxLabel, IgxInput, IgxAvatar, IgxToastModule, IgxToast } from 'igniteui-angular/main';
import { UserService } from '../user/user.service';
import { User } from '../models/user';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.css']
})
export class DetailComponent implements OnInit {
@ViewChild('toast') toast: IgxToast;
public user: User;
constructor( private route: ActivatedRoute, private userService: UserService ) {
}
ngOnInit() {
this.route.params.subscribe(params => {
this.userService.getUser(params.id).subscribe(
(user) => {this.user = user;}
);
});
}
public save(){
this.userService.save(this.user).subscribe(()=>{
this.toast.show();
});
}
}
src/app/detail/detail.component.html
<div class="detail">
<div>
<h2 igxLabel>User ID: {{user.id}}</h2>
<igx-avatar size="large" roundShape="true" src="{{user.image}}"></igx-avatar>
</div>
<div class="igx-form-group">
<input [(ngModel)]="user.name" igxInput type="text" />
<label igxLabel>Name</label>
</div>
<div class="igx-form-group">
<input style="display: none" [(ngModel)]="user.birthdate" igxInput type="text" />
<igx-datePicker [cancelButtonLabel]="'Close'" [todayButtonLabel]="'Today'" [locale]="'ja-JP'" [(ngModel)]="user.birthdate"></igx-datePicker>
<label igxLabel>Birthday</label>
</div>
<div>
<span igxButton="raised" igxRipple (click)="save()">Save</span>
</div>
<igx-toast #toast message="Updated!">
</igx-toast>
</div>
3. Save and confirm the behavior. You shall see the toast notification when you save the birthday.
Delete a record
Next, implement delete feature.
Dialog
Showing confirmation dialog is important whenever user does some dangerous operation. Ignite UI provides Dialog control to achieve it.
1. Add the module first
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule,
IgxLabelModule, IgxLabel, IgxInput, IgxDatePickerModule, IgxButtonModule, IgxRippleModule,
IgxToastModule, IgxDialogModule } from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
@NgModule({
declarations: [
AppComponent,
ListComponent,
DetailComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
FormsModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule,
IgxLabelModule,
IgxInput,
IgxDatePickerModule,
IgxButtonModule,
IgxRippleModule,
IgxToastModule,
IgxDialogModule
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Update detail component. When user delete a record, it shows a confirmation dialog. And if user actually deletes the record, it navigate back to home by using Router service.
src/app/detail/detail.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { IgxLabel, IgxInput, IgxAvatar, IgxToastModule, IgxToast, IgxDialogModule } from 'igniteui-angular/main';
import { UserService } from '../user/user.service';
import { User } from '../models/user';
import { Router } from '@angular/router';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.css']
})
export class DetailComponent implements OnInit {
@ViewChild('toast') toast: IgxToast;
public user: User;
constructor( private route: ActivatedRoute, private router: Router,private userService: UserService ) {
}
ngOnInit() {
this.route.params.subscribe(params => {
this.userService.getUser(params.id).subscribe(
(user) => {this.user = user;}
);
});
}
public save(){
this.userService.save(this.user).subscribe(()=>{
this.toast.show();
});
}
public delete(){
this.userService.delete(this.user).subscribe(()=>{
this.toast.message = "deleted";
this.toast.show();
this.router.navigate([`/`]);
})
}
}
src/app/detail/detail.component.html
<div class="detail">
<div>
<h2 igxLabel>User ID: {{user.id}}</h2>
<igx-avatar size="large" roundShape="true" src="{{user.image}}"></igx-avatar>
</div>
<div class="igx-form-group">
<input [(ngModel)]="user.name" igxInput type="text" />
<label igxLabel>Name</label>
</div>
<div class="igx-form-group">
<input style="display: none" [(ngModel)]="user.birthdate" igxInput type="text" />
<igx-datePicker [cancelButtonLabel]="'Close'" [todayButtonLabel]="'Today'" [locale]="'ja-JP'" [(ngModel)]="user.birthdate"></igx-datePicker>
<label igxLabel>Birthday</label>
</div>
<div>
<span igxButton="raised" igxRipple (click)="save()">Save</span>
<span igxButton="raised" igxRipple (click)="dialog.open()">Delete</span>
</div>
<igx-toast #toast message="Updated!">
</igx-toast>
<igx-dialog #dialog
title="Confirmation"
message="Are you sure you want to delete the user?"
leftButtonLabel="Cancel"
(onLeftButtonSelect)="dialog.close()"
rightButtonLabel="OK"
(onRightButtonSelect)="delete()">
</igx-dialog>
</div>
3. UI is done, but user service needs to support delete features. The list also needs to know when user delete a record so that it can refresh the list. To achieve it, I user Subject of rxjs.
src/app/user/user.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { User } from '../models/user'
import { Subject } from 'rxjs/Subject';
@Injectable()
export class UserService {
private users: Array<User>;
// Provision a Subject
private userUpdate = new Subject<string>()
public userUpdateSource$ = this.userUpdate.asObservable();
constructor() {
this.users = new Array<User>();
for (let i = 1; i <= 22; i++) {
let birthdate = new Date(2018, 0, i);
this.users.push(new User(
`http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
"User: " + i,
i,
birthdate
));
}
}
getUsers(): Observable<User[]> {
return of(this.users)
}
getUser(id: number): Observable<User> {
return of(this.users.find(x => x.id === +id));
}
save(user: User): Observable<boolean> {
let index = this.users.indexOf(user);
if (index !== -1) {
this.users[index] = user;
return of(true);
}
else {
return of(false);
}
}
delete(user: User): Observable<boolean> {
let index = this.users.indexOf(user);
if (index !== -1) {
this.users.splice(index, 1);
// Notify that user deleted a record to subject.
this.userUpdate.next("updated");
return of(true);
}
else {
return of(false);
}
}
}
4. Next, update the list component to receive the notification when user deletes a record.
src/app/list/list.component.ts
import { Component, OnInit, EventEmitter } from '@angular/core';
import { IgxScroll, IgxScrollEvent, IgxAvatar } from "igniteui-angular/main";
import { User } from '../models/user';
import { UserService } from '../user/user.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
constructor(private userService: UserService, private router: Router) {
this.load();
}
public users: User[] = new Array<User>();
public visibleUsers: User[];
public visibleUsersCount: number = 8;
ngOnInit() {
// Reload the list when user delete a record.
this.userService.userUpdateSource$.subscribe(
(user)=>{this.load();}
)
}
public load():void{
this.userService.getUsers().subscribe(
(users) => {
this.users = users;
this.visibleUsers = this.users.slice(0, this.visibleUsersCount);
}
);
}
private onItemSelect(user: User): void {
this.router.navigate([`/detail/${user.id}`]);
}
private updateList($event: IgxScrollEvent): void {
this.visibleUsers = this.users.slice($event.currentTop, $event.currentTop + this.visibleUsersCount);
}
}
5. Save them all and confirm if it works as expected. In the screenshot below, I deleted User:5.
Add Record
When you can delete a user, you should be able to add new one! Let's implement add feature, shall we?
Checkbox, Switch, Slider and Radio Box
Ignite UI provides various controls to support user input.
1. Add more fields to User first.
export class User {
public image: string
public name: string
public id: number
public birthdate: Date
public gender: Gender
public userRank: number
public isAdmin: boolean
constructor(image: string, name: string, id: number, birthdate: Date,
gender: Gender, userRank: number, isAdmin: boolean ) {
this.image = image;
this.name = name;
this.id = id;
this.birthdate = birthdate;
this.gender = gender;
this.userRank = userRank;
this.isAdmin = isAdmin;
}
}
export enum Gender {
Male = 1,
Female,
Other,
}
2. Update the user service to handle new fields. Also add a function to create new user.
src/app/user/user.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { User, Gender } from '../models/user'
import { Subject } from 'rxjs/Subject';
@Injectable()
export class UserService {
private users: Array<User>;
private userUpdate = new Subject<string>()
public userUpdateSource$ = this.userUpdate.asObservable();
constructor() {
this.users = new Array<User>();
for (let i = 1; i <= 22; i++) {
let birthdate = new Date(2018, 0, i);
this.users.push(new User(
`http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
"User: " + i,
i,
birthdate,
Gender.Other,
i,
false
));
}
}
getUsers(): Observable<User[]> {
return of(this.users)
}
getUser(id: number): Observable<User> {
return of(this.users.find(x => x.id === +id));
}
add(user: User): Observable<boolean> {
this.users.push(user);
this.userUpdate.next("updated");
return of(true);
}
save(user: User): Observable<boolean> {
let index = this.users.indexOf(user);
if (index !== -1) {
this.users[index] = user;
return of(true);
}
else {
return of(false);
}
}
delete(user: User): Observable<boolean> {
let index = this.users.indexOf(user);
if (index !== -1) {
this.users.splice(index, 1);
this.userUpdate.next("updated");
return of(true);
}
else {
return of(false);
}
}
}
3. Run the command to add "new" component.
ng generate component new
4. Add the module to routing.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DetailComponent } from './detail/detail.component';
import { NewComponent } from './new/new.component';
const routes: Routes = [
{ path: 'detail/:id', component: DetailComponent },
{ path: 'new', component: NewComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
5. Add Ignite UI modules.
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {
IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule,
IgxLabelModule, IgxLabel, IgxInput, IgxDatePickerModule, IgxButtonModule, IgxRippleModule,
IgxToastModule, IgxDialogModule, IgxCheckboxModule, IgxSwitchModule, IgxSliderModule, IgxRadioModule
} from 'igniteui-angular/main';
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
import { NewComponent } from './new/new.component';
@NgModule({
declarations: [
AppComponent,
ListComponent,
DetailComponent,
NewComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
FormsModule,
IgxNavbarModule,
IgxIconModule,
IgxScrollModule,
IgxAvatarModule,
IgxLabelModule,
IgxInput,
IgxDatePickerModule,
IgxButtonModule,
IgxRippleModule,
IgxToastModule,
IgxDialogModule,
IgxCheckboxModule,
IgxSwitchModule,
IgxSliderModule,
IgxRadioModule,
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
6. Implement Logics and UI for new component. I also use CSS to modify the default color of Ignite UI controls.
src/app/new/new.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { User, Gender } from '../models/user';
import { UserService } from '../user/user.service';
import { IgxLabel, IgxInput, IgxAvatar, IgxToast, IgxDialog, IgxCheckbox, IgxSwitch, IgxSlider, IgxRadio } from 'igniteui-angular/main';
import { Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-new',
templateUrl: './new.component.html',
styleUrls: ['./new.component.css']
})
export class NewComponent implements OnInit {
constructor(private userService: UserService, private router: Router) {
}
@ViewChild('toast') toast: IgxToast;
public user: User;
public gender: string[];
ngOnInit() {
this.user = new User("", "", 0, null, Gender.Other, 0, true);
let genderValues = Object.keys(Gender);
this.gender = genderValues.slice(genderValues.length / 2);
}
public loadImage(input: HTMLInputElement): void {
if (!input.value) {
return;
}
let reader = new FileReader();
// Callback when file read.
reader.onload = () => {
input.value = "";
this.user.image = reader.result;
}
reader.readAsDataURL(input.files[0]);
}
public create() {
this.userService.add(this.user).subscribe(() => {
this.toast.show();
this.router.navigate([`/`]);
});
}
}
src/app/new/new.component.html
<div class="new">
<div>
<input #imageInput [(ngModel)]="user.image" igxInput type="file" (change)="loadImage(imageInput)" />
<igx-avatar size="large" roundShape="true" src="{{user.image}}"></igx-avatar>
</div>
<div class="igx-form-group">
<input [(ngModel)]="user.id" igxInput type="number" />
<label igxLabel>User ID</label>
</div>
<div class="igx-form-group">
<input [(ngModel)]="user.name" igxInput type="text" />
<label igxLabel>Name</label>
</div>
<div class="igx-form-group">
<input style="display: none" [(ngModel)]="user.birthdate" igxInput type="text" />
<igx-datePicker [cancelButtonLabel]="'Close'" [todayButtonLabel]="'Today'" [locale]="'ja-JP'" [(ngModel)]="user.birthdate"></igx-datePicker>
<label igxLabel>Birthday</label>
</div>
<igx-radio *ngFor="let item of gender" value="{{item}}" name="group" [(ngModel)]="user.gender">{{item}}</igx-radio>
<div class="igx-form-group slider">
<igx-slider [minValue]="0" [maxValue]="50" [lowerBound]="0" [value]="0" [(ngModel)]="user.userRank"></igx-slider>
<label igxLabel>User Rank</label>
</div>
<igx-switch [checked]="user.isAdmin" [(ngModel)]="user.isAdmin" >
Is Admin
</igx-switch>
<div>
<span igxButton="raised" igxRipple (click)="create()">Create</span>
</div>
<igx-toast #toast message="Created!">
</igx-toast>
</div>
src/app/new/new.component.css
.new {
float: left;
margin: 30px;
}
.slider {
padding-top: 10px;
margin-bottom: -10px
}
igx-slider >>> .igx-slider__track-fill {
background: #e41c77;
}
7. Update navigation bar to support new button, as well as refresh button.
src/app/app.component.ts
import { Component, ViewChild, ElementRef } from '@angular/core';
import { User } from './models/user';
import { ListComponent } from './list/list.component';
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild('applist') listComponent: ListComponent;
selectedUser: User;
title = 'Ignite Ui App';
constructor(private router: Router) {
}
onClickMenu(){
window.alert("menu clicked");
}
onClickAdd(){
this.router.navigate(['/new']);
}
onClickRefresh(){
this.listComponent.load();
}
}
src/app/app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<igx-navbar [title]="title" actionButtonIcon="menu" (onAction)="onClickMenu()">
<igx-icon name="add" (click)="onClickAdd()"></igx-icon>
<igx-icon name="refresh" (click)="onClickRefresh()"></igx-icon>
</igx-navbar>
<app-list #applist></app-list>
<router-outlet></router-outlet>
8. Save them all and try the application.
Summary
In this article, I use Angular and Ignite UI to quickly create simple application. Ignite UI has way more capability than I introduce here. So, in the next article, I will enhance the application by using other control. Stay tuned!
Top comments (2)
I can't find IgxScrollModule in the igniteui-angular version 8.2.2. Did it remove?
I don't know but it keeps changing..