Angular app

How to integrate authentication into an Angular app
beginners
30 minutes

Getting started with Angular

In this tutorial, you'll learn how to work with Stacks Connect when using Angular as your framework of choice. It builds on what you've learnt in the Authentication Overview.

Prerequisites

We'll be using the Angular CLI to scaffold the project, so make sure you've got the latest version installed. We're using version 10.2.0.

npm install --global @angular/clinpm install --global @angular/cli

1. Scaffold & Run

Use the ng new command to scaffold a new project. We've named ours ng-stacks-connect.

ng new --minimal --inline-style --inline-templateng new --minimal --inline-style --inline-template

You'll be asked to enter some preferences: whether your app with use routing, and whether you want to use a CSS preprocessor like SASS. For sake of this tutorial, we're keeping things simple. No routing. No preprocessing.

Inside the newly created ng-stacks-connect directory, let's boot up the development server which defaults to localhost:4200.

cd ng-stacks-connect ng servecd ng-stacks-connectng serve

2. Add Stacks Connect

npm install --save @stacks/connect blockstacknpm install --save @stacks/connect blockstack

3. Declare missing globals

Some dependencies of these packages were written for a Nodejs environment. In a browser environment, tools such as Webpack (v4) often abstract the polyfilling of Nodejs specific APIs. Using the Angular CLI, this must be done manually.

Add the following snippet to your src/polyfills.ts

(window as any).global = window; (window as any).process = { version: '', env: {}, }; global.Buffer = require('buffer').Buffer;(window as any).global = window;(window as any).process = { version: '', env: {},};global.Buffer = require('buffer').Buffer;

This does 3 things:

  1. Declares global to window
  2. Declares a global Buffer class
  3. Declares a global process object

4. Authentication flow

Now everything's set up, we're ready to create our auth components

We can use the CLI's generator to scaffold components.

4.1 Sign In button

ng generate componentng generate component

Enter the name: stacks-sign-in-button. You'll find the newly generated component in src/app/stacks-sign-in-button/stacks-sign-in-button.component.ts.

Here's our Sign In button component. Let's replace this example with the following code.

import { Component, OnInit, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-stacks-sign-in-button', template: ` <button (click)="onSignIn.emit()">Sign In</button> `, }) export class StacksSignInButtonComponent { @Output() onSignIn = new EventEmitter(); }import { Component, OnInit, Output, EventEmitter } from '@angular/core';@Component({ selector: 'app-stacks-sign-in-button', template: ` <button (click)="onSignIn.emit()">Sign In</button> `,})export class StacksSignInButtonComponent { @Output() onSignIn = new EventEmitter();}

4.2 Connecting Stacks Connect

Let's add this button to our app-root component (app.component.ts) and wire up the (onSignIn) event. Make sure to import Subject from rxjs.

@Component({ selector: 'app-root', template: `<app-stacks-sign-in-button (onSignIn)="stacksAuth$.next()" ></app-stacks-sign-in-button>`, }) export class AppComponent { stacksAuth$ = new Subject<void>(); }@Component({ selector: 'app-root', template: `<app-stacks-sign-in-button (onSignIn)="stacksAuth$.next()" ></app-stacks-sign-in-button>`,})export class AppComponent { stacksAuth$ = new Subject<void>();}

Here we're using an Rxjs Subject to represent a stream of sign in events. stacksAuth$ will emit when we should trigger the sign in action.

4.3 Authentication

First, describe the auth options we need to pass to Connect. Learn more about AuthOptions here. Let's modify the default component to look like this:

import { Component } from '@angular/core'; import { AuthOptions, FinishedData } from '@stacks/connect'; import { ReplaySubject, Subject } from 'rxjs'; import { switchMap } from 'rxjs/operators'; @Component({ selector: 'app-root', template: ` <app-stacks-sign-in-button (onSignIn)="stacksAuth$.next()"></app-stacks-sign-in-button> <code> <pre>{{ authResponse$ | async | json }}</pre> </code> `, }) export class AppComponent { stacksAuth$ = new Subject<void>(); authResponse$ = new ReplaySubject<FinishedData>(1); authOptions: AuthOptions = { finished: response => this.authResponse$.next(response), appDetails: { name: 'Angular Stacks Connect Demo', icon: 'http://placekitten.com/g/100/100' }, }; ngOnInit() { this.stacksAuth$ .pipe(switchMap(() => import('@stacks/connect'))) .subscribe(connectLibrary => connectLibrary.showBlockstackConnect(this.authOptions)); } }import { Component } from '@angular/core';import { AuthOptions, FinishedData } from '@stacks/connect';import { ReplaySubject, Subject } from 'rxjs';import { switchMap } from 'rxjs/operators';@Component({ selector: 'app-root', template: ` <app-stacks-sign-in-button (onSignIn)="stacksAuth$.next()"></app-stacks-sign-in-button> <code> <pre>{{ authResponse$ | async | json }}</pre> </code> `,})export class AppComponent { stacksAuth$ = new Subject<void>(); authResponse$ = new ReplaySubject<FinishedData>(1); authOptions: AuthOptions = { finished: response => this.authResponse$.next(response), appDetails: { name: 'Angular Stacks Connect Demo', icon: 'http://placekitten.com/g/100/100' }, }; ngOnInit() { this.stacksAuth$ .pipe(switchMap(() => import('@stacks/connect'))) .subscribe(connectLibrary => connectLibrary.showBlockstackConnect(this.authOptions)); }}

Let's run through what's going on. In the authOptions field, we're using the finished handler to emit a value to the authResponse$ which uses a ReplaySubject to persist the latest response.

For initial load performance, we're using import("@stacks/connect") to only load the Stacks Connect library when it's needed. The switchMap operators "switches" out the stacksAuth$ event for the library.

The output of authResponse$ can be added to the template for debugging purposes. This uses Angular's async and json pipes.

4.3 Loading text

One problem with the current implementation is that there's a network delay while waiting to load the Connect library. Let's keep track of the loading state and display some text in the sign in button component. You'll need to import { tap, switchMap } from 'rxjs/operators';.

// src/app/app.component.ts isLoadingConnect$ = new BehaviorSubject(false); ngOnInit() { this.stacksAuth$ .pipe( tap(() => this.isLoadingConnect$.next(true)), switchMap(() => import("@stacks/connect")), tap(() => this.isLoadingConnect$.next(false)) ) .subscribe(connectLibrary => connectLibrary.showBlockstackConnect(this.authOptions) ); }// src/app/app.component.tsisLoadingConnect$ = new BehaviorSubject(false);ngOnInit() { this.stacksAuth$ .pipe( tap(() => this.isLoadingConnect$.next(true)), switchMap(() => import("@stacks/connect")), tap(() => this.isLoadingConnect$.next(false)) ) .subscribe(connectLibrary => connectLibrary.showBlockstackConnect(this.authOptions) );}

We can keep track of it with a BehaviorSubject, which always emits its initial value when subscribed to.

Let's add a loading input to the StacksSignInButtonComponent component.

@Component({ selector: 'app-stacks-sign-in-button', template: ` <button (click)="onSignIn.emit()">{{ loading ? 'Loading' : 'Sign in' }}</button> `, }) export class StacksSignInButtonComponent { @Input() loading: boolean; @Output() onSignIn = new EventEmitter(); }@Component({ selector: 'app-stacks-sign-in-button', template: ` <button (click)="onSignIn.emit()">{{ loading ? 'Loading' : 'Sign in' }}</button> `,})export class StacksSignInButtonComponent { @Input() loading: boolean; @Output() onSignIn = new EventEmitter();}

Then, pass the isLoadingConnect$ Observable into the component, and hide it when the user has already authenticated.

// Edit src/app/app.component.ts <app-stacks-sign-in-button *ngIf="!(authResponse$ | async)" (onSignIn)="stacksAuth$.next()" [loading]="isLoadingConnect$ | async" ></app-stacks-sign-in-button>// Edit src/app/app.component.ts<app-stacks-sign-in-button *ngIf="!(authResponse$ | async)" (onSignIn)="stacksAuth$.next()" [loading]="isLoadingConnect$ | async"></app-stacks-sign-in-button>

Next steps

This tutorial has shown you how to integrate Stacks Connect with an Angular application. You may want to consider abstracting the Stacks Connect logic behind an Angular service, or using Material Design to theme your application.

Previous
Public registry app
Next
Blockchain Naming System
Powered by