上一篇我們把 Angular 、 MetaMask 、 BootStrap 以及 web3.js 都裝好了,這一篇會寫簡單的 Service 來取得錢包 Provider 。
我們先透過 AngularCLI 快速建立一個 Service 來當作往後 Provider 專用的 Service :
ng g s services/provider
因為整個網頁都需要靠 Provider 來運作,所以我們只需要建立一個實例就可以了,讓整個網頁都使用單一 ProviderService 實例,所以我們設定 providedIn: 'root'
,就不會不同模組使用不同的實例:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ProviderService {}
接著我們要把 web3.js 引入,好讓我們可以使用 web3.js 提供的功能:
import * as Web3 from 'web3';
現在的 web3 已經有 type 定義,故 import 方式會有不同,若使用較新版的 web3 ,則改成
import Web3 from 'web3';
在 constructor
的時候就要取得 Provider ,先檢查 window
下有沒有 web3
屬性,如果有就表示有錢包提供環境,所以去取用 currentProvider
,如果這是美麗的錯誤(?)就直接調用我們的 Ganache :
import { Injectable } from '@angular/core';
import * as Web3 from 'web3';
declare let window: any;
@Injectable({
providedIn: 'root'
})
export class ProviderService {
private web3: any = null;
constructor() {
this.web3 = typeof window.web3 !== 'undefined'
? new Web3(window.web3.currentProvider)
: new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
}
}
如果有發生相關錯誤再執行即可
這時候去編譯的話會編譯失敗!得到的相關錯誤是: Module not found: Error: Can’t resolve 'crypto' ,這個問題在 Angular 6 以上會出現,在編譯時會忽略 node.js 的 API ,所以我們必須把部分 node.js 的 API 也編譯進去,那該怎麼做呢?我們這邊寫一個插件去更改 Webpack 的設置,最主要就是要把 node: false
改掉:
const fs = require('fs');
const file = './node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
fs.readFile(file, 'utf8', (err, data) => {
if (err) return console.log(err);
const result = data.replace(/node: false/g, 'node: {crypto: true, stream: true}');
fs.writeFile(file, result, 'utf8', (err) => {
if (err) return console.log(err);
});
});
可以把這個插件存起來,並設定 package.json
來讓首次使用的開發者透過指令來執行:
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"patch-webpack": "node plugins/patch.js"
},
然後輸入下方指令就完成了:
npm run patch-webpack
前面我們取得了 Provider ,不過這樣子並沒有把網頁跟錢包做連接,而是要透過 Provider 去做連接,這邊我們寫一個方法來建立連線,為了方便統一管理,將會盡量用 rxjs
來處理非同步事件,所以我們把 enable()
透過 from()
來將 Promise
轉換成 Observable
,在取得連線同意後會取得帳戶列表:
import { Observable, from } from 'rxjs';
public enableConnect(): Observable<any> {
return from(this.web3.currentProvider.enable());
}
然後先放在 constructor
中呼叫,這邊順便寫了取得 accounts 的方法:
import { Injectable } from '@angular/core';
import { Observable, from } from 'rxjs';
import { take } from 'rxjs/operators';
import * as Web3 from 'web3';
declare let window: any;
@Injectable({
providedIn: 'root'
})
export class ProviderService {
private web3: any = null;
private accountList: Array<string> = [];
constructor() {
this.web3 = typeof window.web3 !== 'undefined'
? new Web3(window.web3.currentProvider)
: new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
this.enableConnect().pipe(take(1)).subscribe(
res => { this.accountList = res; },
err => { console.error(err); }
);
}
public enableConnect(): Observable<any> {
return from(this.web3.currentProvider.enable());
}
}
那要怎麼查看是不是真的有拿到 Provider 呢?可以先在 app.component.ts
中注入 ProviderService ,然後在 Service 用 console.log()
來查看就可以了!
成功透過 web3.js 來取得 Provider ,並將網頁與錢包做連線,需要注意的是 Angular 6 以上編譯會報錯,需要去更改 Webpack 設定。
Angular 使用 web3 时出现 Can't Resolve 'Crypto' 报错的解决方法
如果出現" Module not found: Error: Can’t resolve 'crypto' ",請問在那裏更改"Webpack"設定,我不知道那個檔案在那裏。
thank you very much.
已經解決了,原來是要在root目錄下新增一個資料夾,命名"plugins",然後在裏面新增一個JS檔案,命名"patch.js",在JS檔案裏,複製以下code.
const fs = require('fs');
const file = './node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
fs.readFile(file, 'utf8', (err, data) => {
if (err) return console.log(err);
const result = data.replace(/node: false/g, 'node: {crypto: true, stream: true}');
fs.writeFile(file, result, 'utf8', (err) => {
if (err) return console.log(err);
});
});
然後再執行"npm run patch-webpack"。