Angular / Firebase / Authentication

This blog post is a little out of the ordinary since it is not related in any way to ServiceNow. Well except for the common technologies like html, css, and javascript.

In my spare time I like to develop websites using Angular, Ionic Framework and Firebase has hosting. I like Firebase a lot because so many features are tightly integrated like authentication and database.

So for a while I have been working on implementing authentication in a web application. I would like to be able to use Google and Facebook for login. I have seen several Firebase courses, read numerous tutorials on this subject. Common to all of them is that they are more than 1 year old and about a year ago a major change was made in Firebase so code examples are using outdated methods/modules etc and using AngularFire compat mode. I might be wrong about this but when I hear something is used in compatibility mode it means compromises and missing functionality. So I want to avoid using the compat versions of AngularFire.

Finally I succeeded in getting authentication to work with Angular 15, AngularFire 7.5 and Firebase 9.15. In this blog post I will describe the simplest web app that just adds a login and logout button. It will not include auth gaurds. At least not at this time. I might add this at a later stage.

Setup project

Login to Firebase console, create an account if you don’t have one. It is free and authentication is available within the free tier. Create a new project and in authentication enable the google provider. In this example I will only use use since it is easy to add more once you got the basic logic established.

Then install following

npm install -g @angular/cli
npm install -g @ionic/cli
npm install -g firebase-tools

Switch to a folder where you want your project to live and run this command, Choose Angular and blank template. You can say no to create account. The Ionic start command will create a new folder for the project.

ionic start auth-test
cd ./auth-test

Then it is time to add AngularFire. When this command runs you are asked about which project you want to use and what services you want to use. To start with just select Authentication. You can always add other services later.

ng add @angular/fire

At this point you can check if the app is working by running

ionic serve

Modify code

Now it is time to add the code for authentication

Run following commands to add login page and authentication service.

ionic g page login
ionic g service services/auth

Next open app.module.ts the code should look something like this

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { environment } from '../environments/environment';
import { provideAuth, getAuth } from '@angular/fire/auth';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideAuth(() => getAuth())
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule { }

Next open auth.service.ts the code should look like this, the service only sets up an observable to give the app the information wether logged in or not you can add more your self such as the full user detals and the token with claims etc.

import { Injectable } from '@angular/core';
import { Auth, onAuthStateChanged } from '@angular/fire/auth';
import { GoogleAuthProvider, signInWithPopup, browserLocalPersistence } from "firebase/auth";
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  signedIn$ = new BehaviorSubject<Boolean>(false);

  constructor(private auth: Auth) {

    this.auth.setPersistence(browserLocalPersistence);
    onAuthStateChanged(auth, (user) => {
      if (user) {
        this.signedIn$.next(true);
      } else {
        this.signedIn$.next(false);
      }
    });
  }

  loginWithGoogle() {
    const provider = new GoogleAuthProvider();
    signInWithPopup(this.auth, provider)
      .then((result) => {
        console.log(result);
      }).catch((error) => {
        console.log(error);
      });
  }

  logout() {
    this.auth.signOut();
  }

}

Then open login.page.ts and the code should look like this, again keeping it at minimum code

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../services/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  styleUrls: ['./login.page.scss'],
})
export class LoginPage implements OnInit {
  signedIn: Boolean = false;

  constructor(private authService: AuthService) {
    this.authService.signedIn$.subscribe((result) => {
      console.log(result);
      this.signedIn = result;
    });
  }

  ngOnInit() {
  }

  loginGoogle() {
    this.authService.loginWithGoogle();
    this.dataService.addNewEvent();
  }
  loginFacebook() {
    this.authService.loginWithFacebook();
  }

  logout() {
    this.authService.logout();
  }
}

The final thing should be in login.page.html it should look something like this

<ion-header>
  <ion-toolbar>
    <ion-title>login</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

  <div *ngIf="signedIn; else showLogin">
    <ion-button (click)="logout()">
      <ion-icon slot="start" name="log-out-outline"></ion-icon>Logout
    </ion-button>
  </div>
  <ng-template #showLogin>
    <ion-button (click)="loginGoogle()">
      <ion-icon slot="start" name="log-in-outline"></ion-icon>Login with Google
    </ion-button>

  </ng-template>

</ion-content>

I know the observable signedIn$ could be used with async pipe etc.

Test it

If I remembered everything you should be able to test login now. Run the app with

ionic serve

If the app opens without error navigate manually to /login and click on the button to login with google. You should get an option to choose google acount and then you will be logged in and the login button changes to logout.

Emulators

How to connect to the emulators was also changed so here is how I got those to work with version 9 of Firebase.

To connect to the auth emulator you need to do a few more things

First step is to enable/install the emulator, run the command and answer the questions

firebase init emulators

Once installed you can start the emulator(s) with

firebase emulators:start

But the app is not connected to the emulator yet.

In app.component.ts add following

import { Auth, connectAuthEmulator} from '@angular/fire/auth';
import { environment } from '../environments/environment';

....
constructor(private auth: Auth) {
if (environment.useEmulators) {
      connectAuthEmulator(this.auth, 'http://localhost:9099');
    }
}

....

In environment.ts and environment.prod.ts add useEmulators variable and set to true in dev and false in prod.

Now when you start the app it should connect to the emulator instead.

Finished

I hope this helps. I do neither claim this is the best or most correct way. It is what I came up with to avoid using the compat version of AngularFire.

Any comments are welcome. I cannot guarantee I can answer any questions 🙂

San Diego – Unified navigation

So San Diego has been out for a while and I have some thoughts about the unified navigation in the new Next Experience.

Generally I like the unified navigation but there are also a few things I do not like.

Like

  • More space on the screen
  • Updated styling

Do not like

  • The pinning. Only one of the items “All”, “Favorites”, “History” and “Workspaces” can be pinned at a time. I would like the option to pin them all in a tabbed view like in UI16.
  • Searching. In UI16 when typing in the navigator both favorites and the entire menu is searched. Now only one list is searched at a time.
  • Searching. Even worse is that it requires 2 clicks before you can search if the tab is not pinned. First one click to open the list and then a click in the search box. It should be so that the search box automatically gets focus when opening the list so we can start typing immediately.
  • Searching. And when you have a filter applied the clear filter button only appears after you click in the filter box. The clear button should be visible next to the filter so it can be cleared without having to activate the filter box.
  • Transparency of the opened navigator when not pinned. I think it should be less transparent, the text and different background colors visible under it is distracting as it is now.

Thats it for now. I am currently working on a script to duplicate any configurable workspace. Contact me if you are interested in this.

Next Experience

What is Now Framework?

At the time of writing (14-02-2022) this article is really about my first attempts with Now Experience Framework. However as soon as San Diego is GA and the CLI tools have been updated I will resume my exploration of the new Next Experience and update this article as I go along. So for now the rest of this article is something I wrote in 2020.

Now Framework is the new UX Framework for ServiceNow introduced with Agent Workspace.

With the release of Orlando ServiceNow has released the tools for us to develop custom components that can be added to a landing page or as a context sidepanel. It is even possible to build a standalone (undocumented) portal based on components.

How to get started?

Now Experience is based on standard web components directly supported by most browsers. So not longer AngularJS or Angular and definately no longer Jelly.

I think it is essential to understand how web components work in order to be successful in building components for Now Experience.

I highly recommend starting with some online courses about Web components. For example following:

What next?

Undocumented stuff

How to open a new tab

 dispatch(REFERENCE_INFO_CLICK, {
    "referenceInfoClick": {
        // Table of record you want to open
        "referencetable": "sys_user", 

        // Record you want to open sys_id
        "sys_id": "6816f79cc0a8016401c5a33be04be441" 
    }
});

Tips

When re-deploying a component you MUST clear the browser cache as well as use CTRL-F5 when reloading the Agent Workspace page otherwise the new version of the component is NOT loaded by the browser. Very annoying, hopefully this will be fixed.

now-ui.json

When using any OOB NOW components it is necessary to update the property “inner components” in the file now-ui.json with all components including any sub components that these components use.

For example if using “now-template-card” then inner components should look something like this

	"components": {
		"x-80603-test-comaround": {
			"innerComponents": [
				"now-button",
				"now-heading",
				"now-loader",
				"now-card",
				"now-template-card",
				"now-template-card-attachment",
				"now-template-card-omnichannel"
			],
			"uiBuilder": {
				"associatedTypes": [
					"global.core"
				],
				"label": "ComAround Context Sidepanel",
				"icon": "calendar-clock-fill",
				"description": "Searches for knowledge articles in ComAround based on short description field of displayed record."
			}
		}
	},
	"scopeName": "x_80603_test_com_2"
}

Powershell

If you work with components and deploy on multiple instances you will very quickly get tired og the now-cli login command. So I have setup a number of powershellscripts so I can quickly switch between components and at the same time login to the instance the component belongs to.

I created a git repository here. You can download it and customize it. It has lots of examples like for docker. I got this repo originally when I attended a Angular Developer Certification course last year. (so it is very much course related)

What I did in my copy local I created a new folder “servicenow” and in there I created these functions to quickly switch.

First a change dir function

function goto-snc-git {
  cd "d:\GitReposServiceNow"
  pwd
}

Then a login function, yes contains cleartext userid and pasword so do not commit to repo

function snc-login-pdi {
  now-cli login --host https://xxxx.service-now.com/ --username xxx --password xxxxx
}

And then the last one

function pdi {
  goto-snc-git
  snc-login-pdi
}

Future

Build small and specific, because we have not yet seen the full extent of ServiceNow’s strategy with Now Experience. Quebec will be a game-changer.

So what are you waiting for, get going and build components.

Here is a small demo of my first poc component

Search-Now application

Some years ago I developed a windows frontend for ServiceNow’s Code search feature. This worked just fine. But technologies develop and I learn new stuff like Angular and PWA Progressive Web Aps.

So I decided to convert the windows app to a PWA. A website built with Ionic, Capacitor and Angular. This means it is truly cross platform compatible.

Configuration in ServiceNow

Newer instances do not have the search group “ServiceNow CodeSearch”. You can download that here.

Install on device.

The app can be installed on home screen. I do not have Android so cannot say exactly how it is done on these devices.

On iPhone and iPads open Safari and open the url: https://search-now.app/ when loaded you can add the app to your home screen by clicking on the “Share” icon and selecting add to homepage. Done. Now you can run the app directly from you iPhone or iPad.

When navigating to the site in chrome on windows there is also an install app option in the “….” menu.

Site url: https://search-now.app/

Configure app

When the app is started first time you are directed to the tab to add instances.

Then click on NEW

and fill in the information then click on add.

You can edit, delete and copy instances by swiping to the left which reveals the buttons.

It is also possible to manually enter instances by editing the config file directly.

Click on

This opens a text field with the config file in json format. It is an array of objects.

For easy editing I suggest you user “copy to clipboard” and switch to an editor, edit the file then copy it and use “Past from clipboard” to insert the file again. Make sure the structure is correct. Formatting does not matter.

This is how the file should look like.

[
  {
    "id": "e8a14f02-2290-43d6-a7a8-86d607b931fd",
    "name": "PDI",
    "url": "https://xxxxxx.service-now.com",
    "user": "userid",
    "password": "password",
    "cloudService": false
  }
]

Swipe action on the instance list reveals buttons to delete, copy and edit an instance.

Note! Currently there is no warning when deleting.

Searching

You can now start searching. First select the instance, then group. (table not implemented yet) enter search term and click on search. Searching takes a little while but then the results are displayed. You can open each search result directly in ServiceNow by clicking on the OPEN link.

Updating app

When a new version is available a popup appear and you can update the app to the latest version.

More screenshots

Inbound Actions

Managing inbound actions can be a nightmare. I often see the out-of-box inbound actions duplicated and modified. The duplication is not really the problem as such because we do require 3 inbound action per table we want to handle inbound emails for.

The problem is that nobody seems to consider centralizing the code so you only need to maintain the code in one place. The worst example I have seen the same code has been duplicated 200 times and if for some reason there is a change in how the emails should be registered all 200 inbound actions needs to be updated.

So I have designed a model for how I propose new inbound actions should be managed.

The code is based on the out-of-box inbound actions for create and update incident (“Create Incident”, “Update Incident” & “Create Incident (Forwarded)”).

The reply inbound action

This is how I would create the inbound action for new

(function runAction( /*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

  new global.InboundActionHelper().newEmail(current, event, email, logger, classifier);

})(current, event, email, logger, classifier);

The update inbound action

(function runAction( /*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

  new global.InboundActionHelper().replyEmail(current, event, email, logger, classifier);

})(current, event, email, logger, classifier);

The forward inbound action

(function forwardEmail( /*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

  new global.InboundActionHelper().newEmail(current, event, email, logger, classifier);

})(current, event, email, logger, classifier);

The script include

And then all the code would be in the script include, this code could be made more shorter. For example a lot of the code in new and forward is the same code and could be moved to a common function that is executed for both reducing the redundant code. Leaving only the code that really is specific for new, forward and reply in the specific functions.

var InboundActionHelper = Class.create();
InboundActionHelper.prototype = {
  initialize: function () {
  },

  newEmail: function (current, event, email, logger, classifier) {
    current.caller_id = gs.getUserID();
    current.comments = "received from: " + email.origemail + "\n\n" + email.body_text;
    current.short_description = email.subject;
    current.category = "inquiry";
    current.incident_state = IncidentState.NEW;
    current.notify = 2;
    current.contact_type = "email";

    if (email.body.assign != undefined)
      current.assigned_to = email.body.assign;

    if (email.importance != undefined) {
      if (email.importance.toLowerCase() == "high") {
        current.impact = 1;
        current.urgency = 1;
      }
    }
    current.insert();
  },

  replyEmail: function () {
    gs.include('validators');

    if (current.getTableName() == "incident") {
      current.comments = "reply from: " + email.origemail + "\n\n" + email.body_text;

      if (gs.hasRole("itil")) {
        if (email.body.assign != undefined)
          current.assigned_to = email.body.assign;

        if (email.body.priority != undefined && isNumeric(email.body.priority))
          current.priority = email.body.priority;
      }

      current.update();
    }
  },

  forwardEmail: function () {
    current.caller_id = gs.getUserID();
    current.comments = "forwarded by: " + email.origemail + "\n\n" + email.body_text;
    current.short_description = email.subject;

    current.category = "inquiry";
    current.incident_state = IncidentState.NEW;
    current.notify = 2;
    current.contact_type = "email";

    if (email.body.assign != undefined)
      current.assigned_to = email.body.assign;

    current.insert();
  },

  type: 'InboundActionHelper'
};

Why do this

As you can see the inbound actions are very similar and since servicenow have initilized current with the target table you can use that to determine what other actions you need to do in case you have to something special if email is coming from someone specific, sent to a specific email or contans certain words etc.

All the code is centralized in the script include so it will be much easier to make a general change to all inbound actions just by editing in one place.

Another advantage of this is it will be easier to implement a method of testing inbound actions in test instead of in prod. Many times a customer got several email adresses and the cases created is often assigned based on the email address the email was sent to. This can only truely be tested in prod as there is only one email address and that is redirected to the prod instance. Instead one could implement a test model that simulates email was sent to a specific address be looking at who sent it. So if user A sent the email then treat the email as if it was sent to email1@domain.com if the email was sent bu user B then treat the email as if it was sent o email2@domain.com etc.

Suggestions

Suggestions are welcome. I hope developers will start adopt this idea/model so customes can have a manageable set of inbound actions.

Which WYSIWYG editor to use in ServiceNow?

In the last two projects I have worked on the TinyMCE editor plays a huge part of the applications I developed for the Service Portal (a dynamic pdf generator and a new email client supporting secure emails).

I found the editor to be very difficult to customize and prevent it from altering the html content. The latest issue I have encountered is an issue with new lines. The editor inserts either P or BR. The problem is that when BR is inserted it is the version <br /> and we found that in rare cases this can actually lead to loss of editor content.

So I am looking for alternative editors for future projects.

So far I have tested following alternatives very briefly. The tests consists of only a few things so far:

  • Can I instantiate the editor and edit content?
  • Can I get the content via event when user has modified it?
  • Can I set the content for example when user clicks a button?
  • Can I insert images from clipboard

ProductTypeWorksCommentsLink
TinyMCE 5CommercialNoNot compatibletiny.cloud
CKeditor 5Commercial NoUnable to testckeditor.com
Froala 4Commercial YesEasy pdf exportfroala.com
QuillOpen SourceYesquilljs.com
PellOpen SourceYesjaredreich.com/pell

I still need to do more thourough testing but I kind of like Froala.

As for TinyMCE 5. This seems like the obvious choice since ServiceNow is already using version 4. However version 5 requires the page to be rendered in standards mode and that is not the case in ServiceNow.

Do You know any editors that will work in Service Portal or as a component in Now Experience Framework ?

If you do please comment below or send me a message on LinkedIn.

Widget became slow after upgrading from Paris to Quebec

Applies to Quebec and Rome releases.

Yesterday I learned something surprising about Service Portal Widgets. Maybe other developer already know this but it was a surprise to me. Even after I have worked with the new Service Portal since it was released. The problem first appeared after upgrading my PDI to Paris and Rome.

I am building a service portal application with multiple widgets that communicate via angular service. So when a button is clicked in one widget a message is sent and the other widgets acts accordingly for example by showing or hiding information etc.

I started this on a Paris PDI before Quebec was released and everything was fine. Then because of other work I did not continue working on this until after my PDI was upgraded to Rome. Then the problem appeared. Now one (out of 10) widgets suddenly reacted very slowly on messages. The html would not update immediately, it could take up to about one minute.

I spent many hours debugging, commenting code out etc to try to isolate what was causing this. Then finally I found the answer in a stackoverflow question that mentioned $scope.$apply(). I had never heard about this and never seen it used in a OOB widget. It is used in about 10 widgets.

The AngularJS documentation says

$apply() is used to execute an expression in AngularJS from outside of the AngularJS framework.

LINK

So of course I immediately tried to add this simple line of code and voila the widget in question updated fast again. A simple solution that took many hours for me to discover.

Consider following code in the client controller part of a widget. This code subscribes to a service and wait for a specific message. When that message arrives it updates a variable in $scope that is displayed in the html with the {{}} binding syntax.

$(document).ready(function () {

  var removeListener = someService.addListener(function (action) {

    if (action.action == 'doSomething') {
      $scope.field = action.someData;
      $scope.$apply();
    }

  });

});

Without the $scope.$apply() the update of html can take as long as one minute when running on Quebec or later releases. With the line it is updated immediately.

I have confirmed that Quebec introduced changes to the Service Portal that makes this necessary. I confirmed by loading my application on a Paris PDI and ran the widget without the $apply() and it worked as expected with no delays. Then I upgraded the PDI to Quebec and made no changes to the widget but now it was extremely slow. Then I added the $apply() and the widget worked fast again. To re-confirm I upgraded the PDI to Rome and the same would happen.

So the question is when to use $apply. Well the documentation says whenever you update $scope outside a digest. A simple way to find out is just to add it whenever you make updates to $scope. If it is not necessary then you will get an error message in the console.log stating that code is already running in digist so $apply() is not required.

As I mentioned this code is not widely used in OOB Widgets which is why I never ran into this. I usually go check out new widgets when a new release is available to learn about new stuff in the portal.

Containerizing MID Server

From Rome release it is now possible to run MID servers in containers.

This means maintaining and deploying extra MID servers now can be done very fast.

Official documentation HERE.

In the old days setting up multiple MID servers on the same host would require going through the installation multiple times. Now you just build the MID server image one time for the relevant ServiceNow release and push it to a repository such as Docker Hub.

In the following I will describe how I setup an image for running multiple MID servers.

Step one: Download the correct recipe from your ServiceNow instanse. I choose the linux recipe as I could not get the windows recipe to work. It might be an issue only with the first release of Rome.

Step two: Unzip the downloaded zip-file and maybe rename / move to a suitable folder.

Step three: Build. Now begins the fun stuff. My preferred way is to open the folder with Visual Studio Code, then open a terminal window. You can also open a command prompt or powershell window.

Then run the command: docker build . –tag dockerid/midserver-rome

Replace ” dockerid ” with your docker hub id if you want to push the image to the hub.

Building first time will take a while so go grab a cup of coffe and enjoy the fact that your are going to save time setting up mid servers in the future 🙂

Step four: Setup an env file with the parameters to start the MID server so it can connect to your instanse.

Filename: mid1.env

Add following parameters in the file and save:

MID_INSTANCE_URL=https://your-instanse-name.service-now.com
MID_INSTANCE_USERNAME=userid setup with mid server roles
MID_INSTANCE_PASSWORD=password for userid
MID_SERVER_NAME=the name you see inside ServiceNow

Step five: Run. Then you can start the MID server with

docker run –env-file mid1.env dockerid/midserver-rome


After a few minutes you can see the server in serivcenow. You validate it the usual way.

To run a second mid server just copy mid1.env to mid2.env and adjust the server name then run the container agian with this file as env file instead.

Step six: Push to docker hub.

First login with docker login

Then run docker image push dockerid/midserver-rome

And now you can run it on other hosts.