Angular UI Development with PrimeNG

4 (12 reviews total)
By Sudheer Jonna , Oleg Varaksin
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Getting Started with Angular and PrimeNG

About this book

PrimeNG is a leading UI component library for Angular applications with 80+ rich UI components. PrimeNG was a huge success in the Angular world and very quickly. It is a rapidly evolving library that is aligned with the last Angular release. In comparison with competitors, PrimeNG was created with enterprise applications in mind. This book provides a head-start to help readers develop real–world, single-page applications using the popular development stack.

This book consists of 10 chapters and starts with a short introduction to single-page applications. TypeScript and Angular fundamentals are important first steps for subsequent PrimeNG topics. Later we discuss how to set up and configure a PrimeNG application in different ways as a kick-start. Once the environment is ready then it is time to learn PrimeNG development, starting from theming concepts and responsive layouts. Readers will learn enhanced input, select, button components followed by the various panels, data iteration, overlays, messages and menu components. The validation of form elements will be covered too. An extra chapter demonstrates how to create map and chart components for real-world applications. Apart from built-in UI components and their features, the readers will learn how to customize components to meet their requirements.

Miscellaneous use cases are discussed in a separate chapter, including: file uploading, drag and drop, blocking page pieces during AJAX calls, CRUD sample implementations, and more. This chapter goes beyond common topics, implements a custom component, and discusses a popular state management with @ngrx/store. The final chapter describes unit and end-to-end testing. To make sure Angular and PrimeNG development are flawless, we explain full-fledged testing frameworks with systematic examples. Tips for speeding up unit testing and debugging Angular applications end this book.

The book is also focused on how to avoid some common pitfalls, and shows best practices with tips and tricks for efficient Angular and PrimeNG development. At the end of this book, the readers will know the ins and outs of how to use PrimeNG in Angular applications and will be ready to create real- world Angular applications using rich PrimeNG components.

Publication date:
July 2017
Publisher
Packt
Pages
384
ISBN
9781788299572

 

Chapter 1. Getting Started with Angular and PrimeNG

This book presupposes some basic knowledge of TypeScript and Angular 2. Anyway, we would like to give the readers an overview of the most important TypeScript and Angular key concepts used in this book. We will summarize TypeScript and Angular features and present them in understandable, simple, but deeply explained portions. At the time of writing the book, the current TypeScript and Angular Versions are 2.3.x and 4.2.x, respectively. Readers will also meet the PrimeNG UI library for the first time and gain experience with project setups in three various ways. At the end of this chapter, readers will be able to run the first Angular- and PrimeNG-based web application.

In this chapter, we will cover the following topics:

  • TypeScript fundamentals
  • Advanced types, decorators, and compiler options
  • Angular cheat sheet - overview of key concepts
  • Angular modularity and lifecycle hooks
  • Running PrimeNG with SystemJS
  • Setting up PrimeNG project with Webpack
  • Setting up PrimeNG project with Angular CLI
 

TypeScript fundamentals


Angular 2 and higher is built with features of ECMAScript 2015/2016 and TypeScript. The new ECMAScript standards target evergreen browsers and helps to write more powerful, clean, and concise code. You can also use these features in any other less modern browsers with Polyfills such as core-js (https://github.com/zloirock/core-js). But, why do we need to use TypeScript?

TypeScript (http://www.typescriptlang.org) is a typed language and a super set of JavaScript developed by Microsoft. One can say that TypeScript is an advanced JavaScript with optional static typing. TypeScript code is not processed by browsers, it has to be translated into JavaScript by means of a TypeScript compiler. This translation is called compilation or transpilation. The TypeScript compiler transpiles .ts files into .js files. The main advantages of TypeScript are as follows:

  • Types help you find and fix a lot of errors during development time. That means, you have less errors at runtime.
  • Many modern ECMAScript features are supported out of the box. More features are expected according to the roadmap (https://github.com/Microsoft/TypeScript/wiki/Roadmap).
  • Great tooling and IDE support with IntelliSense makes the coding a pleasure.
  • It is easier to maintain and refactor a TypeScript application than one written in untyped JavaScript.
  • Developers feel comfortable with TypeScript due to object-oriented programming patterns, such as interfaces, classes, enums, generics, and so on.
  • Last but not least, Angular 2+ and PrimeNG are written in TypeScript.

It is also important to keep the following points in mind:

  • The Typescript Language Specification says,

    every JavaScript program is also a TypeScript program

    . Hence, a migration from JavaScript to TypeScript code is easily done.
  • TypeScript compiler emits output even when any errors are reported. In the next section, Advanced types, decorators, and compiler options, we will see how we can forbid emitting JavaScript on errors.

What is the best way to learn the TypeScript language? There is an official handbook on the TypeScript's homepage, which is aligned with the last released version. Hands-on learning is possible with the TypeScript playground (http://www.typescriptlang.org/play), which compiles on-the-fly TypeScript code entered in a browser and shows it side by side with the generated JavaScript code:

Alternatively, you can install the TypeScript globally by typing the following command in the command line:

npm install -g typescript

Global installation means, the TypeScript compiler tsc can be reached and used in any of your projects. Installed Node.js and npm are presupposed. Node.js is the JavaScript runtime environment (https://nodejs.org). npm is the package manager. It is shipped with Node.js, but can be installed separately as well. After that, you can transpile one or more .ts files into .js files by typing the following command:

tsc some.ts another.ts

This will result in two files, some.js and another.js.

Basic types

TypeScript exposes the basic types, as well as a couple of extra types. Let's explore the type system with these examples.

  • Boolean: The type is a primitive JavaScript boolean:
let success: boolean = true;
  • Number: The type is a primitive JavaScript number:
let count: number = 20;
  • String: The type is a primitive JavaScript string:
let message: string = "Hello world";
  • Array: The type is an array of value. There are two equivalent notations:
let ages: number[] = [31, 20, 65];
let ages: Array<number> = [31, 20, 65];
  • Tuple: The type represents a heterogeneous array of values. Tuple enables storing multiple fields of different types:
let x: [string, number];
x = ["age", 40];    // ok
x = [40, "age"] ;   // error
  • Any: The type is anything. It is useful when you need to describe the type of variables that you do not know at the time of writing your application. You can assign a value of arbitrary type to a variable of type any. A value of type any in turn can be assigned to a variable of arbitrary type:
let some: any = "some";
some = 10000;
some = false;

let success: boolean = some;
let count: number = some;
let message: string = some;
  • Void: The type represents the absence of having an any type. This type is normally used as the return type of functions:
function doSomething(): void {
  // do something
}
  • Nullable: These types denote two specific types, null and undefined that are valid values of every type. That means, they can be assigned to any other type. It is not always desired. TypeScript offers a possibility to change this default behavior by setting the compiler options strictNullChecks to true. Now, you have to include the Nullable types explicitly using a union type (explained later on), otherwise, you will get an error:
let x: string = "foo";
x = null;    // error
let y: string | null = "foo";
y = null;    // ok

Sometimes, you would like to tell compiler that you know the type better than it does and it should trust you. For instance, imagine a situation where you receive data over HTTP and know exactly the structure of the received data. The compiler doesn't know such structure of course. In this case, you want to turn off the type checking when assigning the data to a variable. It is possible with so called type assertions. A type assertion is like a type cast in other languages, but without the checking of data. You can do that either with angle bracket or the as syntax.

let element = <HTMLCanvasElement> document.getElementById('canvas');
let element = document.getElementById('canvas') as HTMLCanvasElement;

Interfaces, classes, and enums

An interface is a way to take a particular structure/shape and give it a name so that we can reference it later as a type. It defines a contract within our code. Interfaces begin with the keyword interface. Let's take an example:

interface Person {
  name: string
  children?: number
  isMarried(): boolean
  (): void
}

The specified interface Person has the following:

  • The name property of type string.
  • The optional property children of type number. Optional properties are denoted by a question mark and can be omitted.
  • The isMarried method that returns a boolean value.
  • Anonymous (unnamed) method that returns nothing.

Typescript allows you to use the syntax [index: type] to specify a string or number type based collection of key/value pairs. Interfaces perfectly fit such data structures. For example, consider the following syntax:

interface Dictionary {
  [index: number]: string
}

Note

An interface is only used by TypeScript compiler at compile time, and is then removed. Interfaces don't end up in the final JavaScript output. General, no types appear in the output. You can see that in the TypeScript playground mentioned earlier.

Beside interfaces, there are classes that describe objects. A class acts as a template for instantiating specific objects. The syntax for TypeScript's classes is almost identical to that of native classes in ECMAScript 2015 with some handy additions. In TypeScript, you can use public, private, protected, and readonly access modifiers:

class Dog {
  private name: string;    // can only be accessed within this class
  readonly owner: string = "Max";    // can not be modified
  constructor(name: string) {this.name = name;}
  protected sayBark() { }
}

let dog = new Dog("Sam");
dog.sayBark();  // compiler error because method 'sayBark' is protected and
                // only accessible within class 'Dog' and its subclasses.

Members with omitted modifiers are public by default. If a property or method is declared with the static keyword, there is no need to create an instance to access them.

A class can be abstract, that means, it may not be instantiated directly. Abstract classes begin with the keyword abstract. A class can implement an interface as well as extend another class. We can achieve that using the implements and extends keywords, respectively. If a class implements some interface, it must adopt all properties from this interface; otherwise, you will get an error about missing properties:

interface Animal {
  name: string;
}

class Dog implements Animal {
  name: string;
  // do specific things
}

class Sheepdog extends Dog {
  // do specific things    
}

Note

Derived classes that contain constructor functions must call super(). The super() call executes the constructor function on the base class.

It is possible to declare a constructor parameter with a modifier. As result, a member will be created and initialized in one place:

class Dog {
  constructor(private name: string) { }

  // you can now access the property name by this.name
}

Note

This shortened syntax is often used in Angular when we inject services into components. Angular's services are normally declared in the component's constructor with the private modifier.

The last basic type to be mentioned here is enum. Enums allow us to define a set of named constants. Enum members have numeric values associated with them (started with 0):

enum Color {
  Red,
  Green,
  Blue
}

var color = Color.Red;    // color has value 0

Functions

Parameters and return values in the function signature can be typed too. Types protects you against JavaScript errors during function execution because the compiler warns you punctually at build time when the wrong types are used:

function add(x: number, y: number): number {
  return x + y;
}

Function type is a way to declare the type of a function. To explicitly declare a function type, you should use the keywords var or let, a variable name, a colon, a parameter list, a fat arrow =>, and the function's return type:

var fetchName: (division: Division, customer: Customer) => string;

Now, you must provide an implementation of this declaration:

fetchName = function (division: Division, customer: Customer): string {
  // do something
}

This technique is especially useful for callbacks. Imagine a filter function which filters arrays by some criterion. An exact criterion can be encapsulated in the passed in callback function that acts as predicate:

function filter(arr: number[], callback: (item: number) => boolean): number[] {
  let result: number[] = [];
  for (let i = 0; i < arr.length; i++) {
    if (callback(arr[i])) {
      result.push(arr[i]);
    }
  }
  return result;
}

A possible function call with a specific callback could appear as follows:

let result = filter([1, 2, 3, 4], (item: number) => item > 3);

In TypeScript, every function parameter is assumed to be required. There are two ways to mark a parameter as optional (optional parameters can be omitted when calling the function).

  • Use a question mark after the parameter name:
functiondoSomething(param1: string, param2?: string) {
  // ...
}
  • Use the parameter's default value (ECMAScript 2015 feature), which gets applied when no value is provided:
functiondoSomething(param1: string, param2 = "some value") {
  // ...
}

Now, you are able to call this function with just one value.

doSomething("just one value");

Generics

In TypeScript, you can define generic functions, interfaces, and classes like in other programming languages. A generic function has type parameters listed in angle brackets:

function reverseAndMerge<T>(arr1: T[], arr2: T[]): T[] {
  return arr1.reverse().concat(arr2.reverse());
}

let arr1: number[] = [1, 2, 3];
let arr2: number[] = [4, 5, 6];
let arr = reverseAndMerge(arr1, arr2);

Such generic functions can be defined with generic interfaces as well. The function signature for reverseAndMerge is compatible with the following generic interface:

interface GenericArrayFn<T> {
  (arr1: T[], arr2: T[]): T[];
}

let arr: GenericArrayFn<number> = reverseAndMerge;

Note that the generic type parameter list in angle brackets follows the name of the function and interface. This is also true for classes:

class GenericValue<T> {
  constructor(private value: T) { }
  increment: (x: T) => T;
  decrement: (x: T) => T;
}

let genericValue = new GenericValue<number>(5);
genericValue.increment = function (x) {return ++x;};
genericValue.decrement = function (x) {return --x;};

Modules

ECMAScript 2015 has introduced built-in modules. The features of modules are as follows:

  • Each module is defined in its own file.
  • Functions or variables defined in a module are not visible outside unless you explicitly export them.
  • You can place the export keyword in front of any variable, function, or class declaration to export it from the module.
  • You can use the import keyword to consume the exported variable, function, or class declaration.
  • Modules are singletons. Only a single instance of a module exists, even if it was imported multiple times.

Some exporting possibilities are listed here:

// export data
export let color: string = "red";

// export function
export function sum(num1: number, num2: number) {
  return num1 + num1;
}

// export class
export class Rectangle {
  constructor(private length: number, private width: number) { }
}

You can declare a variable, function, or class and export it later. You can also use the as keyword to rename exports. A new name is the name used for importing:

class Rectangle {
  constructor(private height: number, private width: number) { }
}

export {Rectangle as rect};

Once you have a module with exports, you can access its functionality in another module using the import keyword:

import {sum} from "./lib.js";
import {Rect, Circle} from "./lib.js";

let sum = sum(1, 2);
let rect = new Rect(10, 20);

There is a special case that allows you to import the entire module as a single object. All exported variables, functions, and classes are available on that object as properties:

import * as lib from "./lib.js";

let sum = lib.sum(1, 2);

Imports can be renamed with the as keyword and used under the new name:

import {sum as add} from "./lib.js";

let sum = add(1, 2);
 

Advanced types, decorators, and compiler options


TypeScript has more types and advanced constructs such as decorators and type definition files. This chapter gives an overview on advanced topics and shows how to customize the compiler configuration.

Union types and type aliases

A union type describes a value that can be one of many types. The vertical bar | is used as separator for each type the value can have. For instance, number | string is the type of a value that can be a number or string. For such values, we can only access members that are common to all types in the union. The following code works because the length property exists on both strings and arrays:

var value: string | string[] = 'some';
let length = value.length;

The next code snippet gives an error because the model property does not exist on the Bike type:

interface Bike {
  gears: number;
}

interface Car {
  gears: number;
  model: string;
}

var transport: Bike | Car = {gears: 1};
transport.model = "Audi";    // compiler error

Type alias is used as alternative name for the existing type or combination of types. It doesn't create a new type. A type alias begins with the type keyword.

type PrimitiveArray = Array<string|number|boolean>;
type Callback = () => number;
type PrimitiveArrayOrCallback = PrimitiveArray | Callback;

Type aliases can be used for better code readability, for example, in the function parameter list.

function doSomething(n: PrimitiveArrayOrCallback): number {
  ...
}

Type aliases can also be generic and make tricky types, which can not be made with interfaces.

Type inference

Type inference is used when the type is not provided explicitly. For instance in the following statements:

var x = "hello";
var y = 99;

These don't have explicit type annotations. TypeScript can infer that x is a string and y is a number. As you see, the type can be omitted if the compiler is able to infer it. TypeScript improves the type inference continuously. It tries to guess a best common type when elements of several types are present in an array. The type of the following variable animal, where Sheepdog extends Dog, is Dog[]:

let animal = [new Dog(), new Sheepdog()];

The best common type of the next array is (Dog | Fish)[] because the class Fish doesn't extend to any other class:

class Fish {
  kind: string;
}

let animal = [new Dog(), new Sheepdog(), new Fish()];

Type inference is also used for functions. In the next example, the compiler can figure out the types of the function's parameter (string) and the return value (boolean):

let isEmpty: (param: string) => boolean;
isEmpty = function(x) {return x === null || x.length === 0};

Decorators

Decorators were proposed in ECMAScript 2016 (https://github.com/wycats/javascript-decorators). They are similar to Java annotations--they also add metadata to class declaration, method, property, and the function's parameter, but they are more powerful. They add new behaviors to their targets. With decorators, we can run arbitrary code before, after, or around the target execution, like in aspect-oriented programming, or even replace the target with a new definition. In TypeScript, you can decorate constructors, methods, properties, and parameters. Every decorator begins with the @ character followed by the name of the decorator.

How does it work under the hood that takes its target as argument? Let's implement a classic example with a logging functionality. We would like to implement a method decorator @log. A method decorator accepts three arguments: an instance of the class on which the method is defined, a key for the property, and the property descriptor (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).

If the method decorator returns a value, it will be used as a new property descriptor for this method:

const log = (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => {
  // save a reference to the original method
  var originalMethod = descriptor.value;
  // replace the original function
  descriptor.value = function(...args: any[]) {
    console.log("Arguments: ", args.join(", "));
    const result = originalMethod.apply(target, args);
    console.log("Result: ", result);
    return result;
  }
  return descriptor;
}

class Rectangle {
  @log
  area(height: number, width: number) {
    return height * width;
  }
}

let rect = new Rectangle();
let area = rect.area(2, 3);

This decorator logs received arguments and return values. Decorators can be composed and customized with parameters too. You can write the following, for instance:

class Rectangle {
  @log("debug")
  @persist("localStorage")
  area(height: number, width: number) {
    return height * width;
  }
}

Angular offers different types of decorators that are used for dependency injection or adding metadata information at compilation time:

  • Class decorators such as @NgModule, @Component, and @Injectable
  • Property decorators such as @Input and @Output
  • Method decorators such as @HostListener
  • Parameter decorators such as @Inject

TypeScript compiler is able to emit some design-time type metadata for decorators. To access this information, we have to install a Polyfill called reflect-metadata:

npm install reflect-metadata --save

Now we can access, for example, the type of the property (key) on the target object as follows:

let typeOfKey = Reflect.getMetadata("design:type", target, key);

Note

Refer to the official TypeScript documentation to learn more about decorators and reflect metadata API (http://www.typescriptlang.org/docs/handbook/decorators.html).

Note

In TypeScript, Angular applications, decorators are enabled by setting the compiler options emitDecoratorMetadata and experimentalDecorators to true (compiler options are described later on).

Type definition files

JavaScript programs written in native JavaScript don't have any type information. If you add a JavaScript library such as jQuery or Lodash to your TypeScript-based application and try to use it, the TypeScript compiler can find any type information and warn you with compilation errors. Compile-time safety, type checking, and context-aware code completion get lost. That is where type definition files come into play.

Type definition files provide type information for JavaScript code that is not statically typed. Type definition files ends with .d.ts and only contain definitions which are not emitted by TypeScript. The declare keyword is used to add types to JavaScript code that exists somewhere. Let's take an example. TypeScript is shipped with the lib.d.ts library describing ECMAScript API. This type definition file is used automatically by the TypeScript compiler. The following declaration is defined in this file without implementation details:

declare function parseInt(s: string, radix?: number): number;

Now, when you use the parseInt function in your code, the TypeScript compiler ensures that your code uses the correct types and IDEs show context-sensitive hints when you're writing code. Type definition files can be installed as dependencies under the node_modules/@types directory by typing the following command:

npm install @types/<library name> --save-dev

A concrete example for jQuery library is:

npm install @types/jquery --save-dev

Note

In Angular, all type definition files are bundled with Angular npm packages and located under node_modules/@angular. There is no need to install such files separately like we did for jQuery. TypeScript finds them automatically.

Most of the time, you have the compile targetES5 (generated JavaScript version, which is widely supported), but want to use some ES6 (ECMAScript 2015) features by adding Polyfills. In this case, you must tell the compiler that it should look for extended definitions in the lib.es6.d.ts or lib.es2015.d.ts file. This can be achieved in compiler options by setting the following:

"lib": ["es2015", "dom"]

Compiler options

Typically, the first step in a new TypeScript project is to add in a tsconfig.json file. This file defines the project and compiler settings, for instance, files and libraries to be included in the compilation, output structure, module code generation, and so on. A typical configuration in tsconfig.json for Angular 2+ projects looks like the following:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "outDir": "dist",
    "lib": ["es2015", "dom"]
  },
  "types": ["node"],
  "exclude": ["node_modules", "dist"]
}

The listed compiler settings are described as follows. A full list of all options is available at the TypeScript documentation page (https://www.typescriptlang.org/docs/handbook/compiler-options.html).

Option

Type

Default

Description

target

string

ES3

This specifies ECMAScript target version: ES3, ES5, ES2015, ES2016, and ES2017.

module

string

ES6 if target is "ES6" and CommonJS otherwise

This specifies the format of module code generation: None, CommonJS, AMD, System, UMD, ES6, or ES2015.

moduleResolution

string

Classic if module is "AMD," System, ES6, and Node otherwise

This determines how modules get resolved. Either Node for Node.js style resolution or Classic.

noImplicitAny

boolean

false

This raises errors on expressions and declarations with an implied any type.

sourceMap

boolean

false

This generates the corresponding .map file. This is useful if you want to debug original files.

emitDecoratorMetadata

boolean

false

This emits design type metadata for decorated declarations in source. You have to set this value to true if you want to develop web applications with Angular.

experimentalDecorators

boolean

false

This enables experimental support for ECMAScript decorators. You have to set this value to true if you want to develop web applications with Angular.

outDir

string

-

This is the output directory for compiled files.

lib

string[]

Refer to the documentation for more information.

This is the list of library files to be included in the compilation. Refer to the documentation for more information.

types

string[]

-

This is the list of names of type definitions to include.

exclude

string[]

-

This is the list of (sub) directories excluded from the compilation.

Note

You can stop the compiler from emitting JavaScript on errors by setting the --noEmitOnError option to true.

 

Angular cheat sheet - overview of key concepts


Angular 2 introduces completely new concepts for building web applications. The new Angular platform is complex. It is not possible to explain numerous Angular features in detail. Instead, we will concentrate on the most important key concepts such as dependency injection, components, and communication between them, built-in directives, services, template syntax, forms, and routing.

Components, services, and dependency injection

Normally, you write Angular applications by composing HTML templates with the Angular-specific markup and component classes to manage those templates. A component is simply a TypeScript class annotated with @Component. The @Component decorator is used to define the associated metadata. It expects an object with the following most used properties:

  • selector: This is the name of the HTML tag representing this component
  • template: This is an inline-defined template with HTML/Angular markup for the view
  • templateUrl: This is the path to an external file where the template resides
  • styles: An inline-defined styles to be applied to this component's view
  • styleUrls: An array of paths to external files with styles to be applied to this component's view
  • providers: An array of providers available to this component and its children
  • exportAs: This is the name under which the component instance is exported in a template
  • changeDetection: This is the change detection strategy used by this component
  • encapsulation: This is the style encapsulation strategy used by this component

A component class interacts with the view through an API of properties and methods. Component classes should delegate complex tasks to services where the business logic resides. Services are just classes that Angular instantiates and then injects into components. If you register services at the root component level, they act as singletons and share data across multiple components. In the next section, Angular modularity and lifecycle hooks, we will see how to register services. The following example demonstrates how to use components and services. We will write a service class ProductService and then specify an argument of type ProductService in the constructor of ProductComponent. Angular will automatically inject that service into the component:

import {Injectable, Component} from '@angular/core';

@Injectable()
export class ProductService {
  products: Product[];

  getProducts(): Array<Product> {
    // retrieve products from somewhere...
    return products;
  }
}

@Component({
  selector: 'product-count',
  template: `<h2 class="count">Found {{products.length}} products</h2>`,
  styles: [`
    h2.count {
      height: 80px;
      width: 400px;
    }
  `]
})
export default class ProductComponent {
  products: Product[] = [];

  constructor(productService: ProductService) {
    this.products = productService.getProducts();
  }
}

Note

Notice that we applied the @Injectable() decorator to the service class. This is necessary for emitting metadata that Angular needs to inject other dependencies into this service. Using @Injectable is a good programming style even if you don't inject other services into your service.

It is good to know what an item in the providers array looks like. An item is an object with the provide property (symbol used for dependency injection) and one of the three properties useClass, useFactory, or useValue that provide implementation details:

{provide: MyService, useClass: MyMockService}
{provide: MyService, useFactory: () => {return new MyMockService()}}
{provide: MyValue, useValue: 50}

Templates and bindings

A template tells Angular how to render the component's view. Templates are HTML snippets with the specific Angular's template syntax, such as interpolation, property, attribute, and event bindings, built-in directives, and pipes to mention just a few. We will give you a quick overview of the template syntax starting with interpolation. Interpolation is used to evaluate expressions in double curly braces. The evaluated expression is then converted to a string. The expression can contain any mathematical calculations, component's properties and methods, and many more:

<p>Selected car is {{currentCar.model}}</p>

Angular evaluates template expressions after every change detection cycle. Change detection cycles are triggered by many asynchronous activities such as HTTP responses, key and mouse events, and many more. The next fundamental template syntax is related to various bindings. Property binding sets an element property to a component property value. The element property is defined in square brackets:

<img [src]="imageUrl">
<button [disabled]="formValid">Submit</button>

Here, imageUrl and formValid are a component's properties. Note that this is a one-way binding because the data flow occurs in one direction, from the component's properties into target element properties. Attribute binding allows us to set an attribute. This kind of binding is used when there is no element property to bind. The attribute binding uses square brackets too. The attribute name itself is prefixed with attr., for example, consider ARIA attributes for web accessibility:

<button [attr.aria-expanded]="expanded" [attr.aria-controls]="controls">
  Click me
</button>

User interactions result in a data flow from an element to a component. In Angular, we can listen for certain key, mouse, and touch events by means of event binding. The event binding syntax consists of a target event name within parentheses on the left and a quoted template statement on the right. In particular, you can call a component's method. In the next code snippet, the onSave() method is called on a click:

<button (click)="onSave()">Save</button>

The method (generally template statement) gets a parameter--an event object named $event. For native HTML elements and events, $event is a DOM event object:

<input [value]="name" (input)="name=$event.target.value">

Two-way binding is possible as well. The [(value)] syntax combines the brackets of property binding with the parentheses of event binding. Angular's directive NgModel is best suited for the two-way binding on native or custom input elements. Consider the following sample:

<input [(ngModel)]="username">

Is equivalent to:

<input [value]="username" (input)="username=$event.target.value">

Two-way binding in a nutshell: a property gets displayed and updated at the same time when the user makes changes. A template reference variable is another example of handy template syntax. You can declare a variable with the hash symbol (#) on any DOM element and reference this variable anywhere in the template. The next example shows the username variable declared on an input element. This reference variable is consumed on a button--it is used to get an input value for the onclick handler:

<input #username>
<button (click)="submit(username.value)">Ok</button>

A template reference variable can also be set to a directive. A typical example is the NgForm directive which provides useful details about the form elements. You can, for example, disable the submit button if the form is not valid (required fields are not filled in and so on):

<form #someForm="ngForm">
  <input name="name" required [(ngModel)]="name">
  ...
  <button type="submit" [disabled]="!someForm.form.valid">Ok</button>
</form>

Last but not least, the pipe operator (|). It is used for the transformation of the expression's result. The pipe operator passes the result of an expression on the left to a pipe function on the right. For example, the pipe date formats JavaScript Date object according to the specified format (https://angular.io/docs/ts/latest/api/common/index/DatePipe-pipe.html):

Release date: {{releaseDate | date: 'longDate'}}
// Output: "August 30, 2017"

Multiple chained pipes can be applied as well.

Built-in directives

Angular has a lot of built-in directives: ngIf, ngFor, ngSwitch, ngClass, and ngStyle. The first three directives are so called structural directives, which are used to transform the DOM's structure. Structural directives start with an asterisk (*). The last two directives manipulate the CSS classes and styles dynamically. Let's explain the directives in the examples.

The ngIf directive adds and removes elements in the DOM, based on the Boolean result of an expression. In the next code snippet, <h2>ngIf</h2> is removed when the show property evaluates to false and gets recreated otherwise:

<div *ngIf="show">
  <h2>ngIf</h2>
</div>

Angular 4 has introduced a new else clause with the reference name for a template defined by ng-template. The content within ng-template is shown when the ngIf condition evaluates to false:

<div *ngIf="showAngular; else showWorld">
  Hello Angular
</div>
<ng-template #showWorld>
  Hello World
</ng-template>

ngFor outputs a list of elements by iterating over an array. In the next code snippet, we iterate over the people array and store each item in a template variable called person. This variable can be then accessed within the template:

<ui>
  <li *ngFor="let person of people">
    {{person.name}}
  </li>
</ui>

ngSwitch conditionally swaps the contents dependent on condition. In the next code snippet, ngSwitch is bound to the choice property. If ngSwitchCase matches the value of this property, the corresponding HTML element is displayed. If no matching exists, the element associated with ngSwitchDefault is displayed:

<div [ngSwitch]="choice">
  <h2 *ngSwitchCase="'one'">One</h3>
  <h2 *ngSwitchCase="'two'">Two</h3>
  <h2 *ngSwitchDefault>Many</h3>
</div>

ngClass adds and removes CSS classes on an element. The directive should receive an object with class names as keys and expressions as values that evaluate to true or false. If the value is true, the associated class is added to the element. Otherwise, if false, the class is removed from the element:

<div [ngClass]="{selected: isSelected, disabled: isDisabled}">

ngStyle adds and removes inline styles on an element. The directive should receive an object with style names as keys and expressions as values that evaluate to style values. A key can have an optional .<unit> suffix (for example, top.px):

<div [ngStyle]="{'color': 'red', 'font-weight': 'bold', 'border-top': borderTop}">

Note

In order to be able to use built-in directives in templates, you have to import CommonModule from @angular/common and add it to the root module of your application. Angular's modules are explained in the next chapter.

Communication between components

Components can communicate with each other in a loosely coupled manner. There are various ways Angular's components can share data, including the following:

  • Passing data from parent to child using @Input()
  • Passing data from child to parent using @Output()
  • Using services for data sharing
  • Calling ViewChild, ViewChildren, ContentChild, and ContentChildren
  • Interacting with the child component using a local variable

We will only describe the first three ways. A component can declare input and output properties. To pass the data from a parent to a child component, the parent binds the values to the input properties of the child. The child's input property should be decorated with @Input(). Let's create TodoChildComponent:

@Component({
  selector: 'todo-child',
  template: `<h2>{{todo.title}}</h2>`
})
export class TodoChildComponent {
  @Input() todo: Todo;
}

Now, the parent component can use todo-child in its template and bind the parent's todo object to the child's todo property. The child's property is exposed as usual in square brackets:

<todo-child [todo]="todo"></todo-child>

If a component needs to pass the data to its parent, it emits custom events via the output property. The parent can create a listener to a particular component's event. Let's see that in action. The child component ConfirmationChildComponent exposes an EventEmitter property decorated with @Output() to emit events when the user clicks on buttons:

@Component({
  selector: 'confirmation-child',
  template: `
    <button (click)="accept(true)">Ok</button>
    <button (click)="accept(false)">Cancel</button>
  `
})
export class ConfirmationChildComponent {
  @Output() onAccept = new EventEmitter<boolean>();

  accept(accepted: boolean) {
    this.onAccept.emit(accepted);
  }
}

The parent subscribes an event handler to that event property and reacts to the emitted event:

@Component({
  selector: 'confirmation-parent',
  template: `
    Accepted: {{accepted}}
    <confirmation-child (onAccept)="onAccept($event)"></confirmation-child>
  `
})
export class ConfirmationParentComponent {
  accepted: boolean = false;

  onAccept(accepted: boolean) {
    this.accepted = accepted;
  }
}

A bi-directional communication is possible via services. Angular leverages RxJS library (https://github.com/Reactive-Extensions/RxJS) for asynchronous and event-based communication between several parts of an application as well as between an application and remote backend. The key concepts in the asynchronous and event-based communication are Observer and Observable. They provide a generalized mechanism for push-based notification, also known as the observer design pattern. Observable represents an object that sends notifications, and Observer represents an object that receives them.

Angular implements this design pattern everywhere. For example, Angular's Http service returns an Observable object:

constructor(privatehttp:Http) {}

getCars(): Obvervable<Car[]> {
  return this.http.get("../data/cars.json")
    .map(response => response.json().data as Car[]);
}

In case of the inter-component communication, an instance of the Subject class can be used. This class inherits both Observable and Observer. That means it acts as a message bus. Let's implement TodoService that allows us to emit and receive Todo objects:

@Injectable()
export class TodoService {
  private subject = new Subject();

  toggle(todo: Todo) {
    this.subject.next(todo);
  }

  subscribe(onNext, onError, onComplete) {
    this.subject.subscribe(onNext, onError, onComplete);
  }
}

Components can use this service in the following way:

export class TodoComponent {
  constructor(private todosService: TodosService) {}

  toggle(todo: Todo) {
    this.todosService.toggle(todo);
  }
}

export class TodosComponent {
  constructor(private todosService: TodosService) {
    todosService.subscribe(
      function(todo: Todo) { // TodoComponent sent todo object },
      function(e: Error) { // error occurs },
      function() { // completed }
    );
  }
}

Forms

Forms are the main building blocks in every web application. Angular offers two approaches to build forms: template-driven forms and reactive forms. This section gives you a short overview of template-driven forms.

Note

Reactive forms are suitable when you need to create dynamic forms programmatically in the component's class. Refer to the official Angular documentation to learn reactive forms (https://angular.io/docs/ts/latest/guide/reactive-forms.html).

We already mentioned two directives: NgForm and NgModel. The first directive creates a FormGroup instance and binds it to a form in order to track aggregate form value and validation status. The second one creates a FormControl instance and binds it to the corresponding form element. The FormControl instance tracks the value and the status of the form element. Each input element should have a name property that is required to register the FormControl by the FormGroup under the name you assigned to the name attribute. How to deal with this tracked data? You can export the NgForm and NgModel directives into local template variables such as #f="ngForm" and #i="ngModel", respectively. Here, f and i are local template variables that give you access to the value and status of FormGroup and FormControl, respectively. This is possible because the properties from FormGroup and FormControl are duplicated on the directives themselves. With this knowledge in hand, you can now check if the whole form or a particular form element:

  • Is valid (valid and invalid properties)
  • Has been visited (touched and untouched properties)
  • Has some changed value (dirty and pristine properties)

The next example illustrates the basic concept:

<form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
  <label for="name">Name</label>
  <input type="text" id=name" name="name" required
         [(ngModel)]="name" #i="ngModel"> 
  <div [hidden]="i.valid || i.pristine">
    Name is required
  </div>
  <button>Submit</button>
</form>

// Output values and states
Input value: {{i.value}}
Is input valid? {{i.valid}}
Input visited? {{i.touched}}
Input value changed? {{i.dirty}}
Form input values: {{f.value | json}}
Is form valid? {{f.valid}}
Form visited? {{f.touched}}
Form input values changed? {{f.dirty}}

The NgModel directive also updates the corresponding form element with specific CSS classes that reflect the element's state. The following classes are added/removed dependent on the current state:

State

Class if true

Class if false

Element has been visited

ng-touched

ng-untouched

Element's value has changed

ng-dirty

ng-pristine

Element's value is valid

ng-valid

ng-invalid

This is handy for styling. For example, in case of validation errors, you can set red borders around input elements:

input.ng-dirty.ng-invalid {
  border: solid 1px red;
}

Routing

Angular's router module allows you to configure navigation in a single page application without a full page reload. The router can display different views (compiled component templates) within a special tag called <router-outlet>. During navigation, one view will be replaced by another one. A simple routing configuration looks as follows:

const router: Routes = [
  {path: '', redirectTo: 'home', pathMatch: 'full'},
  {path: 'home', component: HomeComponent},
  {path: 'books', component: BooksComponent}
];

When you navigate to the web context root, you will be redirected to /home. As a reaction to that, the view of the HomeComponent will be displayed in <router-outlet>. It is obvious that a direct navigation to /home displays the same view. A navigation to /books displays the view of BooksComponent. Such router configuration should be converted to an Angular's module by RouterModule.forRoot:

const routes:ModuleWithProviders=RouterModule.forRoot(router);

This is then imported in a root module class. In addition to the root module, an Angular application can consist of a lot of feature or lazy-loaded modules. Such separate modules can have their own router configurations which should be converted to Angular's modules with RouterModule.forChild(router). The next section, Angular modularity and lifecycle hooks, discusses modules in detail. Angular offers two strategies for implementing client-side navigation:

  • HashLocationStrategy: This strategy adds a hash sign (#) to the base URL. Everything after this sign represents a hash fragment of the browser's URL. The hash fragment identifies the route. For example, http://somehost.de:8080/#/books. Changing the route doesn't cause a server-side request. Instead, the Angular application navigates to a new route and view. This strategy works with all browsers.
  • PathLocationStrategy: This strategy is based on the History API and only works in browsers that support HTML5. This is the default location strategy.

The details are to be mentioned here. If you want to use the HashLocationStrategy, you have to import two classes, LocationStrategy and HashLocationStrategy from '@angular/common' and configure providers as follows:

providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]

Providers are described in the next section, Angular modularity and lifecycle hooks. The PathLocationStrategy class requires a configuration of the base URL for the entire application. The best practice is to import APP_BASE_HREF constant from '@angular/common' and use it as a provider in order to configure the base URL:

providers: [{provide: APP_BASE_HREF, useValue: '/'}]

How to trigger a navigation? You can achieve that in two ways, either by a link with a routerLink property, which specifies an array consisting of route (path) and optional parameters:

<a [routerLink]="['/']">Home</a>
<a [routerLink]="['/books']">Books</a>

<router-outlet></router-outlet>

Or programmatically, by invoking the navigate method on Angular's Router service:

import {Router} from '@angular/router';

...

export class HomeComponent {

  constructor(private router: Router) { }

  gotoBooks() {
    this.router.navigate(['/books']);
  }
}

You can also pass parameters to a route. Placeholders for parameters start with a colon (:):

const router: Routes = [
  ...
  {path: 'books/:id', component: BookComponent}
];

Now, when navigating to a book with real parameters, for example, programmatically as this.router.navigate(['/books/2']), the real parameter can be accessed by ActivatedRoute:

import {ActivatedRoute} from '@angular/router';

...

export class BooksComponent {
  book: string;

  constructor(private route: ActivatedRoute) {
    this.book = route.snapshot.params['id'];
  }
}

The router outlet can be named as well:

<router-outlet name="author"></router-outlet>

The associated configuration should contain the outlet property with the name of the router outlet:

{path: 'author', component: AuthorComponent, outlet: 'author'}
 

Angular modularity and lifecycle hooks


Angular modularity with NgModules provides a great way to organize the code in a web application. Many third-party libraries, such as PrimeNG, Angular Material, Ionic, are distributed as NgModules. Lifecycle hooks allow us to perform custom logic at component level at a well-defined time. This section covers these major concepts in detail.

Modules and bootstrapping

Angular modules make it possible to consolidate components, directives, services, pipes, and many more into cohesive blocks of functionality. Angular's code is modularized. Every module has its own functionality. There are FormsModule, HttpModule, RouterModule, and many other modules as well. What does a module look like? A module is a class annotated with the @NgModule decorator (imported from @angular/core). @NgModule takes a configuration object that tells Angular how to compile and run the module code. The most significant properties of the the configuration object are:

  • declarations: The array with components, directives, and pipes, which are implemented in that module and belong to that module.
  • imports: The array with dependencies in form of other modules which need to be made available to that module.
  • exports: The array of components, directives, and pipes to be exported and permitted to be imported by another modules. The rest is private. This is the module's public API and similar to how the export keyword works in ECMAScript modules.
  • providers: This is the array of services (service classes, factories, or values), which are available in that module. Providers are parts of the module and can be injected into components (inclusive sub-components), directives, and pipes defined within the module.
  • bootstrap: Every Angular application has at least one module--the root module. The bootstrap property is only used in the root module and contains the component which should be instantiated first when bootstrapping the application.
  • entryComponents: This is the array of components that Angular generates component factories for. Normally, you need to register a component as an entry component when it is intended to be created dynamically at runtime. Such components can not be figured out automatically by Angular at template compilation time.

A typical module configuration for any separate example in this book looks something like this:

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FormsModule} from '@angular/forms';
import {APP_BASE_HREF} from '@angular/common';

// PrimeNG modules needed in this example
import {ButtonModule} from 'primeng/components/button/button';
import {InputTextModule} from 'primeng/components/inputtext/inputtext';

import {AppComponent} from './app.component';
import {SectionComponent} from './section/section.component';
import {routes} from './app-routing.module';

@NgModule({
  imports: [BrowserModule, BrowserAnimationsModule, FormsModule,
            routes, ButtonModule, InputTextModule],
  declarations: [AppComponent, SectionComponent],
  providers: [{provide: APP_BASE_HREF, useValue: '/'}],
  bootstrap: [AppComponent]
})
export class AppModule { }

Note

BrowserModule is needed to get access to the browser-specific renderers and Angular standard directives such as ngIf and ngFor. Don't import BrowserModule in any other modules except the root module. Feature modules and lazy-loaded modules should import CommonModule instead.

The following is an example of how to bootstrap an Angular application in the JIT mode (just in time compilation):

import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app';

platformBrowserDynamic().bootstrapModule(AppModule);

In the ahead-of-time mode (AOT compilation), you need to provide a factory class. To generate the factory class, you must run the ngc compiler instead of the TypeScript tsc compiler. In the last two sections of this chapter, you will see how to use AOT with Webpack and Angular CLI. The bootstrapping code in the AOT mode looks like the following:

import {platformBrowser} from '@angular/platform-browser';
import {AppModuleNgFactory} from './app.ngfactory';

platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

Note

Templates with bindings written in Angular need to be compiled. With AOT, the compiler runs once at build time. With JIT, it runs every time at runtime. Browsers load a pre-compiled version of the application much faster and there is no need to download the Angular compiler if the app is already compiled.

Modules can also be lazy loaded when they get requested (on demand). This approach reduces the size of web resources loaded on initial page display. The page appears faster. If you want to enable lazy loading, you have to configure the router to load the module lazy. All you need is a path object with a loadChildren property, which points to the path and name of the lazy loaded module:

{path: "section", loadChildren: "app/section/section.module#SectionModule"}

Note that the value of loadChildren property is a string. Furthermore, the module importing this router configuration should not declare the lazy loaded module as dependency in the imports property of the configuration object.

Lifecycle hooks

Angular components come with lifecycle hooks, which get executed at specific times in the component's life. For this purpose, Angular offers different interfaces. Each interface has a method of the same name as the interface name with the prefix ng. Each method is executed when the corresponding lifecycle event occurs. They are also called lifecycle hook methods. Angular calls the lifecycle hook methods in the following sequence after the constructor has been called:

The lifecycle hook method

Purpose and timing

ngOnChanges

This is called whenever one or more data-bound input properties change. This method is called on initial changes (before ngOnInit) and any other subsequent changes. This method has one parameter--an object with keys of type string and values of type SimpleChange. The keys are the component's property names. The SimpleChange object contains current and previous values. A usage example is shown next.

ngOnInit

This is called once, after the first ngOnChanges. Note that the constructor of a component should only be used for dependency injection because data-bound input values are not yet set in the constructor. Everything else should be moved to the ngOnInit hook. A usage example is shown next.

ngDoCheck

This is called during every change detection run. It is a good place for custom logic, which allows us to do a fine-grained check of which property on our object changed.

ngAfterContentInit

This is called once, after Angular puts external content into the component's view. A placeholder for any external content is marked with the ngContent directive (the ng-content tag). A usage example of the ngContent directive is demonstrated afterwards.

ngAfterContentChecked

This is called after Angular checks the content put into the component's view.

ngAfterViewInit

This is called once, after Angular initializes the component's and child's views.

ngAfterViewChecked

This is called after Angular checks the component's views and child views.

ngOnDestroy

This is called just before Angular destroys the component's instance. This happens when you remove the component with built-in structural directives such as ngIf, ngFor, ngSwitch, or when you navigate to another view. This is a good place for cleanup operations such as unsubscribing observables, detaching event handlers, canceling interval timers, and so on.

Let's see an example of how to use ngOnInit and ngOnChanges:

import {Component, OnInit, OnChanges, SimpleChange} from '@angular/core';

@Component({
  selector: 'greeting-component',
  template: `<h1>Hello {{text}}</h1>`
})
export class GreetingComponent implements OnInit, OnChanges {
  @Input text: string;

  constructor() { }

  ngOnInit() {
    text = "Angular";
  }

  ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
    console.log(changes.text);
    // changes = {'text': {currentValue: 'World', previousValue: {}}}
    // changes = {'text': {currentValue: 'Angular', 
                  previousValue: 'World'}}
  }
}

Usage in HTML:

<greeting-component [text]="World"></greeting-component>

Let's now see how to use the ngContent directive:

export @Component({
  selector: 'greeting-component',
  template: `<div><ng-content></ng-content> {{text}}</div>`
})
class GreetingComponent {
  @Input text: string;
}

Usage in HTML:

<greeting-component [text]="World"><b>Hello</b></greeting-component>

After the component's initialization, the following hook methods get always executed on every change detection run: ngDoCheck -> ngAfterContentChecked -> ngAfterViewChecked -> ngOnChanges.

 

Running PrimeNG with SystemJS


PrimeNG (https://www.primefaces.org/primeng) is an open source library of rich UI components for Angular 2+. PrimeNG is derived from PrimeFaces--the most popular JavaServer Faces (JSF) component suite. If you know PrimeFaces, you will feel at home with PrimeNG due to similar API. Currently, PrimeNG has 80+ visually stunning widgets that are easy to use. They are divided into several groups such as input and select components, buttons, data iteration components, panels, overlays, menus, charts, messages, multimedia, drag-and-drop, and miscellaneous. There are also 22+ free and premium themes.

PrimeNG fits perfectly with the mobile and desktop development because it is a responsive and touch optimized framework. PrimeNG showcase is a good place to play with the components, try them in action, study documentation, and code snippets. Anyway, we need a systematic approach for getting started with PrimeNG. This is what this book tries to convey. In this chapter, we will set up and run PrimeNG with SystemJS (https://github.com/systemjs/systemjs)--universal module loader supporting various module formats. SystemJS is a good choice for learning purposes if you want to try TypeScript, Angular, PrimeNG code snippets, or write small applications in Plunker (https://plnkr.co) because it can load your files, transpile them (if needed) and resolve module dependencies on-the-fly. In the real applications, you should choose Webpack or Angular CLI-based setups that have more power and advanced configurations. They also bundle your application in order to reduce the amount of HTTP requests. Those setups will be discussed in the next two sections.

The SystemJS configuration for Angular

First of all, you need to install Node.js and npm, which we already mentioned in the TypeScript fundamentals you need to know section. Why do we need npm? In HTML and SystemJS configuration, we could reference all dependencies from https://unpkg.com. But, we prefer to install all dependencies locally so that IDEs are fine with autocompletion. For instance, to install SystemJS, you have to run the following command in a console of your choice:

npm install systemjs --save

For readers, we created a complete demo seed project where all dependencies are listed in the package.json file.

Note

The complete seed project with PrimeNG and SystemJS is available on GitHub athttps://github.com/ova2/angular-development-with-primeng/tree/master/chapter1/primeng-systemjs-setup.

All dependencies in the seed project can be installed by running npm install in the project root. If you explore the index.html file, you can see that the SystemJS library is included in the <head> tag. After that, it becomes available as a global System object, which exposes two static methods: System.import() and System.config(). The first method is used to load a module. It accepts one argument--a module name, which can be either a file path or a logical name mapped to the file path. The second method is used for setting configuration. It accepts a configuration object as an argument. Normally, the configuration is placed within the systemjs.config.js file. Complete scripts to be included in index.html are TypeScript compiler, Polyfills, and SystemJS related files. The bootstrapping occurs by executing System.import('app'):

<script src="../node_modules/typescript/lib/typescript.js"></script>
<script src="../node_modules/core-js/client/shim.min.js"></script>
<script src="../node_modules/zone.js/dist/zone.js"></script>
<script src="../node_modules/systemjs/dist/system.src.js"></script>
<script src="../systemjs.config.js"></script>

<script>
  System.import('app').catch(function (err) {
    console.error(err);
  });
</script>

An excerpt from the configuration object for Angular projects is listed here:

System.config({
  transpiler: 'typescript',
  typescriptOptions: {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  map: {
    '@angular/animations':
      'node_modules/@angular/animations/bundles/animations.umd.min.js',
    '@angular/common':
      'node_modules/@angular/common/bundles/common.umd.min.js',
    '@angular/compiler':
      'node_modules/@angular/compiler/bundles/compiler.umd.min.js',
    '@angular/core': 
      'node_modules/@angular/core/bundles/core.umd.min.js',
    '@angular/forms':
      'node_modules/@angular/forms/bundles/forms.umd.min.js',
    ...
    'rxjs': 'node_modules/rxjs',
    'app': 'src'
  },
  meta: {
    '@angular/*': {'format': 'cjs'}
  },
  packages: {
    'app': {
      main: 'main',
      defaultExtension: 'ts'
    },
    'rxjs': {main: 'Rx'}
});

A brief explanation gives an overview of the most important configuration options:

  • The transpiler option specifies a transpiler for TypeScript files. Possible values are typescript, babel, and traceur. The transpilation happens in browser on-the-fly.
  • The typescriptOptions option sets the TypeScript compiler options.
  • The map option creates aliases for module names. When you import a module, the module name is replaced by an associated value according to the mapping. In the configuration, all entry points for Angular files are in UMD format.
  • The packages option sets meta information for imported modules. For example, you can set the main entry point of the module. Furthermore, you can specify default file extensions to be able to omit them when importing.

Adding PrimeNG dependencies

Every project using PrimeNG needs the locally installed library. You can achieve this by running the following command:

npm install primeng --save

As a result, PrimeNG is installed in your project root under the node_modules folder as well as added in package.json as a dependency. Here again, you can skip this step if you use the seed project hosted on GitHub--just run npm install. The next step is to add two new entries to the SystemJS configuration file. For shorter import statements, it is recommended to map primeng to node_modules/primeng. PrimeNG components are distributed as CommonJS modules ending with .js. That means we should set the default extension too:

System.config({
  ...
  map: {
    ...
    'primeng': 'node_modules/primeng'
  },
  packages: {
    'primeng': {
      defaultExtension: 'js'
    },
    ...
  }
});

Now, you are able to import PrimeNG modules from primeng/primeng. For instance, write this line to import AccordionModule and MenuItem:

import{AccordionModule, MenuItem} from 'primeng/primeng';

This way of importing is not recommended in production because all other available components will be loaded as well. Instead of that, only import what you need using a specific component path:

import {AccordionModule} from 'primeng/components/accordion/accordion';
import {MenuItem} from 'primeng/components/common/api';

In the demo application, we will only use ButtonModule and InputTextModule so that we need to import them as follows:

import {ButtonModule} from 'primeng/components/button/button';
import {InputTextModule} from 'primeng/components/inputtext/inputtext';

The demo project we want to create consists of application code and assets. A detailed description of every file would go beyond the scope of this book. We will only show the project structure:

A typically PrimeNG application needs a theme. We would like to take the Bootstrap theme. The file index.html must have three CSS dependencies included within the <head> tag--the theme, the PrimeNG file, and the FontAwesome file for SVG icons (http://fontawesome.io):

<link rel="stylesheet" type="text/css"
      href="../node_modules/primeng/resources/themes/bootstrap/theme.css"/>
<link rel="stylesheet" type="text/css"
      href="../node_modules/primeng/resources/primeng.min.css"/>
<link rel="stylesheet" type="text/css"
      href="src/assets/icons/css/font-awesome.min.css"/>

All FontAwesome files were placed under src/assets/icons. Mostly PrimeNG components are native, but there is a list of components with third-party dependencies. These are explained in the following table:

Component

Dependency

Schedule

FullCalendar and Moment.js

Editor

Quill editor

GMap

Google Maps

Charts

Charts.js

Captcha

Google Recaptcha

Exact links to those dependencies will be shown later in concrete examples. For now, we have finished our setup. Let's start our first application by running npm start in the project root.

The application gets launched in browser with two PrimeNG components, as shown in the following screenshot. As you can see, a lot of single web resources (CSS and JS files) are being loaded in the browser:

 

Setting up PrimeNG project with Webpack


Webpack (https://webpack.js.org) is a de facto standard bundler for single-page applications. It analyzes dependencies between JavaScript modules, assets (styles, icons, and images) as well as other files in your application and bundles everything together. In Webpack, everything is a module . You can, for example, import a CSS file like a JavaScript file using require('./myfile.css') or import './myfile.css'.

Webpack can figure out the right processing strategy for imported files by means of the file extension and associated loader. It is not always reasonable to build one big bundle file. Webpack has various plugins to split your code and generate multiple bundle files. It can also load parts of your application asynchronously on demand (lazy loading). All these features make it a power tool. In this section, we will give a high-level overview of Webpack 2 core concepts and show essential steps for creating a Webpack-based Angular, PrimeNG application.

Note

The complete seed project with PrimeNG and Webpack is available on GitHub athttps://github.com/ova2/angular-development-with-primeng/tree/master/chapter1/primeng-webpack-setup. The project structure was kept the same as in the SystemJS-based setup.

Entry point and output

JavaScript and other files imported into each other are closely interwoven. Webpack creates a graph of all such dependencies. The starting point of this graph is called entry point. An entry point tells Webpack where to start to resolve all dependencies and creates a bundle. Entry points are created in the Webpack configuration file using the entry property. In the seed project on GitHub, we have two configuration files, one for the development mode (webpack.dev.js) and one for the production (webpack.prod.js) mode, each with two entry points.

In the development mode, we use the main entry point for JIT compilation. The main.jit.ts file contains quite normally bootstrapping code. The second entry point combines files from core-js (Polyfills for modern ECMAScript features) and zone.js (the basis for Angular's change detection):

entry: {
  'main': './main.jit.ts',
  'polyfill': './polyfill.ts'
}

In the production mode, we use the main entry point for AOT compilation. JIT and AOT were mentioned in the Angular modularity and lifecycle hooks section:

entry: {
  'main': './main.aot.ts',
  'polyfill': './polyfill.ts'
}

The output property tells Webpack where to bundle your application. You can use placeholders such as [name] and [chunkhash] to define what the names of output files look like. The [name] placeholder will be replaced by the name defined in the entry property. The [chunkhash] placeholder will be replaced by the hash of the file content at project build time. The chunkFilename option determines the names of on-demand (lazy) loaded chunks--files loaded by System.import(). In the development mode, we don't use [chunkhash] because of performance issues during hash generation:

output: {
  filename: '[name].js',
  chunkFilename: '[name].js'
}

The [chunkhash] placeholder is used in the production mode to achieve so called long term caching--every file gets cached in the browser and will be automatically invalidated and reloaded when the hash changes:

output: {
  filename: '[name].[chunkhash].js',
  chunkFilename: '[name].[chunkhash].js'
}

Note

A hash in the filename changes every compilation when the file content is changed. That means, files with hashes in names can not be included manually in the HTML file (index.html). HtmlWebpackPlugin (https://github.com/jantimon/html-webpack-plugin) helps us to include generated bundles with <script> or <link> tags in the HTML. The seed project makes use of this plugin.

Loaders and plugins

Webpack only understands JavaScript files as modules. Every other file (.css, .scss, .json, .jpg, and many more) can be transformed into a module while importing. Loaders transform these files and add them to the dependency graph. Loader configuration should be done under module.rules. There are two main options in the loader configuration:

  • The test property with a regular expression for testing files the loader should be applied to
  • loader or use property with the concrete loader name
module: {
  rules: [
    {test: /.json$/, loader: 'json-loader'},
    {test: /.html$/, loader: 'raw-loader'},
    ...
  ]
}

Note that loaders should be registered in package.json so that they can be installed under node_modules. Webpack homepage has a good overview of some popular loaders (https://webpack.js.org/loaders). For TypeScript files, it is recommended to use the following sequence of loaders in the development mode:

{test: /.ts$/, loaders: ['awesome-typescript-loader', 'angular2-template-loader']}

Multiple loaders are applied from right to left. The angular2-template-loader searches for templateUrl and styleUrls declarations and inlines HTML and styles inside of the @Component decorator. The awesome-typescript-loader is mostly for speeding up the compilation process. For AOT compilation (production mode), another configuration is required:

{test: /.ts$/, loader: '@ngtools/webpack'}

Webpack has not only loaders, but also plugins which take responsibility for custom tasks beyond loaders. Custom tasks could be the compression of assets, extraction of CSS into a separate file, generation of a source map, definition of constants configured at compile time, and so on. One of the helpful plugins used in the seed project is the CommonsChunkPlugin. It generates chunks of common modules shared between entry points and splits them into separate bundles. This results in page speed optimizations as the browser can quickly serve the shared code from cache. In the seed project, we moved Webpack's runtime code to a separate manifest file in order to support long-term caching. This will avoid hash recreation for vendor files when only application files are changed:

plugins: [
  new CommonsChunkPlugin({
    name: 'manifest',
    minChunks: Infinity
  }),
  ...
]

As you can see, configuration of plugins is done in the plugins option. There are two plugins for production configuration yet to be mentioned here. The AotPlugin enables AOT compilation. It needs to know the path of tsconfig.json and the path with module class used for bootstrapping:

new AotPlugin({
  tsConfigPath: './tsconfig.json',
  entryModule: path.resolve(__dirname, '..') + 
               '/src/app/app.module#AppModule'
})

UglifyJsPlugin is used for code minification:

new UglifyJsPlugin({
  compress: {
    dead_code: true,
    unused: true,
    warnings: false,
    screw_ie8: true
  },
  ...
})

Adding PrimeNG, CSS, and SASS

It's time to finish the setup. First, make sure that you have PrimeNG and FontAwesome dependencies in the package.json file. For example:

"primeng": "~2.0.2",
"font-awesome": "~4.7.0"

Second, bundle all CSS files into one file. This task is accomplished by ExtractTextPlugin, which is needed for loaders and plugin configuration:

{test: /.css$/, loader: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: "css-loader"
  })
},
{test: /.scss/, loader: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: ['css-loader', 'sass-loader']
  }),
  exclude: /^_.*.scss/
}
...
plugins: [
  new ExtractTextPlugin({
    filename: "[name].css"  // file name of the bundle
  }),
  ...
]

Note

For production, you should set the filename to "[name].[chunkhash].css". The bundled CSS file gets automatically included into index.html by HtmlWebpackPlugin.

We prefer not to use styleUrls in the components. The seed project imports a CSS und SASS files in one place--inside of main.scss file located under src/assets/css:

// vendor files (imported from node_modules)
@import "~primeng/resources/themes/bootstrap/theme.css";
@import "~primeng/resources/primeng.min.css";
@import "~font-awesome/css/font-awesome.min.css";

// base project stuff (common settings)
@import "global";

// specific styles for components
@import "../../app/app.component";
@import "../../app/section/section.component";

Note that the tilde ~ points to the node_modules. More precisely the Sass preprocessor interprets it as the node_modules folder. Sass is explained in Chapter 2, Theming Concepts and Layouts. The main.scss file should be imported in the entry points main.jit.ts and main.aot.ts:

import './assets/css/main.scss';

Webpack takes care of the rest. There are more goodies from Webpack--a development server with live reloading webpack-dev-server (https://webpack.js.org/configuration/dev-server). It detects changes made to files and recompiles automatically. You can start it with npm start or npm run start:prod. These commands represent npm scripts:

"start": webpack-dev-server --config config/webpack.dev.js --inline --open
"start:prod": webpack-dev-server --config config/webpack.prod.js --inline --open

Note

When running webpack-dev-server, the compiled output is served from memory. This means, the application being served is not located on disk in the dist folder.

That's all. More configuration options for unit and end-to-end testing will be added in Chapter 10, Creating Robust Applications.

 

Setting up PrimeNG project with Angular CLI


Angular CLI (https://cli.angular.io) is a comfortable tool to create, run, and test Angular applications out of the box. It generates the code in no time. We will describe some useful commands and show you how to integrate PrimeNG with Angular CLI. First, the tool should be installed globally:

npm install -g @angular/cli

When it is installed, every command can be executed in the console with prepended ng. For instance, to create a new project, run ng new [projectname] [options]. Let's create one. Navigate to a directory that will be the parent directory of your project and run the following command:

ng new primeng-angularcli-setup --style=scss

This command will create an Angular 4 project within the folder primeng-angularcli-setup. The option --style sets a CSS preprocessor. Here, we want to use SASS files and need a Sass preprocessor. The preprocessor compiles SASS files whenever we make changes. You don't need to set a preprocessor if you only have CSS files.

Note

The complete preconfigured seed project with PrimeNG and Angular CLI is available on GitHub athttps://github.com/ova2/angular-development-with-primeng/tree/master/chapter1/primeng-angularcli-setup.

The created project has the following top directories and files:

Directory/file

Short description

e2e

Folder with e2e tests (.e2e-spec.ts files) and page objects (.po.ts files).

src

Source code folder where the application code should be written.

.angular-cli.json

Set up configuration file. PrimeNG dependencies can be listed here.

karma.conf.js

Karma configuration file for unit testing.

protractor.conf.js

Protractor configuration file for e2e testing.

package.json

Standard file for package management of npm-based projects.

tsconfig.json

Settings for TypeScript compiler.

tslint.json

Settings for TSLint.

You can now start the application by typing the following:

ng serve

This command will run a local server at http://localhost:4200 by default. You will see the text app works! in the browser. The ng serve command uses webpack-dev-server internally. The server is running in the watch mode. It refreshes the page automatically when any changes occur. There are a lot of configuration options. You can, for example, set a custom port by the --port option. Refer to the official documentation for more details at https://github.com/angular/angular-cli/wiki. To compile the application into an output directory, run the following command:

ng build

The build artifacts will be stored in the dist directory.

Note

The --prod option in ng build or ng serve will minify the files and remove unused (dead) code. The --aot option will use AOT compilation and produce even more smaller and optimized artifacts.

To run unit and e2e tests, execute ng test and ng e2e commands, respectively.

Generating scaffolding

Angular CLI allows us to generate components, services, directives, routes, pipes, and many more with ng generate. Here is how you would generate a component:

ng generate component path/name

For example, if we run the following command:

ng generate component shared/message

Four files will be generated and one updated. The produced output will be:

installing component
  create src/app/shared/message/message.component.scss
  create src/app/shared/message/message.component.html
  create src/app/shared/message/message.component.spec.ts
  create src/app/shared/message/message.component.ts
  update src/app/app.module.ts

The new component is registered in app.module.ts automatically. The generation of other scaffoldings is identical. For example, to generate a service, run this command:

ng generate service path/name

There are plenty of useful options. You can set, for example, --spec=false to skip test file generation.

Adding PrimeNG dependencies

Integrating PrimeNG with Angular CLI is straightforward. First, install and save the dependencies:

npm install primeng --save
npm install font-awesome --save

Second, edit the .angular-cli.json file and add three more CSS files to the styles section. These are the same files as in the SystemJS- and Webpack-based setups:

"styles": [
  "styles.css",
  "../node_modules/primeng/resources/themes/bootstrap/theme.css",
  "../node_modules/primeng/resources/primeng.min.css",
  "../node_modules/font-awesome/css/font-awesome.min.css"
]

Now, you can import desired PrimeNG modules. Refer to the Running PrimeNG with SystemJS section to see how to import PrimeNG modules. In the seed project on GitHub, we have imported the MessagesModule and put some demo code into message.component.html and message.component.ts.

 

Summary


After reading this chapter, you got an overview of TypeScript and Angular concepts you need to understand for the upcoming chapters. TypeScript introduces types which help to recognize errors at development time. There are primitive types, types known from object-oriented programming languages, custom types, and so on. By default, TypeScript compiler always emits an JavaScript code, even in the presence of type errors. In this way, you can quickly migrate any existing JavaScript code to TypeScript just by renaming .js file to .ts without having to fix all compilation errors at once.

A typically Angular application is written in TypeScript. Angular provides a component-based approach which decouples your UI logic from the application (business) logic. It implements a powerful dependency injection system that makes reusing services a breeze. Dependency injection also increases the code testability because you can easily mock your business logic. An Angular application consists of hierarchical components, which communicate with each other in various ways such as @Input, @Output properties, shared services, local variables, and so on.

Angular is a modular framework. Module classes annotated with @NgModule provide a great way to keep the code clean and organized. Angular is flexible--lifecycle hooks allow us to perform custom logic at several stages in the in the component's life. Last but not least, it is fast due to smart change detection algorithm. Angular doesn't offer any rich UI components. It is just a platform for developing single page applications. You need a third-party library to create rich UI interfaces.

PrimeNG is a collection of such rich UI components for Angular 2+. In comparison with competitors, PrimeNG was created for enterprise applications and provides 80+ components. Adding PrimeNG dependencies is easy done. You only need to add PrimeNG and FontAwesome dependencies to the package.json file, and three CSS files: primeng.min.css, font-awesome.min.css, and theme.css for any theme you like. The next chapter will cover the theming concept in detail.

An Angular and PrimeNG application consists of ES6 (ECMAScript 2015) modules. Modules can be exported and imported. All modules in an application build a dependency graph. Therefore, you need a specific tool to resolve such modules starting at some entry point(s) and to output a bundle. There are some tools doing this and other tasks such as loading modules on demand, and similar.

In this chapter, SystemJS and Webpack loaders were discussed. SystemJS is only recommended for demo applications for the purpose of learning. Webpack-based builds are more sophisticated. Webpack has a combination of loaders for every file type and plugins. Plugins include useful behaviors into the Webpack build process, for example, creating common chunks, minification of web resources, copying files and directories, creating SVG sprites, and more. To quickly start the development in TypeScript and Angular, generate your projects with Angular CLI. This is a scaffolding tool, which makes it easy to create an application that works out of the box.

About the Authors

  • Sudheer Jonna

    Sudheer Jonna was born in Nellore, India. Currently, he works as a senior software engineer in Singapore. He completed his master's degree in computer applications from JNTU University. In the past few years, he has worked on building various Java and JavaScript web applications based on JSF, PrimeFaces, Struts, Spring, REST, jQuery, Angular, React, and VueJS. He has also worked on many JavaEE and API development technologies, such as JPA (Hibernate), EJB, GraphQL, and Sequelize.

    He is the founder of GeekoTek company and is a longtime JSF and Prime products expert. He is also a project member of the PrimeFaces, PrimeFaces Extensions, and PrimeNG open source projects. He is the author of three other Packt books, titled Learning PrimeFaces Extension Development, PrimeFaces BluePrints, and PrimeFaces Theme Development. He has worked as a technical reviewer on a few books. He is a regular speaker, trainer, reviewer, blogger, organizer, and active member of public forums. He is interested in R&D on the latest technologies.

    He shares his knowledge through his personal website. You can follow him on Twitter with the handle @SudheerJonna.

    Browse publications by this author
  • Oleg Varaksin

    Oleg Varaksin is a senior software engineer living in the Black Forest, Germany. He is a graduate computer scientist who studied informatics at Russian and German universities. His main occupation and "daily bread" in the last few years has consisted of building various Java-and JavaScript-based web applications based on JSF, PrimeFaces, Spring, REST, JavaScript, jQuery, Angular, and HTML5. Currently, he is working at Swiss Federal Railways on a new ticket webshop.

    Oleg is an experienced and passionate web developer and has been working with the Prime UI libraries from the beginning. He is also a well-known member of the PrimeFaces community, creator of the PrimeFaces Extensions project, and the author of the PrimeFaces Cookbook. Oleg loves JavaScript, new ECMAScript standards, TypeScript, Angular, PrimeNG, RxJS, and Redux architecture. He has a deep understanding of web usability and accessibility.

    Oleg normally shares the knowledge he has acquired on his blog. His Twitter handle is @OlegVaraksin.

    Browse publications by this author

Latest Reviews

(12 reviews total)
Purchase was pretty straight forward and hassle free.
Easy to order and download, reasonable pricing
This book is for an older version of PrimeNG. I was looking for specific information regarding the more recent "Turbo Table" and there was just the older "Table" reference. The first couple of chapters have some good information on how to get setup with their components, but the chapters on the components themselves are light on reference information and explanations. They mostly refer you to their demo app, but do not offer examples on all of the different options available and how to use them. I was still left with a lot of questions after looking through the chapter on the Table component - I guess I will have to use their forums for the information I am looking for.

Recommended For You

Book Title
Unlock this full book FREE 10 day trial
Start Free Trial