mirror of https://github.com/lukechilds/docs.git
committed by
kyranjamie
4 changed files with 222 additions and 8 deletions
@ -0,0 +1,213 @@ |
|||||
|
--- |
||||
|
title: Building an app with Angular |
||||
|
description: Learn how to integrate authentication within an Angular application |
||||
|
experience: beginners |
||||
|
duration: 30 minutes |
||||
|
tags: |
||||
|
- tutorial |
||||
|
# images: |
||||
|
# large: /images/pages/todo-app.svg |
||||
|
# sm: /images/pages/todo-app-sm.svg |
||||
|
--- |
||||
|
|
||||
|
# Building an with Angular |
||||
|
|
||||
|
<!--  --> |
||||
|
|
||||
|
## Getting started with Angular |
||||
|
|
||||
|
In this tutorial, you'll learn how to work with Stacks Connect when using [Angular](https://angular.io/) as your framework of choice. It builds on what you've learnt in the [Authentication Overview](/authentication/overview). |
||||
|
|
||||
|
-> This article presumes some familiarity with [Angular](https://angular.io/), as well as [Reactive Extensions (RxJS)](https://rxjs.dev/). |
||||
|
|
||||
|
### Prerequisites |
||||
|
|
||||
|
We'll be using the [Angular CLI](https://cli.angular.io/) to scaffold the project, so make sure you've got the latest version installed. We're using version `10.2.0`. |
||||
|
|
||||
|
```sh |
||||
|
npm install --global @angular/cli |
||||
|
``` |
||||
|
|
||||
|
## 1. Scaffold & Run |
||||
|
|
||||
|
Use the `ng new` command to scaffold a new project. We've named ours `ng-stacks-connect`. |
||||
|
|
||||
|
```sh |
||||
|
ng new --minimal --inline-style --inline-template |
||||
|
``` |
||||
|
|
||||
|
Inside the newly created `ng-stacks-connect` directory, we can boot up the development server on [localhost:4200](http://localhost:4200). |
||||
|
|
||||
|
```sh |
||||
|
ng serve |
||||
|
``` |
||||
|
|
||||
|
## 2. Add Stacks Connect |
||||
|
|
||||
|
```sh |
||||
|
npm install --save @stacks/connect blockstack |
||||
|
``` |
||||
|
|
||||
|
-> Note that we're also installing the `blockstack` package, as it's a [peer dependency](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#peerdependencies) of Stacks Connect |
||||
|
|
||||
|
## 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` |
||||
|
|
||||
|
```typescript |
||||
|
(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 |
||||
|
|
||||
|
```sh |
||||
|
ng generate component |
||||
|
``` |
||||
|
|
||||
|
Enter the name: `stacks-sign-in-button`. You'll find the newly generated component in `src/app/stacks-sign-in-button`. |
||||
|
|
||||
|
Here's our Sign In button component |
||||
|
|
||||
|
```typescript |
||||
|
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. |
||||
|
|
||||
|
```typescript |
||||
|
@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](https://docs.blockstack.org/authentication/overview). |
||||
|
|
||||
|
```typescript |
||||
|
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`](https://rxjs.dev/api/index/class/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. |
||||
|
|
||||
|
### 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. |
||||
|
|
||||
|
```typescript |
||||
|
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](https://rxjs.dev/api/index/class/BehaviorSubject), which always emits its initial value when subscribed to. |
||||
|
|
||||
|
Let's add a `loading` input to the `StacksSignInButtonComponent` component. |
||||
|
|
||||
|
```typescript highlight=3,6 |
||||
|
@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. |
||||
|
|
||||
|
```html |
||||
|
<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](https://angular.io/guide/architecture-services), or using [Material Design](https://material.angular.io/) to theme your application. |
Loading…
Reference in new issue