Angular authenticator
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.
This article presumes some familiarity with Angular, as well as Reactive Extensions (RxJS).
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/cli
Step 1: Scaffold and run
Use the ng new
command to scaffold a new project. We've named ours ng-stacks-connect
.
ng 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 serve
Step 2: Add Stacks connect
npm install --save @stacks/connect blockstack
We're also installing the blockstack
package, as it's a peer dependency of Stacks Connect
Step 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.
Buffer
, for example, is a global class in a Nodejs environment. In the browser is it undefined
so we must declare it to avoid runtime exceptions
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;
This does 3 things:
- Declares
global
towindow
- Declares a global
Buffer
class - Declares a global
process
object
Step 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.
Sign-in button
ng 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();
}
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>();
}
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.
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)
);
}
}
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.
A ReplaySubject
is an Observable that starts without an initial value, but replays the latest x emissions when subscribed to
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.
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)
);
}
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();
}
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>
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 app.