Developer Blog

Tipps und Tricks für Entwickler und IT-Interessierte

Power Shell | Cookbook

Scripts

Parameter in a Powershell Script

param (
    [Parameter(Mandatory=$true)]  [string] $folder = "",
    [Parameter(Mandatory=$false)] [string] $type,
    [Parameter(Mandatory=$false)] [switch] $migrate,
    [Parameter(Mandatory=$false)] [switch] $help
)

Call the Script

.\script.ps1 foldername
.\script.ps1 foldername -type=folder

.\script.ps1 foldername -type=folder -migrate

Parameter Debug and Verbose

This parameter could not be defined in a script, because they are already present.

param (
    [Parameter(Mandatory=$false)] [switch] $debug
)
.\using_parameter.ps1 : Ein Parameter mit dem Namen "Debug" wurde mehrfach für den Befehl definiert.
In Zeile:1 Zeichen:1
+ .\using_parameter.ps1
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], MetadataException
    + FullyQualifiedErrorId : ParameterNameAlreadyExistsForCommand

To access their values, use

param (
    [Parameter(Mandatory=$false)] [switch] $help
)

$debug   = $PSBoundParameters.ContainsKey('Debug')
$verbose = $PSBoundParameters.ContainsKey('Verbose')

Write-Host "help    = $help"
Write-Host "debug   = $debug"
Write-Host "verbose = $verbose"
> .\using_parameter.ps1  -debug -help
help    = True
debug   = True
verbose = False
> .\using_parameter.ps1  -debug 
help    = False
debug   = True
verbose = False
> .\using_parameter.ps1  -debug -verbose
help    = False
debug   = True
verbose = True

String

Converting String to TitleCase

function toTitleCase($string) {
    return $string.substring(0,1).toupper()+$string.substring(1).tolower()

}

Filesystem

List of Files in a Folder

CommandDescription
Get-ChildItemFolders and Files
Get-ChildItem -DirectoryOnly Folders
Get-ChildItem -FileOnly Files
Get-ChildItem -Recurse -DirectoryOnly Folders, Recursive
Get-ChildItem -Recurse -FileOnly Files, Recursive
(ls -r *.txt).fullname
Get-ChildItem -recurse -Filter .editorconfig -path . |



dir -Path . -Filter ProfileInformationController* -Recurse |



Counting Files

CommandDescription
(Get-ChildItem | Measure-Object).CountCount the files and folders
(Get-ChildItem -Directory | Measure-Object).CountOnly Folders
(Get-ChildItem -File | Measure-Object).CountOnly Files
(Get-ChildItem -Recurse -Directory | Measure-Object).CountOnly Folders, Recursive
(Get-ChildItem -Recurse -File | Measure-Object).CountOnly Files, Recursive

Using  Scripting.FileSystemObject

$objFSO = New-Object -com  Scripting.FileSystemObject
$objFSO.GetFolder($folder).Files.Count

Find a Folders within a Folder (not recursive)

(Get-ChildItem --Attributes Directory).Name

Run Command for each Folder

(Get-ChildItem  -Attributes Directory).Name |



With Enumerator

[IO.Directory]::EnumerateFiles($folder) | ForEach-Object {
    Write-Host $_
}

Find all folders with Pattern

Get-ChildItem -recurse -depth 2 -directory -path ..\packages_flutter | `
Where-Object { $_.FullName -match 'example' }                        | `



Delete files with pattern

Get-ChildItem *.code -recurse | foreach { Remove-Item -Path $_.FullName }
Get-ChildItem -Path C:Temp -Include *.* -File -Recurse | foreach { $_.Delete()}

Files

Filename and Extension of File with Split-Path

Filename

Split-Path -Path <PATH> -Leaf

Folder

Split-Path -Path <PATH>

Extension

Split-Path -Path <PATH> -Extension

Create File

New-Item new.txt -type file

Loop through entries of a textfile

Get-Content list-of-folders | Where-Object {$_ -NotMatch "#.*" } | ForEach-Object { 
    .\check_folder.ps1$_
    # Write-Host "Weiter -->" -NoNewLine
    # $key = $Host.UI.RawUI.ReadKey()
}

Loop through results from Select-String

Here is an example that greps for a string and uses the results in a loop to determine if some action should be taken

$pattern = "powershell"
$files = Select-String -Path "d:\script\*.txt" -Pattern $pattern
foreach ($file in $files) {
   $filename=$file.Filename
   $item = get-item $file.Path
    "File '{0}' matches the pattern '{1}'" -f $item.FullName, $pattern
    "It was last modified on {0}" -f $item.LastWriteTime
 
   $response = Read-Host -Prompt "Set the archive bit on this file?" 
   If ($response -eq 'Y') {
      $item.attributes="Archive"
   }
}

Check if a file exist

Test-Path $PROFILE

Searching in Files

Get-ChildItem -Recurse | Select-String "dummy" -List | Select Path
Get-ChildItem -Recurse *.sql | Select-String "create .*_tab_" | Select-Object -Unique Path
Select-String -path *.txt -pattern PowerShell

Select-String -path *.txt -pattern PowerShell -notmatch

Parsing Files

Get first line of output

$eventResult.Split([Environment]::NewLine) | Select -First 1

Web

Download Web-Page

Invoke-WebRequest -Uri <link>

List all Links

$response = Invoke-WebRequest -Uri <link>
$response.links | Select -Expand href

List all Links with filtering href by RegEx

$response.Links | Where-Object {$_.href -like "*videos*" } | ForEach-Object { $_.href }

List all Links with filtering class name by RegEx

$response.Links | Where-Object {$_.class -eq "page-numbers"} | Format-List innerText, href

Download and Install Visual Studio Code in portable Mode

$FOLDER=Get-Date -Format "yyyy-MM-dd-HH-mm"

Write-Host "Create link for folder $FOLDER"


# Download
# https://code.visualstudio.com/sha/download?build=stable&os=win32-x64-archive
# https://code.visualstudio.com/sha/download?build=insider&os=win32-x64-archive

$LINK="https://code.visualstudio.com/sha/download?build=insider&os=win32-x64-archive"
$FILE="vscode-insider.zip"

if (Test-Path "$FILE") {
	Remove-Item "$FILE"
}

Invoke-WebRequest "$LINK" -OutFile "$FILE"

Expand-Archive "$FILE" "$FOLDER"

if (Test-Path $FOLDER\data)
{
	Remove-Item $FOLDER\data
}


if (Test-Path code) { Remove-Item code }

# Using junction from SysInternalsSuite to create symlinks

junction code $FOLDER
junction code\data data

Environment

Show env variables

gci env:* | Sort-Object Name

Show env variables with name pattern

gci env: | Where name -like '*HOME

Processes

List Processes and Path

Get-Process | Select-Object Path

Show processes using a specific port

Get-Process -Id (Get-NetTCPConnection -LocalPort YourPortNumberHere).OwningProcess

Network

Pipeline

Parse out from command

$REPOSITORY=<URL of Repository>
git branch -r | ForEach-Object { Write-Output "git clone -b $_ $REPOSITORY $_" } | Out-File -FilePath .clone-all-branches

Permissions

Show current policy

Get-ExecutionPolicy

Allow custom scripts to execute

Set-ExecutionPolicy -Scope CurrentUser unrestricted

Security

Self-Sign a script

Step 1: Create your code signing certificate

New-SelfSignedCertificate -DnsName user@via-internet.de `
                          -CertStoreLocation Cert:\currentuser\my  `
                          -Subject "CN=Local Code Signing"  `
                          -KeyAlgorithm RSA  `
                          -KeyLength 2048  `
                          -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider"  `
                          -KeyExportPolicy Exportable  `
                          -KeyUsage DigitalSignature  `
                          -Type CodeSigningCert

Step 2: Open the Certificate Manager for Current User

certmgr /s my

Step 3: Copy the new certificate to the appropriate cert stores

From Personal folder into Trusted Root Certification Authorities and into Trusted Publishers stores.

German: Von Eigene Zertifikate nach Vertrauenswürdige Stammzertifizierungsstellen und Vertrauenswürdige Herausgeber

Step 4: Sign your PowerShell script with the new cert

$CERT=@(Get-ChildItem cert:\CurrentUser\My -CodeSigning)[1]
Set-AuthenticodeSignature .\HelloWorld.ps1 $CERT

Or

❯ Set-AuthenticodeSignature -FilePath .\HelloWorld.ps1 -Certificate (Get-ChildItem -Path Cert:CurrentUserMy -CodeSigningCert)

Final Check

❯ Get-AuthenticodeSignature .\HelloWorld.ps1

Github

Download Repositories

gh repo list <github username> --limit 1000 |



Profiles

Different PowerShell profiles

DescriptionPath
Current User, Current Host – console$Home[My ]DocumentsWindowsPowerShellProfile.ps1
Current User, All Hosts   $Home[My ]DocumentsProfile.ps1
All Users, Current Host – console   $PsHomeMicrosoft.PowerShell_profile.ps1
All Users, All Hosts      $PsHomeProfile.ps1
Current user, Current Host – ISE$Home[My ]DocumentsWindowsPowerShellMicrosoft.P owerShellISE_profile.ps1
 All users, Current Host – ISE  $PsHomeMicrosoft.PowerShellISE_profile.ps1

Show Path for all profiles

$PROFILE | Format-List * -Force

Create a new profile

New-Item $PROFILE.CurrentUserAllHosts -ItemType file -Force

Customizing

Theming

https://ohmyposh.dev/

Install Posh-Git and Oh-My-Posh.

Install-Module posh-git   -Scope CurrentUser
Install-Module oh-my-posh -Scope CurrentUser
Install-Module -Name PSReadLine -AllowPrerelease -Scope CurrentUser -Force -SkipPublisherCheck

Then run “notepad $PROFILE” and add these lines to the end:

Import-Module posh-git
Import-Module oh-my-posh

Set-Theme Paradox

Set a custom theme

Import-Module posh-git
Import-Module oh-my-posh
Set-Theme Paradox

Show current theme settings

$ThemeSettings
$ThemeSettings.CurrentThemeLocation

Customize Prompt

Show current Path

function prompt
{
    "PS " + $(get-location) + "> "
}

Randor Color

function prompt
{
    $random = new-object random
    $color=[System.ConsoleColor]$random.next(1,16)
    Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor $color
    return " "
}

Display current time at the end of prompt line (this will mess up you console buffer)

function prompt
{
    $oldposition = $host.ui.rawui.CursorPosition
    $Endline = $oldposition
    $Endline.X+=60
    $host.ui.rawui.CursorPosition = $Endline
    Write-Host $(get-date).Tostring("yyyy-MM-dd HH:mm:ss")
    $host.ui.rawui.CursorPosition = $oldposition
    Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor Magenta
    return " "
}

Show current user, host, current line number

$global:CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
function prompt
{
    $host.ui.rawui.WindowTitle = $CurrentUser.Name + " " + $Host.Name + " " + $Host.Version + " Line: " + $host.UI.RawUI.CursorPosition.Y
    Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor Magenta
    return " "
}

Weitere Anpassungsmöglichkeiten

https://www.norlunn.net/2019/10/07/powershell-customize-the-prompt/

Security

❯ New-SelfSignedCertificate -DnsName user@via-internet.de -CertStoreLocation Cert:CurrentUserMy -Type CodeSigning

   PSParentPath: Microsoft.PowerShell.SecurityCertificate::CurrentUserMy

Thumbprint                                Subject              EnhancedKeyUsageList
----------                                -------              --------------------
4AED871E6DB5FF3E85EB1625C5369DBDB3E120FD  CN=user@via-interne… Codesignatur
❯ Set-AuthenticodeSignature -FilePath demo.ps1 -Certificate (Get-ChildItem -Path Cert:CurrentUserMy -CodeSigningCert)

    Directory: D:TMP

SignerCertificate                         Status                               StatusMessage                     Path
-----------------                         ------                               -------------                     ----
4AED871E6DB5FF3E85EB1625C5369DBDB3E120FD  Valid                                Signature verified.               demo.ps1

Final Check

❯ Get-AuthenticodeSignature .demo.ps1

    Directory: D:CLOUDEnvironmentsKeycloakKeycloak12.0.1bin

SignerCertificate                         Status                                StatusMessage                    Path
-----------------                         ------                                -------------                    ---
4AED871E6DB5FF3E85EB1625C5369DBDB3E120FD  Valid                                 Signature verified.              demo.ps1

From Bash to Powershell

Alias for WHICH command

❯ (get-command FILE.EXE).Path
❯ Set-Alias where Get-Command
❯ where FILE.EXE

Snippets

Formatting

0..31 | ForEach-Object { 
    "{0,6}`t{1,6}`t{2,5}`t0x{0:X4}" -f $_,[Convert]::ToString($_,2), [Convert]::ToString($_,8) 
}

Select a Service By Name (Partial Match)

> Get-Service | Select-Object -ExpandProperty Name | Select-String -pattern 'ws'

CURL For Powershell

> (Invoke-WebRequest www.google.com).Content

List Environment Variables

> gci env: | sort name

Get PS Version

> $PSVersionTable

List Installed Programs

> Get-WmiObject -Class Win32_Product

Colored Header in Output

$prefix ="$prefix                   ".Substring(0,6)
$title  ="$title                    ".Substring(0,15)

$line = "$prefix ${title}: ${line}"
$fill = " " * ([Console]::WindowWidth - $line.length)
Write-Host "${line}${fill}"  -ForegroundColor White -BackgroundColor Blue -NoNewline

PySpark | Cookbook

Websites

  • The Blaze Ecosystem (Blaze)
  • Dask: Flexible library for parallel computing in Python.
  • DataShape: Data layout language for array programming. 
  • Odo: Shapeshifting for your data
    It efficiently migrates data from the source to the target through a network of conversions.

Reading Textfiles

from pyspark.sql import SparkSession

spark = SparkSession \
    .builder \
    .appName("Demo") \
    .config("spark.demo.config.option", "demo-value") \
    .getOrCreate()

df = spark.read.csv("input.csv",header=True,sep="|");
from pyspark import SparkContext
from pyspark.sql import SQLContext
import pandas as pd

sc = SparkContext('local','example') 
sql_sc = SQLContext(sc)

p_df= pd.read_csv('file.csv')  # assuming the file contains a header
# p_df = pd.read_csv('file.csv', names = ['column 1','column 2']) # if no header
s_df = sql_sc.createDataFrame(pandas_df)
sc.textFile("file.csv") \
    .map(lambda line: line.split(",")) \
    .filter(lambda line: len(line)>1) \
    .map(lambda line: (line[0],line[1])) \
    .collect()
sc.textFile("input.csv") \
    .map(lambda line: line.split(",")) \
    .filter(lambda line: len(line)<=1) \
    .collect()
spark.read.csv(
    "input.csv", header=True, mode="DROPMALFORMED", schema=schema
)
(spark.read
    .schema(schema)
    .option("header", "true")
    .option("mode", "DROPMALFORMED")
    .csv("input.csv"))

Read CSV file with known structure

from pyspark.sql.types import StructType, StructField
from pyspark.sql.types import DoubleType, IntegerType, StringType

schema = StructType([
    StructField("A", IntegerType()),
    StructField("B", DoubleType()),
    StructField("C", StringType())
])

(sqlContext
    .read
    .format("com.databricks.spark.csv")
    .schema(schema)
    .option("header", "true")
    .option("mode", "DROPMALFORMED")
    .load("input.csv"))

Jenkins | Cookbook

Working with VS Code

Validate Jenkins File

Install VS Code Plugin Jenkins Pipeline Linter Connector

Add configuration in .vscode/settings.json

"jenkins.pipeline.linter.connector.crumbUrl": "<JENKINS_URL>/crumbIssuer/api/xml?xpath=concat(//crumbRequestField
"jenkins.pipeline.linter.connector.user": "<USERNAME>",
"jenkins.pipeline.linter.connector.pass": "<PASSWORD>",
"jenkins.pipeline.linter.connector.url": "<JENKINS_URL>/pipeline-model-converter/validate",

Replace <USERNAME>, <PASSWORD> and <JENKINS_URL> with your values, for example

"jenkins.pipeline.linter.connector.crumbUrl": "http://localhost:8080/crumbIssuer/api/xml?xpath=concat(//crumbRequestField
"jenkins.pipeline.linter.connector.user": "admin",
"jenkins.pipeline.linter.connector.pass": "secret",
"jenkins.pipeline.linter.connector.url": "http://localhost:8080/pipeline-model-converter/validate",

Working with Jenkins Client (CLI)

Download Client

wget localhost:8080/jnlpJars/jenkins-cli.jar

Working with Plugins

Create aPlugin

mkdir SamplePlugin
cd SamplePlugin
mvn -U archetype:generate -Dfilter="io.jenkins.archetypes:"
mvn -U archetype:generate -Dfilter="io.jenkins.archetypes:global-configuration-plugin"
[INFO] Scanning for projects...
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml
Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml (14 kB at 32 kB/s)
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml (20 kB at 44 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-archetype-plugin/maven-metadata.xml
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-archetype-plugin/maven-metadata.xml (918 B at 18 kB/s)
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.1.2:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.1.2:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.1.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> io.jenkins.archetypes:global-configuration-plugin (Skeleton of a Jenkins plugin with a POM and an example piece of global configuration.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Choose io.jenkins.archetypes:global-configuration-plugin version:
1: 1.2
2: 1.3
3: 1.4
4: 1.5
5: 1.6
Choose a number: 5:
[INFO] Using property: groupId = unused
Define value for property 'artifactId': com.examples.jenkins.plugins
Define value for property 'version' 1.0-SNAPSHOT: :
[INFO] Using property: package = io.jenkins.plugins.sample
Confirm properties configuration:
groupId: unused
artifactId: com.examples.jenkins.plugins
version: 1.0-SNAPSHOT
package: io.jenkins.plugins.sample
 Y: : y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: global-configuration-plugin:1.6
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: artifactId, Value: com.examples.jenkins.plugins
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: io.jenkins.plugins.sample
[INFO] Parameter: packageInPathFormat, Value: io/jenkins/plugins/sample
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: io.jenkins.plugins.sample
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: artifactId, Value: com.examples.jenkins.plugins
[INFO] Project created from Archetype in dir: /Users/Shared/CLOUD/Kunde.BSH/workspace/SamplePlugin_Config/com.examples.jenkins.plugins
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  45.525 s
[INFO] Finished at: 2020-03-01T17:28:27+01:00
[INFO] ------------------------------------------------------------------------

Verify Plugin

 cd com.examples.jenkins.plugins
mvn verify

Run Plugin

mvn hpi:run

Working with Groovy Scripts

Include a common groovy script in Jenkins file

1: Create a common.groovy file with function as needed

def mycommoncode() {
}

2: In the main Jenkinfile load the file and use the function as shown below

node{ 
   def common = load “common.groovy”
   common.mycommoncode()
}

Basic example of Loading Groovy scripts

File example.groovy

def example1() {
 println 'Hello from example1' 
}
def example2() {
 println 'Hello from example2'
}

The example.groovy script defines example1 and example2 functions before ending with return this. Note that return this is definitely required and one common mistake is to forget ending the Groovy script with it.Jenkinsfile

def code node('java-agent') { 
    stage('Checkout') { checkout scm } 
    stage('Load') { code = load 'example.groovy' } 
    stage('Execute') { code.example1() }
} 

code.example2()

Processing Github JSON from Groovy

In this demo, we first show how to process JSON response from Github API in Groovy.Processing JSON from Github

String username = System.getenv('GITHUB_USERNAME') 
String password = System.getenv('GITHUB_PASSWORD') 
String GITHUB_API = 'https://api.github.com/repos' String repo = 'groovy' 
String PR_ID = '2' // Pull request ID 
String url = "${GITHUB_API}/${username}/${repo}/pulls/${PR_ID}" 

println "Querying ${url}" 

def text = url.toURL().getText(requestProperties: ['Authorization': "token ${password}"]) 
def json = new JsonSlurper().parseText(text) 

def bodyText = json.body // Check if Pull Request body has certain text if ( bodyText.find('Safari') ) {
     println 'Found Safari user' }

The equivalent bash command for retrieving JSON response from Github API is as follows:Equivalent bash command

// Groovy formatted string 
String cmd = "curl -s -H \"Authorization: token ${password}\" ${url}" 
// Example String 
example = 'curl -s -H "Authorization: token XXX" https://api.github.com/repos/tdongsi/groovy/pulls/2'

Processing Github JSON from Jenkinsfile

Continuing the demo from the last section, we now put the Groovy code into a callable function in a script called “github.groovy”. Then, in our Jenkinsfile, we proceed to load the script and use the function to process JSON response from Github API.github.groovy

import groovy.json.JsonSlurper
def getPrBody(String githubUsername, String githubToken, String repo, String id) {
   String GITHUB_API = 'https://api.github.com/repos' 
   String url = "${GITHUB_API}/${githubUsername}/${repo}/pulls/${id}" 

   println "Querying ${url}"

def text = url.toURL().getText(requestProperties: ['Authorization': "token ${githubToken}"])
def json = new JsonSlurper().parseText(text) 
def bodyText = json.body return bodyText } 
return this

Jenkinsfile

def code node('java-agent') { 
    stage('Checkout') { checkout scm }
    stage('Load') { code = load 'github.groovy' } 
    stage('Execute') {

Best Practice

D3.js | Cookbook

Basic Tasks

Create Container

const svg = d3
	.select('#playground')
	.append('svg')
	.attr('width', width + margin.left + margin.right)
	.attr('height', height + margin.top + margin.bottom)
	.append('g')
	.attr('transform',
	      'translate(' + margin.left + ',' + margin.top + ')');

Axes: Create

const x = d3Scale.scaleLinear().domain([0, 100]).range([0, width]);
const y = d3Scale.scaleLinear().domain([0, 100]).range([height, 0]);

const xAxis = d3Axis.axisBottom(x).tickFormat(d3.timeFormat(
const yAxis = d3Axis.axisLeft(y).ticks(10);

Axes

Format

var axisTimeFormat = d3.time.format.multi([
    ["
    ["
    [
    [
    [
    [
    [
    [
 ]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickFormat(axisTimeFormat);

Installation

$ npm install -save d3
                    d3-scale
                    d3-array
                    d3-shape
                    d3-interpolate
                    d3-color
                    d3-time-format
                    d3-ease
                    d3-transition
                    d3-selection
                    d3-time
                    d3-path
                    d3-dispatch
                    d3-format
                    d3-timer
                    d3-drag
                    d3-geo
                    d3-brush
                    d3-dsv
                    d3-random

ClojureScript | Cookbook

UI: Common tasks

Loading spinner

index.html

<head>
	....
	<link href="lib/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" type="text/css">
	<link href="lib/css/style.css" rel="stylesheet" type="text/css">
</head>

<body>
	<div id="app">
		<div class="d-flex justify-content-center">
			<div class="loader"></div>
		</div>
	</div>
	<script src="lib/jquery/3.4.1/jquery.min.js" type="text/javascript"></script>
	<script src="lib/bootstrap/4.4.1/js/bootstrap.bundle.min.js" type="text/javascript"></script>
	....

lib/css/style.css

.loader {
	position: fixed;
	z-index: 999;
	overflow: visible;
	margin: auto;

	border: 16px solid #f3f3f3;
	border-radius: 50%;
	border-top: 16px solid blue;

	width: 120px; height: 120px;
	top: 0; left: 0; bottom: 0; right: 0;

	-webkit-animation: spin 2s linear infinite;
	/* Safari */
	animation: spin 2s linear infinite;
}

/* Transparent Overlay */
.loader:before {
	content: '';
	display: block;
	position: fixed;
	top: 0; left: 0;
	width: 100%;
	height: 100%;
}

/* Safari */
@-webkit-keyframes spin {
	0%   { -webkit-transform: rotate(0deg); }
	100% { -webkit-transform: rotate(360deg); }
}

@keyframes spin {
	0%   { transform: rotate(0deg);  }
	100% { transform: rotate(360deg);}
}

Rendering

How to render content

(r/render-component [content]
  (.querySelector js/document "#app"))
(r/render-component [content])
  (.-body js/document)
(defn ^:export run []
  (r/render [simple-example]
  (js/document.getElementById "app"))
(defn home-page []
  [:div
    [:h2 "Homepage!"]])

;; -------------------------
;; Initialize app

(defn mount-root []
  (reagent/render [home-page] (.getElementById js/document "app")))

(defn init! []
  (mount-root))

Render several html tags as one

(defn content-row
    ""
    [col1 col2 col3]
    [:<>
        [:div.grid-item col1]
        [:div.grid-item col2]
        [:div.grid-item col3]])
rxjs

rxjs | Cookbook

Informations

NGRX – Reactive State for Angular

Original Post is here. Github repo has the code snippets at choopage’s GitHub repo.

Recipes


Chaining of observable

The below snippet would return 0, 1, 2, 3…, n until it is stopped. It would be returned at every 2 sec. See reference here.

import { Observable } <strong>from 'rxjs/Rx';

let obs = Observable.<em>interval</em>(2000);
let req = obs
    .flatMap(v => { return Observable.of(v) })
    .subscribe(
        v   => console.log(v),
        err => console.error(err),
        ()  => console.log(<strong>'done'</strong>)
    );

Response transformer

The below would return undefined at every 2 sec interval.

import { Observable } from 'rxjs/Rx';

let obs = Observable.interval(2000);

obs
    .let(responseTransformer)
    .subscribe(
        v  => console.log(v),
        err=> console.error(err)
    );

responseTransformer(input: Observable<any>) {
    return input.map(v => v.data);
}

Using RxJS composition over base class extension

export class MyAPI {
    constructor(private http: Http) {
    }

    get(url: string, options: any) {
        return this.http.get(url, options).let(responseTransformer);
    }
}

responseTransformer(input: Observable<any>) {
    return input.map(v => v.data);
}

Higher Order Observable

We create higher order observable using .map.

const numObservable = Rx.Observable.interval(1000).take(4);
const higherOrderObservable = numObservable.map(x => Rx.Observable.of(1, 2));

higherOrderObservable.subscribe(<br>x => x.subscribe(y => console.log(y)));

Further use of higher order observable

usingHigherOrderObservable() {
    Observable
        .interval(1000)
        .groupBy(n => n
}

Flatten a higher order observable with RxJS switch

const numObservable = Rx.Observable.interval(1000).take(2);
const higherOrderObservable = numObservable.map(x => Rx.Observable.of(1, 2)).switch();

/* 
------+--------+---------
       \        \
        1,2      1,2
        
        switch
        
------1-2-------1-2-------

Switch map flattens the higher order observable
*/

higherOrderObservable.subscribe(x => console.log(x));

switchMap: map and flatten higher order observables

const clickObservable = Rx.Observable.fromEvent(document, 'click');

function performRequest() {
  return fetch('<a href="http://jsonplaceholder.typicode.com/users/1'" target="_blank" rel="noreferrer noopener">http://jsonplaceholder.typicode.com/users/1'</a>)
  .then(res =>; res.json());
  //this returns a Promise
}
//Observabl<Event> --> Observable<Response><br>const responseObservable = clickObservable<br>  .switchMap(click => performRequest());//switchMap can convert Promise to Observable<br>//switchMap = map .... + ... switchresponseObservable.subscribe(x => console.log(x.email));

Use groupBy in real RxJS applications

See reference here.

const busObservable = Rx.Observable.of(
  {code: 'en-us', value: '-TEST-'},
  {code: 'en-us', value: 'hello'},
  {code: 'es', value: '-TEST-'},
  {code: 'en-us', value: 'amazing'},
  {code: 'pt-br', value: '-TEST-'},
  {code: 'pt-br', value: 'olá'},
  {code: 'es', value: 'hola'},
  {code: 'es', value: 'mundo'},
  {code: 'en-us', value: 'world'},
  {code: 'pt-br', value: 'mundo'},
  {code: 'es', value: 'asombroso'},
  {code: 'pt-br', value: 'maravilhoso'}
).concatMap(x => Rx.Observable.of(x).delay(500));const all = busObservable
  .groupBy(obj => obj.code);
  .mergeMap(innerObs => innerObs.skip(1).map(obj => obj.value));//Alternatively could using filter and map
/*
const enUS = busObservable
  .filter(obj => obj.code === 'en-us')
  .map(obj => obj.value);const es = busObservable
  .filter(obj => obj.code === 'es')
  .map(obj => obj.value);const all = Rx.Observable.merge(enUS, es);
*/all.subscribe(x => console.log(x));

Using .map versus .switchMap

The below code snippet we can view the result of using .map versus .switchMap

//user.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from "rxjs";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

@Injectable()
export class UserService {

  constructor(private http: Http) { }

  getUsers(): Observable<any> {
    return this.http.get('http://jsonplaceholder.typicode.com/users')
        //.map(v => v.json());
        .switchMap(v => v.json());
  }

}
//app.component.ts
import { Component } from '@angular/core';
import { UserService } from "./user.service";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';

  constructor(private userService: UserService) {

  }

  search(term: string) {
    this.userService.getUsers()
        .subscribe(v => console.log(v));

    /*
    //we can do this with .switchMap
    this.userService.getUsers()
        .subscribe(v => {if (v.email != "Sincere@april.biz") {
          console.log(v.email);
        }});
     */
  }
}

Solving the multiple Async Pipe in Angular ≥ 2.0.0 with share operator

Remember to import import "rxjs/add/operator/share"; See reference here.

squareData$: Observable<string> = Observable.range(0, 10)
        .map(x => x * x)
        .do(x => console.log(`CalculationResult: ${x}`)
        .toArray()
        .map(squares => squares.join(", "))
        .share();  // remove this line: console will log every result 3 times instead of 1

Managing Cold and Hot Observables using publish().refCount() which is similar to .share()

ngOnInit() {
    // in angular 2 and above component.ts file add these    this.coldObservable();
    this.hotObservable();
}
/*
* cold observable is like a recast of video
* */
coldObservable() {
    let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1);
    incrementalObs.subscribe(val => console.log('a: ' + val));
    setTimeout(function() {
        incrementalObs.subscribe(val => console.log('      b: ' + val));
    }, 4500);
}


/*
* hot observable is like watching a live video
* */
hotObservable() {
    let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1).publish().refCount(); //can also use .share()
    incrementalObs.subscribe(val => console.log('a: ' + val));
    setTimeout(function() {
        incrementalObs.subscribe(val => console.log('      b: ' + val));
    }, 4500);
}

Observables Array Operations with flatMap

Reference Rangle.io article.

getLoadList(): void {
  this.shareService
      .fetchLoad()
      .take(1)
      .filter(response => {
        if ( response.status === 200 ) {
          return true;
        } else if ( response.status === 304 ) {
          // do something more
          return false;
        } else {
          this.gotoErrorPage();
          return false;
        }
      })
      .flatMap(response => response.data.loads as Load[])
      .filter(obj => obj.content.contentGrade === 'x')
      .subscribe(
          val => console.log(val),
          err => {
            console.error(err);
          });
}

Error Handling


Error handling in RxJS

Some learning points from RxJS lesson videos. This repo is available in my GitHub repo.

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

import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from "rxjs/BehaviorSubject";

import 'rxjs/add/observable/bindCallback';
import 'rxjs/add/observable/bindNodeCallback';
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/observable/concat';
import 'rxjs/add/observable/defer';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/observable/from';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/observable/fromEventPattern';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/observable/generate';
import 'rxjs/add/observable/if';
import 'rxjs/add/observable/interval';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/race';
import 'rxjs/add/observable/never';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/onErrorResumeNext';
import 'rxjs/add/observable/pairs';
import 'rxjs/add/observable/range';
import 'rxjs/add/observable/using';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/timer';
import 'rxjs/add/observable/zip';
import 'rxjs/add/observable/dom/ajax';
import 'rxjs/add/observable/dom/webSocket';
import 'rxjs/add/operator/buffer';
import 'rxjs/add/operator/bufferCount';
import 'rxjs/add/operator/bufferTime';
import 'rxjs/add/operator/bufferToggle';
import 'rxjs/add/operator/bufferWhen';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/combineAll';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/concatAll';
import 'rxjs/add/operator/concatMap';
import 'rxjs/add/operator/concatMapTo';
import 'rxjs/add/operator/count';
import 'rxjs/add/operator/dematerialize';
import 'rxjs/add/operator/debounce';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/defaultIfEmpty';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/delayWhen';
import 'rxjs/add/operator/distinct';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/distinctUntilKeyChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/exhaust';
import 'rxjs/add/operator/exhaustMap';
import 'rxjs/add/operator/expand';
import 'rxjs/add/operator/elementAt';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/find';
import 'rxjs/add/operator/findIndex';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/groupBy';
import 'rxjs/add/operator/ignoreElements';
import 'rxjs/add/operator/isEmpty';
import 'rxjs/add/operator/audit';
import 'rxjs/add/operator/auditTime';
import 'rxjs/add/operator/last';
import 'rxjs/add/operator/let';
import 'rxjs/add/operator/every';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/materialize';
import 'rxjs/add/operator/max';
import 'rxjs/add/operator/merge';
import 'rxjs/add/operator/mergeAll';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/mergeMapTo';
import 'rxjs/add/operator/mergeScan';
import 'rxjs/add/operator/min';
import 'rxjs/add/operator/multicast';
import 'rxjs/add/operator/observeOn';
import 'rxjs/add/operator/onErrorResumeNext';
import 'rxjs/add/operator/pairwise';
import 'rxjs/add/operator/partition';
import 'rxjs/add/operator/pluck';
import 'rxjs/add/operator/publish';
import 'rxjs/add/operator/publishBehavior';
import 'rxjs/add/operator/publishReplay';
import 'rxjs/add/operator/publishLast';
import 'rxjs/add/operator/race';
import 'rxjs/add/operator/reduce';
import 'rxjs/add/operator/repeat';
import 'rxjs/add/operator/repeatWhen';
import 'rxjs/add/operator/retry';
import 'rxjs/add/operator/retryWhen';
import 'rxjs/add/operator/sample';
import 'rxjs/add/operator/sampleTime';
import 'rxjs/add/operator/scan';
import 'rxjs/add/operator/sequenceEqual';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/single';
import 'rxjs/add/operator/skip';
import 'rxjs/add/operator/skipUntil';
import 'rxjs/add/operator/skipWhile';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/subscribeOn';
import 'rxjs/add/operator/switch';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/switchMapTo';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/takeLast';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/takeWhile';
import 'rxjs/add/operator/throttle';
import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/operator/timeInterval';
import 'rxjs/add/operator/timeout';
import 'rxjs/add/operator/timeoutWith';
import 'rxjs/add/operator/timestamp';
import 'rxjs/add/operator/toArray';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/window';
import 'rxjs/add/operator/windowCount';
import 'rxjs/add/operator/windowTime';
import 'rxjs/add/operator/windowToggle';
import 'rxjs/add/operator/windowWhen';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/zip';
import 'rxjs/add/operator/zipAll';
@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
    title = 'app works!';
    obs = Observable.of(1, 2, 3, 4);

    ngOnInit() {
        this.howToHandleErrorV1();
        this.howToHandleErrorV2();
        this.howToUseRetry();
        this.mergeObservableAndThrowError();
        this.mergeObservableAndErrorResumeNext();
        this.mergeObservableAndErrorCatch();    
    }

    
    
    /*
     * This uses Catch for V1. This introduces Closure. It is effectively the same as V2.
     * */
    howToHandleErrorV1() {
        this.obs
            .map(x => {
                if ( x === 3 ) {
                    throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
                }
                return x;
            })
            .catch(err => Observable.throw('Caught error here Observable.throw')) // continue go down the error path use Observable.throw
            .catch(err => Observable.of('Caught error here Observable.of')) // catch just use Observable.of
            .subscribe(
                x => console.log(x),
                err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
                () => console.log('done completed')
            );
    }

    
    
    /*
     * There is a difference between V1 and V2. For V2 it is using onErrorResumeNext which
     * */
    howToHandleErrorV2() {
        let good = Observable.of('Caught error here Observable.of');

        this.obs
            .map(x => {
                if ( x === 3 ) {
                    throw 'I hate threes'; // When it hit error it actually unsubscribe itself at x === 3 of throw error
                }
                return x;
            })
            .onErrorResumeNext(good) // To catch just use Observable.of
            .subscribe(
                x => console.log(x),
                err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
                () => console.log('done completed')
            );
    }

    
    
    /*
     * For this we use see it retries three times then console.error(err);
     * So retryWhen is for trying network connection websocket
     * */
    howToUseRetry() {
        this.obs
            .map(x => {
                if ( x === 3 ) {
                    throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
                }
                return x;
            })
            .retry(3) // retry three times
            .retryWhen(err => err.delay(2000).take(3)) // similar but with 2 seconds delay and the error is not propagated.
            .retryWhen(err => err.delay(2000).take(3).concat(Observable.throw('bad'))) // this it would throw an error.
            .subscribe(
                x => console.log(x),
                err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
                () => console.log('done completed')
            );
    }
/*
 * Using observable merge operator
 * */
mergeObservableAndThrowError() {
    let mergedObs = Observable.merge(
        this.obs, //1, 2, 3, 4
        Observable.throw('Stop Error'),
        Observable.from(this.array), //0, 1, 2, 3, 4, 5
        Observable.of(999) //999,
    );

    mergedObs.subscribe(
        val => console.log(val), //this should show 1, 2, 3, 4, Stop Error
        error => console.log(error),
        () => console.log("completed")
    );
}

/* Using observable onErrorResumeNext just like merge operator
 * */
mergeObservableAndErrorResumeNext() {
    let mergedObs = Observable.onErrorResumeNext(
        this.obs, //1, 2, 3, 4
        Observable.throw('Stop Error'),
        Observable.from(this.array), //0, 1, 2, 3, 4, 5
        Observable.of(999) //999,
    );

    mergedObs.subscribe(
        val => console.log(val), //this should show 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 999
        error => console.log(error),
        () => console.log("completed")
    );
}
/*
 * Using observable merge operator and catch
 * */
mergeObservableAndErrorCatch() {
    let mergedObs = Observable.merge(
        this.obs, //1, 2, 3, 4
        Observable.throw('Stop Error'),
        Observable.from(this.array), //0, 1, 2, 3, 4, 5
        Observable.of(999) //999,
    ).catch(e => {
        console.log(e);
        return Observable.of('catch error here');
    });

    mergedObs.subscribe(
        val => console.log(val), //this should show 1, 2, 3, 4, Stop Error, Catch Error Here
        error => console.log(error),
        () => console.log("completed")
    );
}
}

map vs flatMap in RxJS

Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable. See my GitHub repo.

obs = Observable.of(1, 2, 3, 4);

ngOnInit() {
    this.usingMap();
    this.usingMapToMakeInnerObservable();
    this.usingMapAndMergeAll();
    this.usingFlatMap();
}usingMap() {
    this.obs
        .map(x => x * 2) // transform the input by multiple of 2
        .subscribe(
            x => console.log(x),
            err => console.error(err),
            () => console.log('done completed')
        );
}
usingMapToMakeInnerObservable() {
    this.obs
        .map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
        //.map(x => Observable.timer(500).map((x) => x + 3)) // !!! REMEMBER Not the same as the immediate above
        .subscribe(
            x => console.log(x),
            err => console.error(err),
            () => console.log('done completed')
        );
}
// Map and Merge all is the same as just one FlatMap
usingMapAndMergeAll() {
    this.obs
        .map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
        .mergeAll()
        .subscribe(
            x => console.log(x),
            err => console.error(err),
            () => console.log('done completed')
        );
}
// Flat map is the same as map then merge all
// transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
usingFlatMap() {
    this.obs
        .flatMap(x => Observable.timer(500).map(() => x + 10)) // transform the input wrapping it with another observable and addition of 10
        .subscribe(
            x => console.log(x),
            err => console.error(err),
            () => console.log('done completed')
        );
}

Transforming pure Javascript array vs. Observable from array

See GitHub for source code

array = [0, 1, 2, 3, 4, 5];

ngOnInit() {
    this.setArrayToObservableThenTransform();
}/*
* This keeps creating new array. It is good that it creates new array of arr for immutability.
* But it's bad because there is clean up and resource intensive for mobile
* */
transformArray() {
    let result = this.array
        .filter(( x, i, arr ) => {
            console.log('filtering ' + x);
            console.log('is the source array ' + (arr === this.array));
            return x
        })
        .map(( x, i, arr ) => {
            console.log('mapping ' + x);
            console.log('is the source array ' + (arr === this.array));
            return x + '!';
        })
        .reduce(( r, x, i, arr ) => {
            console.log('reducing ' + x);
            return r + x;
        }, '--');

    console.log(result);
}

/*
* This is more efficient for resource management because it linearly scans and discard when not right
* */
setArrayToObservableThenTransform() {    let obsArray = Observable.from(this.array); // Use Observable.from() instead of Observable.of(). There is diff.    obsArray
        .filter(( x: any ) => {
            console.log('filtering ' + x);
            return x
        })
        .map(( x ) => {
            console.log('mapping ' + x);
            return x + '!';
        })
        .reduce(( r, x ) => {
            console.log('reducing ' + x);
            return r + x;
        }, '--')
        .subscribe(
            x => console.log(x)
        );
}

Using reduce and scan to aggregate RxJs data

See GitHub for source code

array = [0, 1, 2, 3, 4, 5];ngOnInit() {
    this.reduceArray();
    this.reduceObservableArray();
    this.reduceObservableArray_Abstract2();
    this.scanObservableArray();
}
/*
* This is the same as reduceObservableArray()
* */
reduceArray() {
    let result = this.array.reduce(
        (accumulator, currentValue) => accumulator + currentValue, 3
    ); // 3 is the init value.
    console.log('reduceArray ' + result); // output 18 => 3 + (0 ... 5)
}
/*
* This is the same as reduceArray()
* But this waits for all the arrays to finish emitting before reducing them to one single number
* See the next method to understand better
* */
reduceObservableArray() {
    let obsArray = Observable.from(this.array);
    obsArray.reduce(
        (accumulator, currentValue) => accumulator + currentValue, 3
    ).subscribe(
        val => console.log('reduceObservableArray ' + val)
    );
}
/*
* The exact same reduce function/method as of reduceObserableArray() above
* This proves that it waits for all 6 numbers to come in then reduce them
* */
reduceObservableArray_Abstract2() {
    let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
    obsArray.reduce(
        (accumulator, currentValue) => accumulator + currentValue, 3
    ).subscribe(
        val => console.log('reduceObservableArray_Abstract2 ' + val)
    );
}
/*
* This is the same as the above reduceObserableArray_Abstract2()
* except this is using scan instead of reduce
* */
scanObservableArray() {
    let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
    obsArray.scan(
        (accumulator, currentValue) => accumulator + currentValue, 3
    ).subscribe(
        val => console.log('scanObservableArray() ' + val)
    );
}

Create, next, and subscribe to Subject and BehaviorSubject

There is a Stack Overflow thread which discussed about the difference between Subject and BehaviorSubject. It’s worth understanding.

Also see my personal GitHub for source code.

app.component.ts

import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
// create subject
// there is no need for initial value
subject = new Subject<boolean>();

// create behaviorSubject which require initial value
// true is an initial value. if there is a subscription
// after this, it would get true value immediately
behaviorSubject = new BehaviorSubject<boolean>(true);
ngOnInit() {
    this.subject.next(false); /* Subject subscription wont get anything at this point before the subscribeSubject() */    this.subscribeSubject();
    this.subscribeBehaviorSubject();
}
/*
* Push the next val into the behavior subject
* */
nextSubject(val: boolean) {
    this.subject.next(val);
}

/*
* Any values push into the subject would not be can shown
* before this subscribeSubject() is called
* */
subscribeSubject() {
    this.subject
        //.take(1) //when we include .take(1) we will have a complete. Without this it will continue subscribing
        .subscribe(
            val => console.log(val),
            err => console.error(err),
            () => console.log('completed')
        );
}

/*
* This is the proper way to return a subject as observable
* */
getSubject(): Observable<boolean> {
    return this.subject.asObservable();
}

/*
 * Push the next val into the behavior subject
 * */
nextBehaviorSubject(val: boolean) {
    this.behaviorSubject.next(val);
}

/*
* For angular Behavior subject for a data service as a angular service often initializes
* before component and behavior subject ensures that the component consuming the
* service receives the last updated data even if there are no new
* updates since the component's subscription to this data.
* */
subscribeBehaviorSubject() {
    this.behaviorSubject
        // .first()
        .subscribe(
            val => console.log(val),
            err => console.error(err),
            () => console.log('completed')
        );
}

app.component.html

Subject:
<button (click)="nextSubject(true)">true</button>
<button (click)="nextSubject(false)">false</button>

<br>
BehaviorSubject:
<button (click)="nextBehaviorSubject(true)">true</button>
<button (click)="nextBehaviorSubject(false)">false</button>

Finally operator

usingFinallyOperator() {
    Observable
        .interval(500)
        .take(4)
        .finally(() => console.log('End of the observable, Hello World'))
        .subscribe(
            val => console.log('count taker ' + val)
        );
}

Stopping / Intercepting Observable

Imagine using Gmail where it allows you to undo email sent? We can produce similar experience with Observable

// subscription is created when an observable is being subscribed
subscription: Subscription;

// boolean variable for showing stop observable using takeWhile operator
isTrue: boolean = true;
/*
* basic interval can be used as delay too
* Imagine Gmail allows you to send and undo send within 4 seconds of sending
* Use Case: Perform an action 8 seconds later then intercept if user choose to undo the action
* */basicInterval() {
    let undoInSeconds: number = 8;
    this.subscription = Observable
            .interval(1000)
            .take(undoInSeconds)
            .takeWhile(() => this.isTrue)
            .subscribe(
                (val: number) => {
                    console.log(`${val + 1} seconds...         UNDO`);
                    ( val === (undoInSeconds - 1) ) ? console.log('Email sent / Action performed') : null;
                }
            );
}
/*
* This is to stop observable from continuing performance
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingUnsubscribe() {
    if (!!this.subscription) {
        this.subscription.unsubscribe();
        console.log('subscription: Subscription is unsubscribed');
    }
}

/*
* This is also to stop observable from continuing performance
* This method is more preferable than subscribing method then unsubscribe
 * Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingTakeWhile() {
    this.isTrue = false;
}

Perform conditional Reactive Form validation

This is my approach to performing conditional validation when using Angular. We will minimally manipulate the Observable of RxJS in this example. Let’s try by creating or using app.component.ts.

1 . Create a form that has two form controls reason and otherReason.

/* * Refer to angular official guide at https://angular.io/guide/reactive-forms on how to create reactive form with form controls * */createForm() {
    this.form = this.formBuilder.group({      reason: ['', Validators.required ],
      otherReason: [''],    });
}

2 . Create two methods addValidator and removeValidator.

/*  * For conditional form validation use  * */
private addValidator( control: AbstractControl, newValidator ){
    let existingValidators = control.validator;
    control.setValidators(Validators.compose([ existingValidators, newValidator ]));
    control.updateValueAndValidity();
}

/*  * For conditional form validation use  * */
private removeValidator( control: AbstractControl ){
    control.clearValidators();
    control.updateValueAndValidity();
}

3 . Create third method called conditionalFormValidation.

conditionalFormValidation( parentField, childField, matchValue = 'Others' )
{
    this.form
        .get(parentField)
        .valueChanges
        .forEach(( value: string ) => {
            const childFieldControl: AbstractControl = this.form.get(childField);
            if ( value === matchValue ) {
                this.addValidator(childFieldControl, Validators.required);
            } else {
                this.removeValidator(childFieldControl);
            }
        });
}

4 . Create or add to ngOnInit method

ngOnInit() {    
    this.createForm();
    this.conditionalFormValidation('reason', 'otherReason');
}

The outcome should illustrate that if we select ‘Others’ option in the dropdown list of reason, it should make otherReason form control field as required.

Drop-down selection for reason e.g. reason for absent from work
On selecting Others, the conditional form validation would make otherReason field turn into a required field

Perform manual operations after reading from Firebase database

When using AngularFire2 with Angular + Firebase, in getting a list of data from Firebase, we will get one observable instance but within that one observable is an array of N size. We can manually filter the array inside that one observable instance using arr.filter. It is different from RxJS .filter operator. Of course we can also flatten what is inside an array using .flatMap() operator. However, we’re going use JavaScript array filtering, instead of non-observable filtering, right after getting an observable object.

this.db.list(`/review`)
       .map(arr => arr.filter(item => item.rating > 3));

We can also reverse an array using JavaScript array reverse function. See reference. On a side note, using negative timestamp to reverse Firebase display is also another option.

this.db.list('/review')
       .map(arr => { return arr.reverse(); });

Besides those above, we can also use the response returned from AngularFire2 to perform “local filter/search”. This result can be valuable for autocomplete filtered list or searches. However, this approach below suffers severely in performance issue where at the magnitude of the size of the returned response from AngularFire2. E.g. if the list has N items. It has to iterate at least 1N. Perhaps an average of 2N.

getFilteredClientList(searchQuery: string): Observable<Client[]> {
    let query = searchQuery.trim().toLowerCase();

    return this.db.list(`/client`)
      .map((arr: Client[]) => {
        return arr.filter((item: Client) => {
          return item.email.toLowerCase().indexOf(query) === 0
            || item.given_name.toLowerCase().indexOf(query) === 0
            || item.family_name.toLowerCase().indexOf(query) === 0
        });
      });
  }

VS Code | Cookbook

Extensions

Copy installed extensions to another VS Code

code --list-extensions | xargs -I {} code_insiders --install-extension {}

Formatting

{
	"singleQuote": true,
	"trailingComma": "all",
	"tabWidth": 4,
	"semi": true
}

settings.json

"editor.formatOnSave": true,
"editor.tabCompletion": "on",
"files.exclude": {
	"resources": true,
	"package-lock.json": true,
	".editorconfig": true,
	"node_modules": true,
	"src/environments": true
}

Ionic | Cookbook

Routing and Navigation

Basic Angular Router configuration

Create a routing module that is ‘visible’ to all components in your app

With Angular CLI

ng generate module app-routing

With Ionic

ionic start my-app blank --type=angular

Match URL paths to Pages/Components

app-routing.modules.ts

const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: '**', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home',  loadChildren: './home/home.module#HomePageModule'    },
    { path: 'list',  loadChildren: './list/list.module#ListPageModule'    },
    { path: 'about', loadChildren: './about/about.module#AboutPageModule' }
]

Update routing module imports and exports

app-routing.modules.ts

imports { RouterModule, Routes } from '@angular/routes';
@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})

Do not forget to import the routing module to you main app module

app.module

import { AppRoutingModule } from './app-routing.module';
imports:[
    AppRoutingModule
]

Add a router-outlet to indicate where the pages will be rendered

app.component.html

<router-outlet></router-outlet>

Basics

src/app/app-routing.module.ts

const routes: Routes = [
   { path: 'hello', component: HelloPage }
];

app.component.html

<router-outlet></router-outlet>
  // Regular Route
  { path: 'eager', component: MyComponent },

  // Lazy Loaded Route (Page)
  { path: 'lazy', loadChildren: './lazy/lazy.module#LazyPageModule' },

    // Redirect
  { path: 'here', redirectTo: 'there', pathMatch: 'full' }
];
<ion-button href="/hello">Hello</ion-button>
<a routerLink="/hello">Hello</a>
<a [routerLink]="['/product',product.id]"></a>

Navigate Programmatically

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

@Component({ ... })
export class HomePage {
  constructor(private router: Router) {}

  go() {
    this.router.navigateByUrl('/animals');
  }
}

Navigate to Dynamic URLS

const routes: Routes = [
  // Regular Route
  { path: 'items/:id', component: MyComponent },
];
<ion-button href="/items/abc">ABC</ion-button>
<ion-button href="/items/xyz">XYZ</ion-button>

Passing parameter

Passing parameter with state service

export class ComponentA {
   constructor(private stateService: StateService) {}
   goToComponentB(): void {
        this.stateService.data = {...};
        this.router.navigate(['/b']);
    }
}
export class ComponentB implements OnInit {
    constructor(private stateService: StateService) {}
    ngOnInit(): void {
        this.data = this.stateService.data;
        this.stateService.data = undefined;
    }
}

Passing parameter in link

export class ComponentA {
   constructor(private router: Router) {}
   
   goToComponentB(): void {
      this.router.navigate(['/b'], {state: {data: {...}}});
   }
}
go() {
   this.router.navigate(['../list'], { relativeTo: this.route });
}

Passing parameter in routerlink directive

<a [routerLink]=”/b” [state]=”{ data: {...}}”>Go to B</a>

Extracting the data

The state property was added to Navigation which is available through Router.getCurrentNavigation().extras.state.

Problem is that getCurrentNavigation returns Navigation only during the navigation and returns null after the navigation ended. So the Navigationis no longer available in Component’s B onInit lifecycle hook. We need to read the data from browser’s history object:

history.state.data

Extract Data from Routes with ActivatedRoute

When working with dynamic data, you need to extract the params from the URL.

For example, you might want to read from the database when the user navigates to /items/:id, using the ID from the route to make a query.

Angular has an ActivatedRoute service that allows us to grab information from the current route as a plain object or Observable.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({ ... })
export class ProfileComponent implements OnInit {

  id: string;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.id = this.route.snapshot.paramMap.get('id');
  }
}

Or if we need to react to changes, we can subscribe to an Observable.

ngOnInit() {
   this.route.paramMap.subscribe(params => {
      this.products.forEach((p: Product) => {
        if (p.id == params.id) {
          this.product = p;
        }
      });
    });
}
ngOnInit() {
   this.products.forEach((p: Product) => {
      if (p.id == this.route.snapshot.params.id) {
         this.product = p;
      }
   });
}

Migrate from Ionic X to Ionic 4 Routing

Set Root

<ion-button href="/support" routerDirection="root">

or in class

this.navCtrl.navigateRoot('/support');

Push

<ion-button href="/products/12" routerDirection="forward">
this.navCtrl.navigateForward('/products/12');

Pop

<ion-button href="/products" routerDirection="backward">
<ion-back-button defaultHref="/products"></ion-back-button>

Navigate backwards programatically:

this.navCtrl.navigateBack('/products');

Routing in Tabs

{
  path: 'contact',
  outlet: 'modal',
  component: ContactModal
}
http://.../(modal:contact)

Lazy Loading

Code Snippets

// home.module.ts
@NgModule({
  imports: [
    IonicModule,
    RouterModule.forChild([{ path: '', component: HomePage }])
  ],
  declarations: [HomePage]
})
export class HomePageModule {}
// app.module.ts
@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    RouterModule.forRoot([
      { path: 'home', loadChildren: './pages/home/home.module#HomePageModule' },
      { path: '', redirectTo: 'home', pathMatch: 'full' }
    ])
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Code Snippet

app-routing.module.ts
const routes: Routes = [
  { path: 'about', loadChildren: './about/about.module#AboutPageModule' },
];
about/about.module.ts
const routes: Routes = [
  { path: '', component: AboutPage },
];

Using Guards

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {

    const loggedIn = false; // replace with actual user auth checking logic

    if (!loggedIn) {
      this.router.navigate(['/']);
    }

    return loggedIn;
  }
}
const routes: Routes = [
  { path: 'special', component: SpecialPage, canActivate: [AuthGuard] },
];

Troubleshooting of Routing

Enable tracing

 @NgModule({
    imports: [
        RouterModule.forRoot(routes, 
            { enableTracing: true }
        )],
    exports: [RouterModule],
})
export class AppRoutingModule {}

Common Errors and mistakes

Placing global routing patterns at the front ot routing array

const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: '**', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home', component: HomePageComponent },

Finding the right path is a sequential process in search all entries in route[] and select the first with a matting path. So, in our wrong example, every path matches the common pattern ‘**’.

Solution: Put this matting pattenr at the end of the routes[] array

const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home', component: HomePageComponent },
    { path: '**', redirectTo: '/home', pathMatch: 'full' },

Storage

Configuration

import { IonicStorageModule } from '@ionic/storage';

@NgModule({
  declarations: [
    // ...
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    IonicStorageModule.forRoot()
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    // ...
  ],
  providers: [
    // ...
  ]
})
export class AppModule {}
Finally, inject it into any of your components or pages:

import { Storage } from '@ionic/storage';

export class MyApp {
  constructor(private storage: Storage) {
      storage.set('name', 'Max');

      storage.get('age').then((val) => {
          console.log('Your age is',  val);
});
}
}

Code sample

class MyClass {
  constructor(public storage: Storage) {}

  async setData(key, value) {
    const res = await this.storage.set(key, value);
    console.log(res);
  }

  async getData(key) {
    const keyVal = await this.storage.get(key);
    console.log('Key is', keyVal);
  }
}

Storage with Capacitor

import { Plugins } from '@capacitor/core';

const { Storage } = Plugins;

async setObject() {
  await Storage.set({
    key: 'user',
    value: JSON.stringify({
      id: 1,
      name: 'Max'
    })
  });
}

async getObject() {
  const ret = await Storage.get({ key: 'user' });
  const user = JSON.parse(ret.value);
}

async setItem() {
  await Storage.set({
    key: 'name',
    value: 'Max'
  });
}

async getItem() {
  const value = await Storage.get({ key: 'name' });
  console.log('Got item: ', value);
}

async removeItem() {
  await Storage.remove({ key: 'name' });
}

async keys() {
  const keys = await Storage.keys();
  console.log('Got keys: ', keys);
}

async clear() {
  await Storage.clear();
}

Components

Alerts

Code Snippets

showAlert() {
  this.alertCtrl.create({
    message: "Hello There",
    subHeader: "I'm a subheader"
  }).then(alert => alert.present());
}

// Or using async/await

async showAlert() {
  const alert = await this.alertCtrl.create({
    message: "Hello There",
    subHeader: "I'm a subheader"
  });

  await alert.present();
}

Local Notifications

import { Plugins } from '@capacitor/core';
const { LocalNotifications } = Plugins;

LocalNotifications.schedule({
  notifications: [
    {
      title: "Title",
      body: "Body",
      id: 1,
      schedule: { at: new Date(Date.now() + 1000 * 5) },
      sound: null,
      attachments: null,
      actionTypeId: "",
      extra: null
    }
  ]
});

Custom Components

Create custom component

$ ionic generate component components/Sample
> ng generate component components/Sample
CREATE src/app/components/sample/sample.component.scss (0 bytes)
CREATE src/app/components/sample/sample.component.html (25 bytes)
CREATE src/app/components/sample/sample.component.spec.ts (628 bytes)
CREATE src/app/components/sample/sample.component.ts (270 bytes)
UPDATE src/app/components/components.module.ts (621 bytes)
[OK] Generated component!
$ ionic generate module components/Components --flat
> ng generate module components/Components --flat
CREATE src/app/components/components.module.ts (194 bytes)
[OK] Generated module!

Modify selector for component in app/components/sample/sample.component.ts

@Component({
    selector: 'c-sample',
    templateUrl: './c-sample.component.html',
    styleUrls: [ './c-sample.component.scss' ]
})

Rename files for component

cd src/app/components/sample
mv sample.component.html c-sample.component.scss
mv sample.component.html c-sample.component.html
mv sample.component.html c-sample.component.spec.ts
mv sample.component.html c-sample.component.ts

Export created component in app/components/components.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';

import { SampleComponent } from './sample/sample.component';

@NgModule({
    imports: [ CommonModule, IonicModule.forRoot(), ],
    declarations: [ SampleComponent ],
    exports: [ SampleComponent ],
    entryComponents: [],
})
export class ComponentsModule { }

Add page to display the component

$ ionic generate page pages/Sample
> ng generate page pages/Sample
CREATE src/app/pages/sample/sample.module.ts (543 bytes)
CREATE src/app/pages/sample/sample.page.scss (0 bytes)
CREATE src/app/pages/sample/sample.page.html (133 bytes)
CREATE src/app/pages/sample/sample.page.spec.ts (691 bytes)
CREATE src/app/pages/sample/sample.page.ts (256 bytes)
UPDATE src/app/app-routing.module.ts (539 bytes)
[OK] Generated page!

Add custom component to new page sample.page.html

<ion-content padding>
    <c-sample></c-sample>
</ion-content>

Register components module in sample.module.ts

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';

import { ComponentsModule } from 'src/app/components/components.module';
import { SamplePage } from './sample.page';

const routes: Routes = [ { path: '', component: SamplePage } ];

@NgModule({
    declarations: [SamplePage],
    imports: [
        CommonModule,  IonicModule,
        RouterModule.forChild(routes),
        ComponentsModule
    ],
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class SamplePageModule {}

Check Routing in app-routing.modules.ts

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home',   loadChildren: './pages/home/home.module#HomePageModule' },
  { path: 'list',   loadChildren: './pages/list/list.module#ListPageModule' },
  { path: 'sample', loadChildren: './pages/sample/sample.module#SamplePageModule' }
];

Add new page to sidemenu in app.components.ts

  public appPages = [
    { title: 'Home', url: '/home', icon: 'home' },
    { title: 'List', url: '/list', icon: 'list' },
    { title: 'Sample Component', url: '/sample', icon: 'list' }
  ];

Directives

Pipes

HTML Elements

Access HTML Element from Page Class

<div #box></div>
@ViewChild('box', {static: false}) el_box:ElementRef;
box: any;

constructor() {
    this.box = this.el_box.nativeElement;
}

Grabbing Ionic Components with ViewChild

Let’s imagine we have a HomePage component that looks like this and we want to close the menu when an item is clicked.

<ion-menu>
</ion-menu>

Our goal is to access the ion-menu from the TypeScript code so we can call its API methods, like open() and close().

import { Component, ViewChild } from '@angular/core';
import { Menu } from '@ionic/angular';

@Component(...)
export class HomePage {

  @ViewChild(Menu, {static: false})) menu: Menu;

  onDrag() {
    this.menu.close();
  }
}

Explanation:

{ static: false }

If you set static false, the component ALWAYS gets initialized after the view initialization in time for the ngAfterViewInit/ngAfterContentInit callback functions.

{ static: true}

If you set static true, the initialization will take place at the view initialization at ngOnInit

Shortcut: Use Template Variables

There’s actually a very convenient shortcut to using ViewChild in a component. We never have to leave the HTML by setting a template variable in Angular. In this example we reference the menu component with a hashtag and variable name #mymenu.

<ion-menu #mymenu>
  <ion-item (click)="mymenu.close()"></ion-item>

Grabbing Multiple Components with ViewChildren

You might also run into a situation where there are multiple components of the same type on the page, such as multiple FABs:

<ion-fab></ion-fab>
<ion-fab></ion-fab>
<ion-fab></ion-fab>

ViewChildren is almost the same, but it will grab all elements that match this component and return them as an Array.

import { Component, ViewChildren } from '@angular/core';
import { Fab } from '@ionic/angular';

@Component(...)
export class HomePage {

  @ViewChildren(Fab, {static: false})) fabs: Fab[];


  closeFirst() {
    this.fabs[0].close();
  }
}

Now that you know about ViewChild, you should have no problem accessing the API methods found on Ionic’s web components.

Loops in HTML Elements

<ul>
   <li *ngFor="let number of [0,1,2,3,4]">
      {{number}}
   </li>
</ul>
<ul>
  <li *ngFor='#key of [1,2]'>
    {{key}}
  </li>
</ul>
<ul>
  <li *ngFor='#val of "0123".split("")'>{{val}}</li>
</ul>
<ul>
  <li *ngFor='#val of counter(5) ;#i= index'>{{i}}</li>
</ul>

export class AppComponent {
  demoNumber = 5 ;

  counter = Array;

  numberReturn(length){
    return new Array(length);
  }
}

Display Array

<ion-grid class="board">
  <ion-row *ngFor="let r of [0,1,2]">
    <ion-col col-4 class="cell" *ngFor="let c of [0,1,2]" (click)="handle(c+r*3)">
            {{squares[c+r*3]}}
    </ion-col>
  </ion-row>
</ion-grid>

Add function to Button click

<ion-item (click)="onClick($event)">
onClick(ev: any){
	this.log('onClick', 'event=' + ev);
}

Change CSS class on click

Add handler to html element

<a class="btn" (click)='toggleClass($event)'>
    <ion-icon class="icon" name="bluetooth"></ion-icon>
</a>

Import Render2 in page.ts

import { Component, OnInit, Renderer2 } from '@angular/core';
...

constructor(private renderer: Renderer2) { }

Write handler to toggle class

toggleClass(event: any) {
    const classname = 'active';

    if (event.target.classList.contains(classname)) {
        this.renderer.removeClass(event.target, classname);
    } else {
        this.renderer.addClass(event.target, classname);
    }
}

Migrating to Ionic 4

Replace Http Module with HttpClient

Changes in app.module.ts

import { HttpClientModule } from '@angular/common/http';
@NgModule({
    declarations: [AppComponent],
    entryComponents: [],
    imports: [
        ...
        HttpClientModule
        ...
    ],

Changes in service.ts

import { Http } from '@angular/http';
import { HttpClient } from '@angular/common/http';
constructor(public http: Http) { }
constructor(public httpClient: HttpClient) { }

Troubleshooting

ERROR: Unexpected end of JSON input while parsing near

npm cache clean --force
npm install -g @angular/cli

Bootstrap | Coobook

Layout and Positioning

From here.

Now that Bootstrap 4 is flexbox by default, vertical alignment gets a little easier. In general, there are 3 different approaches to vertical alignment…

  1. Auto-margins
  2. Flexbox utilities
  3. Display utilities along with the Vertical Align utilities.

At first, the “Vertical Align” utilities would seem an obvious choice, but these only work with inline and table display elements. Consider the following vertical alignment options and scenarios.

In general, there are 2 types of vertical alignment scenarios you’ll encounter…

  1. vertical centering within a parent container.
  2. or, vertical centering relative to adjacent elements.

1. Vertical Center Using Auto Margins

One way to vertically center is to use my-auto. This will center the element within it’s flexbox container (The Bootstrap 4 .row is display:flex). For example, h-100 makes the row full height, and my-auto will vertically center the col-sm-12 column.

<div class="row h-100">
   <div class="col-sm-12 my-auto">
     <div class="card card-block w-25">Card</div>
   </div>
</div>

my-auto represents margins on the vertical y-axis, and is equivalent to:

margin-top: auto;
margin-bottom: auto;

Demo — Vertical Center Using Auto Margins

2. Vertical Center with Flexbox

Since the Bootstrap 4 .row class is now display:flex you can simply use the new align-self-center flexbox utility on any column to vertically center it:

<div class=”row”>
   <div class=”col-6 align-self-center”>
      <div class=”card card-block”>
      Center
      </div>
   </div>
   <div class=”col-6">
      <div class=”card card-inverse card-danger”>
      Taller
      </div>
   </div>
</div>

or, use align-items-center on the entire .row to vertically center align allcol-* (columns) in the row…

<div class=”row align-items-center”>
  <div class=”col-6”>
     <div class=”card card-block”>
     Center
     </div>
  </div>
  <div class=”col-6”>
     <div class=”card card-inverse card-danger”>
     Taller
     </div>
  </div>
</div>

Demo — Vertical Center Different Height Adjacent Columns

3. Vertical Center Using Display Utils

Bootstrap 4 has now has display utils that are used a wrapper for the CSS display propery such asdisplay:blockdisplay:inlinedisplay:table-cell, display:none, etc.. These can be used with the vertical alignment utilsto align inline, inline-block or table cell elements.

<div class="row h-50">
  <div class="col-sm-12 h-100 d-table">
    <div class="card card-block d-table-cell align-middle">
    I am groot
    </div>
  </div>
</div>

Demo — Vertical Center Using Display Utils

More Vertical Center Examples

Bootstrap 4 Vertical Center in Equal Height Cards
Bootstrap 4 Vertical Centering with Cards