Angular Fundamentals

From Training Material
Jump to navigation Jump to search


title
Angular Fundamentals Training Course
author
Lukasz Sokolowski


Angular Fundamentals

Angular Fundamentals Training Materials

Introduction

  • Angular is a completely new framework
    • Very few AngularJS core concepts are still in Angular
  • AngularJS got old
    • Predates much of what has developed around mobile technology
    • ES2015 - no serious support for new version of JavaScript
    • Angular1 was not designed for use with new web techis (like Web Components, etc)
  • Have been engineered to
    • Maximize performance
    • Emphasize mobile-first development

Intro Con't

  • Angular
    • Has a mobile-first design
    • Engineered for a small footprint
      • Data that flows from the server to the browser is minimized as much as possible
    • Has been broken into a collection of modules
      • Only the code needed to run the application is loaded
    • Syntax has been simplified and made more coherent
      • Easier to learn
      • Better support for tooling and automation

Intro Con't 1

To accomplish these goals

  • Web Components
    • Apps are built out of reusable building blocks hat encapsulate their internal logic
  • ES2015 provides
    • Classes and a solid system for loading Angular modules
  • TypeScript
    • Brings types that enable a simpler and more robust syntax
    • Abillity to build large-scale applications

Intro Con't 2

  • Angular2, along with Typescript
    • Allows to write code in both ES5 and ES2015
    • It's own team uses TypeScript to build the framework
    • Code written in TypeScript is far terser than the alternatives
    • IDEs can provide better IntelliSense and code completion support

Bootstrap Angular app

  • Loading the modules
  • App initialization process (bootstrapping)

Loading the modules

ES2015 introduced a new syntax for module loading

  • It allows for modules to be loaded selectively and asynchronously
  • TypeScript supports the ES2015 module loading syntax
  • SystemJS allows to add module loading to applications that run ES5
  • Combination of the two above gives
    • TypeScript will transpile (compile) the application's components to ES5
    • SystemJS will load them as ES5 modules
    • Each of TypeScript files will be compiled to a SystemJS module
    • SystemJS will then load all the related dependencies and when requested the module itself
  • Example
<script src="https://unpkg.com/systemjs@0.19.27/dist/system.js"></script>
<script src="systemjs.config.js"></script>

Loading the modules con't


Bootstrapping

Bootstrapping process:

  • Angular scans component definition
    • Imports the modules identified in the import statement of the component along with its related dependencies
  • It compiles the HTML view
    • Starts from where the my-app tag is declared.
    • Traverses the template
      • Looks for all interpolations
      • Sets up the binding between the view and the class
  • Post compilation links the view and the component class
    • changes are synced across the model and viewed in real time as we interact with the app
  • Example

Components in Angular

  • Component Pattern instead of MVC
  • Constructs
    • Interpolation, expressions, data binding syntax
  • Change detection

Component pattern

  • In general, involves combining smaller, discrete building blocks into larger finished products
  • In software development, logical units that can be combined into larger applications
    • Tend to have internal logic and properties that are shielded or hidden from the larger application
    • The larger application consumes these building-blocks through specific gateways (interfaces)
      • That expose only what is needed to make use of the component
    • Component's internal logic can be modified without affecting the larger application
      • As long as the interfaces are not changed

Component pattern Con't

In web applications

  • No messes of spaghetti code in applications
  • Reasoning about specific parts of the application in isolation from the other parts
  • Maintenance costs are less
    • Each component's internal logic can be managed separately
    • No affecting the other parts of the application
  • Self-describing components
    • Makes the application easier to understand at a higher level of abstraction

Constructs

  • Usually they live in the HTML template that is inside the @Component decorator
  • They are linking the view HTML and component code
  • Similar to standard HTML with new symbols:
    [ ] property bindings
    ( ) event bindings
    {{ }} interpolation
    

Interpolation

  • Replaces the content of the markup with the value of the expression (howManyTries)
    • The value of the component property will be displayed as the contents inside the interpolation tags
  • Declaration syntax:{{expression}}
    • Similar to a JavaScript expression
    • Is always evaluated in the context of the component
    • Interpolation tags read the value of the property directly from the component without any need for additional code
  • Changes made to component properties are automatically synchronized with the view
    • Easier debugging when we need to see the state of the model
    • No need to put a breakpoint in code for the value of a component property or method call
  • Example
<p class="text-info">No of tries :
  <span class="badge">{{howManyTries}}</span>
</p>

Expressions

  • Pieces of plain JavaScript code
    • Evaluated in the context of the component instance
    • Associated with the template instance in which they are used
  • Should be kept simple (readability)
    • When become complex, should be moved into a method in the component
  • New meanings for operators | and ?.

Expressions Con't

Restrictions

  • Assignment is prohibited, except in event bindings
  • The new operator is prohibited
  • ++, -- and bitwise operators are not supported
  • No referring to anything in the global namespace
  • No referring to a window or document
  • No calling console.log
Safe navigation operator

?. checks for null values in lengthy property paths

  • Example {{ invoice?.item }}
    • If null value (invoice), it stops processing the path
    • But lets the application continue running
  • Good for data loaded asynchronously
    • Because it might not be immediately available to the view
  • Prevents the application from crashing
    • Loads the data when it is available

Binding data

Binding data in Angular

  • Property binding
  • Event binding
  • Example
Property binding

[ ]

  • Binding works by linking the value of the property in component class to the value of some element in the view
  • The binding is dynamic
    • As the value of the property changes
    • The value of the element will be synchronized to the same value
    • No need to write any code to do that
<input type="text" [value]="findJewel" (input)="findJewel = $event.target.value" />
Event binding

( )

  • Event of element is bound to an expression
  • The expression sets the property in component class to $event.target.value
    • The value being entered by the user
  • Angular sets up an event handler for the event that we are binding to
<button (click)="verifyTheTry()" class="btn btn-primary btn-sm">Verify</button>
<button (click)="initializeGame()" class="btn btn-warning btn-sm">Restart</button>

Change detection

  • State maintenance
  • Change detection
State maintenance
  • Angular apps are dynamic
  • Dynamic values are kept up-to-date as the data in an application gets updated
  • Component is the container for the state
    • all the properties in the component instance and their values are available for the template instance that is referenced in the component
    • we can use these values directly in expressions and bindings in the template without having to write any plumbing code to wire them up
Change detection

Angular keeps track of changes in the component as it runs

  • Is reacting to events in the application
  • Uses change detectors
    • Go through every component to determine whether anything has changed that affects the view
    • Event triggers the change detection cycle
      • Identifies that the property that is being used in the view has changed
    • Updates the element in the view that is bound to property with the new value of it
Change detection Con't
  • Multi-step process where Angular
    • First updates the components and domain objects in response to an event
    • Then runs change detection
    • Lastly re-renders elements in the view that have changed
  • This is done on every browser event
    • Also other asynchronous events (XHR requests, timers, etc)
  • one-way data binding
    • Change detection in Angular is reactive and one way
    • Makes just one pass through the change detection graph
    • It vastly improves the performance of Angular

Web components

Standards for web browsers

  • Custom elements
  • Shadow DOM
  • Templates
  • HTML imports

Custom elements

Enable new types of element to be created

  • Other than the standard HTML tag names (div, p, etc)
  • Custom tags provides a location on the screen that can be reserved for binding a component
  • Separation of component from the rest of the page
    • Makes it possible to become truly self-contained

Shadow DOM

Provides a hidden area on the page for scripts, CSS, and HTML

  • Markup and styles in it
    • Will not affect the rest of the page
    • Will not be affected by the markup and styles on other parts of the page
  • Component can use it to render its display

Templates

Repeatable chunks of HTML

  • Have tags that can be replaced with dynamic content at runtime using JavaScript
  • Web Components standardize templating
    • Provide direct support for it in the browser
  • Can be used to make the HTML and CSS inside the Shadow DOM used by the component dynamic

HTML imports

Provide a way to load resources (HTML, CSS, JS) in a single bundle

  • Angular does not use HTML imports
    • It relies on JavaScript module loading instead

Module loading

Module provides a way for JavaScript files to be encapsulated

  • Do not pollute the global namespace
  • Can interact with other modules in a controlled manner
  • Have to be loaded into application for execution
    • ES2015
      • export (or default export)
      • import to consume them elsewhere in application
    • SystemJS, etc

Transpilation

Convertion from ES2015 to ES5

  • Similar to compilation
    • Transfers one source code into the other
  • Transpilers
    • Traceur, Babel, TypeScript

Angular modules

Organise components into complete apps

  • Combine the components into reusable groups of functionality
    • Can be exported and imported throughout the application
    • Modules can do: authentication, common utilities, external service calls, etc
  • lazy loading
    • Can be loaded on demand
  • NgModule
    • Conveniently specify the components that make up a module
  • Every Angular app must have at least one root module

Angular modules con't

An Angular module defines:

  • The components/directives/pipes it owns
  • The components/directives/pipes it makes public for other modules to consume
  • Other modules that it depends on
  • Services that the module wants to make available application wide

Angular CLI, etc

  • Tools
  • Resources

Tools

  • Browser's developer console - a lot of errors with code can be detected just by looking at it
    • put in breakpoints, add a watch, etc
  • Angular Devtools (https://angular.io/devtools)
    • Helps visualise the application through component trees, and visual debugging tools
    • Gives insight into application structure, change detection and performance characteristics
    • New feature, similar to Augury
  • Augury (https://augury.angular.io/)
    • Depricated
  • IDE extensions: Visual Studio Code, JetBrains WebStorm, Sublime Text, Atom, etc
  • JSFiddle and Plunker
  • Angular CLI - from the initial project setup all the way to the final deployment

Resources

Introduction to TypeScript

Created by Microsoft as a superset of JavaScript

  • Contains the features of ES2015 (classes, module loading, etc)
  • Provides more
    • Types
    • Decorators
  • For more, reference Typescript

Types, Functions, Lambdas

Types

  • Allow to mark variables, properties, and parameters in classes as
    • numbers, strings, Booleans, or various structures (arrays, objects, etc)
  • Thanks to that we can perform type checking at design time
    • to make sure that the proper types are being used in the application

Classes, Interfaces, Decorators, Modules

Decorators are simple annotations

  • Can be added to classes using the @ symbol along with a function
  • Provide instructions (metadata) for the use of classes
  • Allow us to identify classes as Angular components
  • Can specify
    • Modules to be used with a component
    • How to implement
      • Various bindings and directives
      • Attaching an HTML view to the component

Component Based Development

To develop applications in Angular

  1. Create components
  2. Bundle them into modules
  3. Bootstrap the application

Develop custom components

Building user interface (UI)

  • Don't follow the usual top-down practice
  • Instead with component-based design
    • We start by looking at the UI and expected behavior
    • Then we encapsulate all of this into a building block (component)
    • This component is then hosted on our page
    • Within the component
      • We separate the UI into a view
      • We separate the behavior into a class
        • Provide the appropriate properties and methods needed to support the behavior

Example 1

Build game "Find the jewel".
The objective of the game is to find a random computer-chosen jewel in as few tries as possible.

Approach:
* First think about the functionality we want to offer
* Second think about the data and behavior that can support the functionality
* Third think about how to build a user interface for it

Test it with 'live-server' (if not yet installed in the training machine, run 'sudo npm i -g live-server')
* in command line go to application's root folder and execute the command 'live-server'
  1. Designing the component
    • Detailing the features that we want the app to support
      • Choosing random jewel (origJewel)
      • Providing input for a user to find the jewel (findJewel)
      • Tracking the number of tries already made (howManyTries)
      • Giving the user hints to improve their try based on their input (hinting)
      • Giving a success message if the user found the proper jewel (hinting)
    • Determining what we need to display to the user and what data we need to track
      • Names in round brackets are the properties that will support those features
      • We need to include these properties in our component
  2. Make app folders ~/angu/examples/findthejewel
    1. Prepare main html file index.html
      <!DOCTYPE html>
      <html>
        <head>
          <title>Find the Jewel</title>
          <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
            <!-- Provides ES2015 features for older browsers -->
          <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
            <!-- Adds decorator support -->
          <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
            <!-- Manages change detection -->
          <script src="https://unpkg.com/zone.js@0.6.23?main=browser"></script>
            <!-- Current version of TypeScript -->
          <script src="https://unpkg.com/typescript@2.0/lib/typescript.js"></script>
            <!-- Loads modules -->
          <script src="https://unpkg.com/systemjs@0.19.27/dist/system.js"></script>
            <!--
              Provides instructions to SystemJS as to which modules it should load
              and directs it to dynamically transpile TypeScript files to ES5 at runtime
            -->
          <script src="systemjs.config.js"></script>
          <script>
            // Calls SystemJS to import component directory (app)
            // Implements the module loading for our app
            System.import('app').catch(function(err){ console.error(err); });
          </script>
        </head>
        <body>
            <!-- Tag "custom element", instructs Angular where to inject the component -->
          <my-app>Our own main element for our app</my-app>
        </body>
      </html>
      
    2. Create components folder and file ~/angu/examples/findthejewel/app/find-the-jewel.component.ts
      /*
      Import statement tells what modules will be loaded and used in component.
      We load only what we need.
      No path or directory, but identifier prefixed with '@angular'.
      */
      import { Component }from '@angular/core';
      
      /*
      Decorator for component, placed directly above the class definition, identified by '@'.
      Property called 'selector', tells Angular to inject this component into '<my-app>' tag.
      Property called 'template', identifies the HTML markup for component.
      Back ticks render the template string over multiple lines.
      Also 'templateUrl' property can point to a separate file.
      */
      @Component({
        selector: 'my-app',
        template: `
          <div class="container">
            <h2>Find the Jewel</h2>
            <p class="well lead">Find which Jewel computer likes more than the others.</p>
            <label>Your Try: </label>
            <input type="text" [value]="findJewel" (input)="findJewel = $event.target.value" />
            <button (click)="verifyTheTry()" class="btn btn-primary btn-sm">Verify</button>
            <button (click)="initGame()" class="btn btn-warning btn-sm">Restart</button>
            <div>
      <!--
      The asterisk * in front of 'structural directive' ngIf is a simplified syntax.
      Angular expands into an HTML5 <template> tag.
      -->
              <p *ngIf="hinting===false" class="alert alert-warning">Nope, not that one.. )-:</p>
              <p *ngIf="hinting===true" class="alert alert-success">Yes! That's it.</p>
            </div>
            <p class="text-info">No of tries :
              <span class="badge">{{howManyTries}}</span>
            </p>
          </div>
          `
      })
      /*
      The class run our component.
      Properties reflect values from the design part, typed in TypeScript.
      Methods contain the operations that component will support.
      'constructor()' will run when an instance of component is first created.
      'verifyTheTry()' updates the 'hinting' and 'howManyTries' properties, called from 'view'.
      */
      export class FindTheJewelComponent {
        hinting: boolean;
        howManyTries: number;
        origJewel: string;
        findJewel: string;
        jewels: string[];
      
        constructor() {
          this.initGame();
        }
        initGame() {
          this.jewels = ['ruby','diamond','agate','amber','aquamarine','amethyst','opal','tourmaline', 'emerald','onyx','pearl','sapphire'];
          this.howManyTries = 0;
          
          // EXERCISE 1:
          // - What if we add new jewel to jewels? fix it!
          //
          // EXERCISE 2:
          // - Move the component's view template into a separated file and link it to the component
      
          let jewelIndex = Math.floor((Math.random() * 12) + 1);
          this.origJewel = this.jewels[jewelIndex];
            // console.log(this.origJewel);
          this.findJewel = null;
          this.hinting = null;
        }
        verifyTheTry() {
          this.hinting = (this.origJewel === this.findJewel);
          this.howManyTries = this.howManyTries + 1;
        }
      }
      
    3. Do module file ~/angu/examples/findthejewel/app/app.module.ts
      /*
      Every Angular component must be contained within an Angular module.
      'root module' - minimum one Angular module file has to be in the root of application.
      
      @ngModule - decorator to configure the module in our application
      - 'imports' property
      --- functionality needed to run our application in a browser
      - 'declarations' property
      --- we provide an array of the components that will be used in our application
      - 'bootstrap' property
      --- indicates the first component that will be loaded when our application starts up
      
      */
      import { NgModule }      from '@angular/core';
      import { BrowserModule } from '@angular/platform-browser';
      
      import { FindTheJewelComponent }   from './find-the-jewel.component';
      
      @NgModule({
          imports:      [ BrowserModule ],
          declarations: [ FindTheJewelComponent ],
          bootstrap:    [ FindTheJewelComponent ]
      })
      
      export class AppModule { }
      
    4. Bootstrap file ~/angu/examples/findthejewel/app/main.ts
      /*
      Code that bootstraps the component.
      Creates an instance of the component.
      
      'bootstrapModule' method creates a new instance of AppModule component
       - 'AppModule' component initializes 'FindTheJewelComponent'
       - It does that by calling the component's constructor method and setting the starting values
      */
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
      import { AppModule } from './app.module';
      
      const platform = platformBrowserDynamic();
      platform.bootstrapModule(AppModule);
      
    5. Config file ~/angu/examples/findthejewel/systemjs.config.js
      /*
       * Sets the configuration for SystemJS
       */
      System.config({
      
            // Mappings that indicate where SystemJS should look for the files
          map : {
              'app': 'app',    // our own application source
                  // RxJS is a library for reactive programming using Observables, to make it easier to compose asynchronous or callback-based code
              'rxjs': 'https://unpkg.com/rxjs@5.0.0-beta.12',
                  // several Angular modules from CDN from NodeJS package manager (NPM scoped packages)
              '@angular/common': 'https://unpkg.com/@angular/common@2.0.0',
              '@angular/compiler': 'https://unpkg.com/@angular/compiler@2.0.0',
              '@angular/core': 'https://unpkg.com/@angular/core@2.0.0',
              '@angular/platform-browser': 'https://unpkg.com/@angular/platform-browser@2.0.0',
              '@angular/platform-browser-dynamic': 'https://unpkg.com/@angular/platform-browser-dynamic@2.0.0'
          },
      /*
      Packages that will be imported and their main entry points.
      
      We select just the packages that we need
      -- helps to minimise the size of the download and provides better performance
      */
          packages:{
              'app':  { main: 'main.ts',  defaultExtension: 'ts' },
              '@angular/common': { main: 'bundles/common.umd.js', defaultExtension: 'js' },
              '@angular/compiler': { main: 'bundles/compiler.umd.js', defaultExtension: 'js' },
              '@angular/core': { main: 'bundles/core.umd.js', defaultExtension: 'js' },
              '@angular/platform-browser': { main: 'bundles/platform-browser.umd.js', defaultExtension: 'js' },
              '@angular/platform-browser-dynamic': { main: 'bundles/platform-browser-dynamic.umd.js', defaultExtension: 'js' },
          },
          // WE SHOULD NOT TRANSPILE CODE IN THE BROWSER ON PRODUCTION SITES! IT'S JUST SIMPLIFICATION FOR THE SAKE OF THE EXAMPLE
          transpiler: 'typescript',
          typescriptOptions: {
              emitDecoratorMetadata: true
          }
      });
      

Component Tree

Example

  • ngtraining
    • popup-window

Exercise

1. Rewrite find-the-jewel app into 'angular-cli' starter
2. Allow to add from UI a new jewel to the list
3. Make the wrongly guessed gem disappear from the list of available gems

Advanced Components

Examples

  • ngtraining

Exercises

  1. Separate addition of jewels into a new component ( use @Input() )
  2. Separate showing the list of jewels into a new component ( use @Output() )

Directives and Pipes

Angular directives

  • Custom tag directives - component directives - components
    • Come with their own view
  • Property and event binding - [ ], ( )
  • Attribute directives
  • Structural directives
  • Directives (custom or platform), like components, have to be registered on a module before they can be used

Attributes directives and Structural directives

  • Do not have their own view
  • Binding data
    • Attribute binding
      • For HTML attributes that do not have a backing DOM property (colspan, aria, etc)
      • Syntax [attr.attribute-name]="expression"
    • Class binding
      • Set and remove a specific class based on the component state
      • Syntax [class.class-name]="expression"
    • Style binding
      • Set inline styles based on the component state
      • Syntax [style.style-name]="expression"

Attributes directives

  • Enhance the appearance and/or behavior of existing elements/components
  • Examples ngStyle, ngClass, ngValue, ngModel, ngSelectOptions, ngControl, ngFormControl, etc
  • ngStyle
    <div [ngStyle]="{
    'width':componentWidth,
    'height':componentHeight,
    'font-size': 'larger',
    'font-weight': ifRequired ? 'bold': 'normal' }"></div>
    
  • ngClass<div [ngClass]="{'required':inputRequired, 'email':whenEmail}"></div>
Exercises
1. Change font size to 48px for text in the container, which shows hints.
2. Make input field required. Use property in component with default value 'true'.
3. Custom directive. Use highlight directive example from 'ngtraining' - for ftj main title ("Find The Jewel").

Structural directives

  • Looks similar to data binding
  • Allow to manipulate the structure of DOM elements
  • Example ngIf
    • Removes or adds DOM elements based on the result of an expression that is assigned to it
      <p *ngIf="hinting===false" class="alert alert-warning">Nope, not that one.. )-:</p>
      <p *ngIf="hinting===true" class="alert alert-success">Yes! That's it.</p>
      
  • Example ngFor
    <ul>
      <li *ngFor="let elementOnTheList of theList"> {{ elementOnTheList }} </li>
    </ul>
    
Exercises
1. Show all jewels in a nice lavender bootstrap grid, as the last element in the "no body, no crime"  (=
2. Make better hinting (same starting letter, same ending letter) 
3. Move all the hinting logic into your own custom directive

Pipes

  • Provide a mechanism to format view content
    • Format the value of an expression displayed in the view
  • Predefined pipes - date, currency, lowercase, uppercase, json, etc
  • Syntax (in a view): {{expression | pipeName:inputParam1}}
  • Can be chained
    • Output from one pipe can serve as the input to another pipe
  • Examples
    {{ someString | slice:0:10 }} //renders first 10 characters
    {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
    {{ 22.15 | currency:"USD" }} <!-Renders USD 22.15 -->
    {{ fullName | slice:0:10 | uppercase }}
    

Exercise

1. Change jewel names in your grid to uppercase characters
2. Custom pipe: show the number of characters in jewel's name, like this "EMERALD 7"
(example in ngtraining)

Building blocks

An Angular application consists of the following parts:

  • Modules
  • Components
  • Templates
  • Metadata
  • Data binding
  • Directives
  • Services
  • Dependency injection

Immutable.js

  • Immutable objects/collections are objects that cannot be changed once created
  • Any property change results in a new object being created
  • Advantages
    • much simpler application development
    • no defensive copying
    • enabling advanced memoization and change detection techniques with simple logic

Observables

  • Core Reactive Extensions features
  • Enhance the application so that users can
    • edit existing objects in the model
    • create new ones
  • Distribute updates throughout an application
    • immediate updates to the application state
  • Observer - the mechanism by which updates are created
  • Observable - represents an observable sequence of events
    • An object such as a component can subscribe to an Observable
      • will receive a notification each time an event occurs
      • allowing it to respond only when the event has been observed
      • instead of every time there is a change anywhere in the application
  • Subject - class, which implements both the Observer and Observable functionality
    • Easy to create a service that allows events to be produced and consumed with a single object
  • Examples
    • ngtraining

Dependency Injection

Allows components and other building blocks

  • To receive services by declaring dependencies on them using constructor parameters
  • To share objects throughout an application
  • To avoid the need to distribute shared objects manually

Examples

  • ngtraining

Exercise

  1. Create a custom service, which will allow to
    • get all the jewels list
    • add new jewel
    • remove jewel

Routing

Routing uses the browser’s URL to manage the content displayed to the user

  • Allows the structure of an application to be kept apart from the components and templates in the application
  • Changes to the structure of the application are made in the routing configuration
    • moved from individual components and directives
  • The routing configuration
    • Set of fragments
      • Match the browser’s URL
      • Select a component whose template is displayed as the content of an HTML element called router-outlet

Routes in Angular

  • Navigation
  • Wildcards and redirection
  • Child routes
  • Guarding routes
    • Delaying Navigation with a Resolver
    • Preventing Navigation with Guards
  • Lazy loading - loading feature modules dynamically
  • Targeting named outlets
  • Examples
    • ngtraining
    • exampleApp

Exercise

  1. Routes with children
    • Route for adding new gems
    • Route for home page
    • Route for 404 page not found
    • Route for listing all jewels
    • Route for finding jewel
    • Route for showing one jewel (as a child component of listing-jewels)
  2. Route for module with gems
  3. Improve gems module with lazy loading

Forms

Angular provides set of constructs that make standard form-based operations easier

  • Allowing user inputs
    • two-way bindings between the form input elements and the underlying model
      • hence avoiding any boilerplate code that we may have to write for model input synchronization
  • Validating those inputs against business rules
  • Submitting the data to the back-end server

Template Driven Forms

Template-driven forms develops a form within an HTML template

  • handling most of the logic for the form-inputs, data validation, saving, and updating-in form directives placed within the template
  • very little form-related code is required in the component class that is associated with the form's template
  • make heavy use of the ngModel form directive
    • It provides two-way data binding for form controls
    • It allows us to write much less boilerplate code to implement a form
    • It also helps us to manage the state of the form
      • whether the form controls have changed and whether these changes have been saved
    • Ability to easily construct messages that display if the validation requirements for a form control have not been met
      • examples - a required field not provided, e-mail not in the right format, etc
  • Exercise
    • Make your ftj engine component's form template-driven and validate it (required)

Form Builder

Model-driven forms

  • start with a model that is constructed in a component class
  • form builder API creates a form in code and associate it with a model
  • to create form controls dynamically based on data we are retrieving from the server
  • for complicated validation, it is often easier to handle it in code
    • keep complicated logic out of the HTML template, making the template syntax simpler
  • make unit-testing the form possible
  • New form directives - FormGroup, FormControl, and FormArray
    • allow the form object that is constructed in code to be tied directly to the HTML markup in the template
    • cleaner and less cluttered template with more focus on the code that drives the form

Examples

Exercises

  • Improve ngtraining example with custom zip validator
  • Provide custom validation for ftj add-new-jewel component
    • ban adding 'salt' and 'sugar'
      • show error message to user "Too much salt and sugar can kill you!"
      • disable submit button, unless the form is valid
    • do not allow to add already existing jewel
  • Pesel app

REST and State Management

HTTP API

Asynchronous HTTP requests

  • HTTP requests sent by the browser on behalf of the application
  • asynchronous - the application continues to operate while the browser is waiting for the server to respond
  • allow Angular to interact with web services
    • persistent data can be loaded into the application and changes can be sent to the server and saved
  • Requests are made using the Http class, delivered as a service through dependency injection
    • This class provides an Angular-friendly wrapper around the browser’s XMLHttpRequest feature
  • Examples
    • ngtraining
    • exampleApp
  • Exercises
    • Move gems list to model and use json-server to get them asynchronously into our grid
    • meanCRUD
    • Rewrite 'ftj', connect with backend 'jewel-db'

Redux and Ngrx

  • NgRx - framework for building reactive applications in Angular
    • inspired by the Redux pattern
    • unifies the events in our application and derives state using RxJS
    • stores a single state and uses actions to express state changes
    • manages complex states (apps with a lot of user interactions and multiple data sources)
    • ngrx.io
  • Alternatives
    • NGXS - ngxs.io
    • Akita - github.com/salesforce/akita
    • Elf - ngneat.github.io/elf


Migrating from Angular 1.x to Angular

Migration Steps

Choosing a path

ng-forward and ng-upgrade

Migrating from Angular 2.x to Angular x.x

More examples

Angular docs

Cheat-sheet

News

What's new in Angular ...

  • 16
  • 17
  • ...next

News Con't - 16

16 - https://blog.angular.io/angular-v16-is-here-4d7a28ec680d

Router inputs

// To enable it:
//// use 'withComponentInputBinding' as part of the 'provideRouter'
const routes = [
  {
    path: 'about',
    loadComponent: import('./about'),
    resolve: { contact: () => getContact() }
  }
];

@Component(...)
export class About {
  // The value of "contact" is passed to the contact input
  @Input() contact?: string;
}

Ex with 16

Exercises:

  1. Make our jewels-list component working as a standalone one
  2. Create new app - bootstrapping one component only
  3. Migrate the whole ftj into standalone mode
  4. Add ssr to ftj app and enable hydration
  5. Turn on esbuild in our ftj

News Con't - 17

17 - https://blog.angular.dev/introducing-angular-v17-4d7033312e4b

  • Built-in control flow
    • new block template syntax - @if, @switch, @for
    • ng generate @angular/core:control-flow
  • Deferrable views - better lazy loading
    • @defer, @loading, @error, @placeholder, @defer
    • triggers - idle, on immediate, on timer, on viewport, on interaction, on hover, when
    • more here - https://v17.angular.io/guide/defer
  • Improvements to server-side rendering (SSR) and static-site generation (SSG or prerendering)
    • new lifecycle hooks - afterRender, afterNextRender
  • Vite and esbuild - are default now for new projects
  • Dependency injection debugging in DevTools
  • Standalone APIs - by default in ng generate
    • ng generate @angular/core:standalone
  • New docs - angular.dev

Testing JS

TestingJS.jpeg

Yes, it's possible.

Hard to believe, but true (-8

Testing tools and frameworks

  • Karma - the test runner for JavaScript
  • Protractor - the end-to-end testing framework (deprecated)
  • Jasmine - the behavior-driven JavaScript testing framework
  • Mocha - the JavaScript testing framework (for nodejs)
  • QUnit - the unit testing framework (for jQuery)
  • Selenium - the tool that automates the web browsers
  • PhantomJS - the headless webkit browser

Test-Driven Development

TddJS.jpg

Test-Driven Development Con't

  • An approach to development
    • We write a test before we write just enough production code to fulfill that test and its refactoring
  • Benefits
    • No change is small
    • Specifically identify the tasks
    • Confidence in refactoring
    • Upfront investment, benefits in the future
    • QA resources might be limited
    • Documentation

Practical TDD with JavaScript

TDD life cycle

  • Test first
    • Write the test, watch it fail
  • Make it run
    • Write just enough code to pass the test
  • Make it better
    • Improve the code without changing its behavior

Unit VS E2E Testing

UnitVSe2eJS.png

Unit Testing

  • Jasmine and Karma
  • Unit Testing in Angular
  • Testing Components
  • Testing Services
  • Testing Attribute Directives
  • Testing Pipes
  • Testing Router

Unit Testing Con't

  • Jasmine test framework
  • Karma test runner

Jasmine

JasmineJS.jpg

The beloved One for Aladyn or..

..intriguingly scenting flower.

Well, the choice is yours (-;

Jasmine Con't

  • Fast - core has no external dependencies
  • Full out of the box set of everything we need to test our code
  • Supports browser tests and Node.js tests
  • https://jasmine.github.io/

Example

Function to test Jasmine test spec
function helloWorld() {
  return 'Hello world!';
}
describe('Hello world', () => { 
  it('says hello', () => { 
    expect(helloWorld()) 
        .toEqual('Hello world!'); 
  });
});
  • describe(string, function) - defines a Test Suite
    • A collection of individual Test Specs
  • it(string, function) - defines an individual Test Spec
    • This contains one or more Test Expectations
  • expect(actual) - is an Expectation
    • With a Matcher it describes an expected piece of behavior in the application
  • matcher(expected) - a boolean comparison between the actual value and the expected value
    • Reports to Jasmine if the expectation is true or false - then the spec will be passed or failed

Jasmine Built-in Matchers

expect(array).toContain(member);
expect(fn).toThrow(string);
expect(fn).toThrowError(string);
expect(instance).toBe(instance);
expect(mixed).toBeDefined();
expect(mixed).toBeFalsy();
expect(mixed).toBeNull();
expect(mixed).toBeTruthy();
expect(mixed).toBeUndefined();
expect(mixed).toEqual(mixed);
expect(mixed).toMatch(pattern);
expect(number).toBeCloseTo(number, decimalPlaces);
expect(number).toBeGreaterThan(number);
expect(number).toBeLessThan(number);
expect(number).toBeNaN();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledTimes(number);
expect(spy).toHaveBeenCalledWith(...arguments);

Jasmine Custom Matchers

  • Example
    • testing_f\src\testing\jasmine-matchers.ts

A few useful global functions

To avoid any duplicated setup and to teardown code

  • beforeAll(), afterAll()
    • Called once before/after all the specs in describe test suite are run.
  • beforeEach(), afterEach()
    • Called before/after each test specification, it() function, has been run.

Example

describe('Hello world', () => {

  let expected = "";

  beforeEach(() => {
    expected = "Hello World";
  });

  afterEach(() => {
    expected = "";
  });

  it('says hello', () => {
    expect(helloWorld())
        .toEqual(expected);
  });
});

Exercise

  • testing-angular/just-jasmine (live coding)

Karma

KarmaJS.jpg

In Polish language it means: food for animal (-;

Let's feed our NG App properly! (-8

Karma Con't

  • Manually running Jasmine tests by refreshing a browser tab repeatedly in different browsers every-time we edit some code can become tiresome.
  • Karma is a tool which lets us spawn browsers and run jasmine tests inside of them all from the command line. The results of the tests are also displayed on the command line.
  • Karma can also watch your development files for changes and re-run the tests automatically.
  • Karma lets us run jasmine tests as part of a development tool chain which requires tests to be runnable and results inspectable via the command line.
  • It’s not necessary to know the internals of how Karma works.
    • When using the Angular CLI it handles the configuration for us and for the rest of this section we are going to run the tests using only Jasmine.
  • https://karma-runner.github.io/latest/index.html

x and f

  • x: disables tests
  • f: focused tests
xdescribe('Hello world', () => { 
  xit('says hello', () => { 
    expect(helloWorld())
        .toEqual('Hello world!');
  });
});
fdescribe('Hello world', () => { 
  fit('says hello', () => { 
    expect(helloWorld())
        .toEqual('Hello world!');
  });
});

Unit Testing in Angular

  • Angular components and directives require special support for testing
    • their interactions with other parts of the application infrastructure can be isolated and inspected
  • Isolated unit tests
    • are able to assess the basic logic provided by the class that implements a component or directive
    • but do not capture the interactions with host elements, services, templates, and other important Angular features
  • TDD (Test, Execute, Refactor) with Assemble, Act, and Assert
  • Example - Create new angular app called ftjt, with router and simple CSS - ng new ftjt
    • Add new selling-jewels component - ng g c selling-jewels --skip-tests --inline-template
    • It should have the title "Buying can be fun, but selling too!", kept as component's property
  • Let's do it together by following the steps below

Test it first

  • Add new file - ftjt/src/app/selling-jewels/selling-jewels.component.spec.ts
  • Start with the base Jasmine template format
describe('Test suite title', () => { 
    beforeEach(() => { 
        // .... 
    }); 
    it('Test spec title', () => { 
        // .... 
    }); 
});

Assemble Part 1

  • initialize the component, execute the contractor of the class, setup Angular testing APIs
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SellingJewelsComponent } from './selling-jewels.component';

// ...
beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [ SellingJewelsComponent ]
  })
  .compileComponents();
}));
// ...

Assemble Part 2

  • define the component object, wrap it into fixture - Angular's utility
// ...
// comp will hold the component object  
let comp: SellingJewelsComponent; 
let fixture: ComponentFixture<SellingJewelsComponent>; 
// ...
beforeEach(() => { 
  fixture = TestBed.createComponent(SellingJewelsComponent); 
  comp = fixture.componentInstance; 
});

Act Part

  • Nothing to do here yet
  • We will see that part later with bigger example

Assert Part

it('Should define a title', () => { 
  expect(comp.title).toBeDefined(); 
});

it('Title should be as expected', () => {
  expect(comp.title).toEqual("Buying can be fun, but selling too!");
});

Make it run

  • Run command ng test (keep it running in the separate terminal, rerun it after adding new c, s, etc)
  • Add the title property - title: string;
  • Tests failed, so we're happy (-:
  • Add the title value - title: string = '';
  • One test fine, 2nd no
  • Correct the title value - title: string = 'Buying can be fun, but selling too!';
  • Both tests passed (-:

Make it better

  • Interpolate the title in component's template
  • We'll use the same module and controller, so the same test file
// ...
  describe('Testing interpolated title', () => { 
    beforeEach(() => { 
        // .... 
    });    
    it('Title of the test spec', () => { 
        // .... 
    }); 
  });

Assemble, Act Parts

  • Assemble - we will explain this setup later
// ...
let p: HTMLElement;
// ...
beforeEach(() => {
  p = fixture.nativeElement.querySelector('p');
  fixture.detectChanges();
});
  • Act
    • No custom action yet (interpolation is from angular core)

Assert Part

it('Title should be interpolated', () => {
  expect(p.textContent).toContain(comp.title);
});

Make it run

  • Tests failed
  • Interpolate the title property in the template - template: `<p>{{title}}</p>`,
  • Test passed (-:

Testing Components

  • Component combines an HTML template and a TypeScript class
  • We test that they work together as intended, by
    • creating the component's host element in the browser DOM, as Angular does
    • investigating the component class's interaction with the DOM as described by its template
  • Class alone
  • TestBed (with DOM)

Nested Components

  • Stubbing
  • NO_ERRORS_SCHEMA
  • Examples
    • app/app.component.spec.ts
    • app/about/about.component.spec.ts
  • Exercise
    • Add new jewel game engine component (name it shortly - engine) - ng g c engine
    • Call components selling-jewels and engine from app component's template
    • Fix ftjt's main app component test

Class alone

  • Examples
    • app/demo/demo.ts (LightswitchComp)
    • app/dashboard/dashboard-hero.component.ts
    • app/welcome/welcome.component.ts
    • ngtraining
      • app/jasmine-test/jasmine-test.component.spec.ts
  • Exercise
    • Move game description in to a new component ("Find which Jewel computer likes more than the others.")
      • cd src/app/engine
      • ng g c descr
      • It should show text 'Game Description' by default and when clicked - 'Find which Jewel computer likes more than the others.'

With DOM

  • TestBed.configureTestingModule() - to setup the TestBed
  • createComponent() - creates an instance, adds a corresponding element to the test-runner DOM, and returns a ComponentFixture
  • ComponentFixture - interacting with the created component
  • beforeEach() - for duplicated TestBed configuration
  • nativeElement - usually with HTMLElement.querySelector()
  • DebugElement - non-browser platform
  • By.css() - run on a different platform

With DOM con't

  • Examples
    • app/banner/banner.component.ts
    • app/welcome/welcome.component.ts
  • Exercise
    • In H2 tag replace "Find The Jewel" text with new interpolated gameTitle property
      • Assert that engine component provides the default value for gameTitle ("Find The Jewel")
      • Test if it works also for different value of gameTitle
    • Assert that engine component changes the value of howManyTries

Testing binding

  • fixture.nativeElement.querySelector() - querying for html
  • createComponent() with fixture.detectChanges() - manual change detection
  • ComponentFixtureAutoDetect - automatic change detection
  • dispatchEvent() - changing an input value

Testing binding Con't

  • Examples
    • app/banner/banner.component.spec.ts
    • app/banner/banner.component.detect-changes.spec.ts
    • app/hero/hero-detail.component.spec.ts
  • Exercise
    • Simulate user input in engine
    • Improve it with <p id="providedJewel">Your gues is {{findJewel}}</p>

External file, dependencies

  • Service test doubles - stubs, fakes, spies, mocks
  • injector.get(service_name) - from the injector of the component-under-test
  • TestBed.inject() - from the root injector (if it's the only provider)
  • Async service

External file, dependencies Con't

  • Examples
    • app/banner/banner-external.component.spec.ts (vs banner.component.spec.ts)
    • app/welcome/welcome.component.spec.ts
    • app/twain/twain.component.spec.ts
  • Exercise
    • Create new service jewel
      • ng g s jewel
      • provide it from app.module.ts
    • Test engine component with injected jewel service
      • mock, stub, test-double, injected

Testing a list of items

  • Examples
    • app/hero/hero-list.component.spec.ts
  • Exercise
    • Create new component jewels-list
      • cd src/app/engine
      • ng g c jewels-list
    • Test jewels-list component

Input, Output

  • Appears inside the view template of a host component
    • host uses a property binding to set the input property
    • listens to events raised by the output property (event binding)
  • We test if such binding works as expected
    • set input values and listen for output events
  • Possible methods - as used by, stand-alone component, used by a substitute (test-host)
    • as used by - usually to much effort and it's over-complicated
    • stand-alone - triggerEventHandler or element's own click() method
    • test-host - disables distractions (*ngFor, components, layout HTML, other bindings, injected services, etc)

Input, Output Con't

  • Examples
    • app/dashboard/dashboard-hero.component.spec.ts
  • Exercise
    • Test communication between engine and add-jewel components (stand-alone)
      • cd src/app/engine
      • ng g c add-jewel
    • Test jewel-detail according to jewels-list-detail (test-host)
      • cd src/app/engine
      • ng g c jewels-list-detail
        • make it with a routerLink
      • ng g c jewel-detail
      • any conclusion? (0;

Testing forms

  • Exercise
    • Add pesel component
      • Source is here
      • Make it as angular's reactive form

Overriding providers

  • Component is created with its own injector - child of the fixture injector
  • Component's own providers
    • Can't be stubbed or configured in TestBed.configureTestingModule
      • For example test cannot get to child injector services from the fixture injector
  • Solution - TestBed.configureTestingModule().overrideComponent() method
    • Replaces the component's providers with test doubles (spy stub)
  • Other angular classes are supported too - overrideDirective, overrideModule, overridePipe

Overriding providers Con't

  • Example
    • app/hero/hero-detail.component.spec.ts
  • Exercise
    • Move validators in pesel component to it's own custom service

Testing Services

  • The easiest Angular construct to unit test
  • With dependencies
    • Do not inject the real services - usually they are difficult to create and control
    • Mock the dependency, use a dummy value, or create a spy
  • TestBed - lets Angular DI do the service creation and deal with constructor argument order
    • Creates a dynamically-constructed Angular test module that emulates an Angular @NgModule.
    • TestBed.configureTestingModule() - metadata object that can have most of the properties of an @NgModule
    • with TestBed.inject()
    • inside the beforeEach()
    • via providers array
  • HTTP services
    • with just HttpClient
    • with HttpClientTestingModule

Testing Services Con't

  • Examples
    • app/demo/demo.spec.ts
    • app/demo/demo.testbed.spec.ts
    • app/model/hero.service.spec.ts
  • Exercise
    • Test jewel service
      • With and without testbed
      • Improve it by adding new jewel
        • cd src/app/engine
        • ng g c add-jewel-service
        • Test it from service perspective
      • Make it as an observable
        • cd src/app
        • ng g s jewel-obs
        • Test it from component perspective
          • Which one: add-jewel-service or jewels-list-detail ?

Testing Attribute Directives

  • Attribute Directive
    • Modifies the behavior of an element, component or another directive
    • Is applied as an attribute on a host element
  • Some noteworthy techniques
    • By.directive - gets the elements that have specific directive when their element types are unknown
    • :not pseudo-class - helps find elements that do not have the directive
    • DebugElement.styles- access to element styles without a real browser (an alternative to nativeElement)
    • Use the Angular's default element injector in tests to get defaults from custom directives
    • DebugElement.properties - access to the artificial custom property that is set by the directive

Testing Attribute Directives Con't

  • Examples
    • app\shared\highlight.directive.spec.ts
  • Exercise
    • Test favorite-jewel directive

Testing Pipes

  • No need to use Angular utilities
    • Manipulates the input value into a transformed output value
  • Isolated tests only
    • Can't tell if it's working properly as applied in the application components

Testing Pipes Con't

  • Examples
    • app/shared/title-case.pipe.spec.ts
    • app/hero/hero-detail.component.spec.ts
    • ngtraining
      • app/pipe-solution/even-uppercase.pipe.spec.ts (live coding)
  • Exercise
    • Test jewel pipe
      • cd src/app
      • ng g p jewel
      • It should count the number of characters in jewel's name
    • Test jewels-list-detail component with jewel pipe

Testing Router

  • Routed Components
  • RouterTestingModule

Testing Router Con't

  • Examples
    • app/dashboard/dashboard.component.spec.ts
    • app/hero/hero-detail.component.spec.ts
    • testing/activated-route-stub.ts
  • Exercise
    • Test the ftjt app router
      • From routed component - jewel-detail
        • When we navigate to jewel it should display that jewel

e2e Testing

Protractor

ProtractorJS.jpg

Ugly name, but full of power (-;

Protractor Con't

  • End-to-end testing tool
    • Runs using Node.js and is available as an npm package
    • Allows the e2e testing by interacting the DOM elements of application
  • Selects a specific DOM element
    • Shares the data with that element
    • Simulates the click of a button, etc
    • Interacts with an application in the same way as a user would
    • Allows expectations to be set based on what the user would expect

Protractor Global Functions

  • browser() - global object from WebDriver, used to interact with the application browser
    • provides useful methods - get(), getTitle(), etc
  • element() - used to find a single element based on the locator
    • supports multiple element selection - element.all (also takes Locator and returns ElementFinderArray)
    • methods - element.all(Locator).get(position), element.all(Locator).count(), etc
  • action - to interact with a DOM
    • methods - getText(), click(), clear(), etc
  • Locator - how to find a certain element in the DOM
    • exported as a global factory function, which will be used with a global by object
    • examples - element(by.css(cssSelector)), element(by.id(id)), element.(by.model), etc

Example

  • A basic search feature
    • We input 'pro' into the search box
      • The search button should be clicked on
      • At least one result should be received
    • No describing a controller, directive, or service
      • Only the expected application behavior
  • The structure and syntax of Protractor mirrors that of Jasmine
    • Protractor is a wrapper around Jasmine, with added features to support e2e testing

Search Example Code (generic)

describe("Giving value 'abc' for input into the search box",function(){ 
  browser.get('/');                                           // 1 - Point browser to website 
  var inputField = element.all(by.css('input'));              // 2 - Select input field 
  inputField.setText('pro');                                  // 3 - Type 'pro' into input field 
  var searchButton = element.all(by.css('#searchButton');     // 4 - Push search button 
  searchButton.click(); 
  it('should display search results',function(){ 
    var searchResults = element.all(by.css('#searchResult');  // 5 - Find the search result details 
    expect(searchResults).count() >= 1);                      // 6 - Assert 
  }); 
});

Protractor Show

  • It is about testing through the eyes of the user
  • Designed to test all the layers of an application
    • Web UI, backend services, persistence layer, etc
  • Focused on how the complete system works when interconnected
  • Allows the developer to focus on the complete behavior of a feature or module
  • Best choice for Angular applications

Protractor Show Con't

  • Documented throughout the Angular tutorials and examples
  • Can be written using multiple testing frameworks - Jasmine, Mocha, etc
  • Provides convenience methods for Angular components
    • waiting for a page to load, expectations on promises, etc
  • Wraps Selenium methods that automatically wait for promises to be fulfilled
  • Supported by SaaS (Software as a Service) providers - Sauce Labs, etc
  • Same people to 'blame' for - guys behind Angular and Google (--;

With Angular

  • Examples (testing_f, reactive-forms, dynamic-forms, lifecycle-hooks, form-validation)
    • e2e\src\app.e2e-spec.ts
    • e2e\protractor.conf.js
  • Exercise - testing forms
    • Run ng e2e in ftjt - fix the default test (-:
    • Write e2e test for ftj game
      • Rewrite the engine and add-new-jewel into reactive forms
      • Test the whole pesel part

Debugging, API helpers, Practices

Practices

  • Keep source and spec file in the same folder
  • Separated test folder - for integration specs
  • KISS - keep it simple stupid
  • e2e tests - in angular avoid them AMAP, instead go for unit tests techniques
    • no comprehensive test coverage
    • difficult to write and perform poorly, break easily
    • no telling if missing or bad data, lost connectivity, and remote service failures
    • when update db, credit card gates - tricky and risky