В этой статье из шести частей мы расскажем, как создать Dapp с помощью Angular. В части I, которая служила введением, мы рассмотрели общую информацию о разработке преимуществ и классификации dapp. Преимущества использования Angular, архитектуры Angular. Вам следует начать с этого.
В предыдущих статьях мы начали разработку нашего dapp. В частности,
мы узнали о классификациях и проектах децентрализованных приложений и о том, что вы можете
разбить свой собственный проект децентрализованного приложения на пять этапов.
Затем мы рассмотрели, зачем использовать Angular и его преимущества. Затем мы создали проект Angular, сначала убедившись, что все необходимые компоненты установлены, а затем установили Angular CLI.
Мы рассмотрели элементы, составляющие Angular, такие как компоненты, модули и директивы. Мы также научились стилизовать децентрализованное приложение, изучив архитектуру в стиле Angular и работая с Angular Material.
Мы начали создавать собственные компоненты и контент; мы разделили приложение на нижний колонтитул, заголовок и тело и создали настраиваемый компонент передачи, который вы будете использовать в этой статье. Мы создали смарт-контракт dapp, используя следующие инструменты: Angular CLI, Truffle, ganache-cli и MetaMask.
В этой статье мы интегрируем наш смарт-контракт в проект Angular
dapp.
Связь с сетью Ethereum
В предыдущей статье мы получили контракт, работающий в Терминале; следующим шагом будет взаимодействие нашего децентрализованного приложения с контрактом. Это делается через web3.js, который представляет собой набор библиотек, позволяющих вам взаимодействовать с локальным или удаленным узлом Ethereum, используя соединение HTTP или IPC.
Сначала вернитесь в папку проекта Angular, а затем установите web3.js с флагом - сохраните, чтобы сохранить устанавливаемую вами библиотеку.
cd ethdapp/ npm install web3 --save + [email protected]
Если установка прошла успешно, вы увидите в выводе, что
версия была установлена. На момент написания web3 имеет версию 1.2.4.
Вы также будете устанавливать @ truffle / contract, который предоставляет код оболочки
, упрощающий взаимодействие с вашим контрактом. На момент
последней на момент написания была версия 4.1.3.
npm install @truffle/contract --save + [email protected]
Совет. В случае возникновения проблем с совместимостью.
web3 версии 1.2.4 и @ truffle / contract версия
4.0.31 являются последними версиями и совместимы с Angular 9.x .
Однако это может измениться, поэтому следите за устанавливаемой версией
, чтобы убедиться в ее совместимости и избежать ошибок. Переустановите с точной
@ [версией], например,
npm install @truffle/[email protected]
Трансферная служба
Теперь, когда у вас установлены библиотеки, вы можете продолжить. В этом разделе
вы создадите и напишите класс обслуживания. Класс обслуживания
будет вашим промежуточным уровнем интерфейса для Angular, который будет взаимодействовать с web3. Для начала вы можете использовать флаг ng s, что означает «обслуживание».
cd ~/Desktop/ethdapp ng g s services/transfer CREATE src/app/services/transfer.service.spec.ts (367 bytes) CREATE src/app/services/transfer.service.ts (137 bytes)
Затем нам нужно заменить исходный код класса обслуживания на логику взаимодействия
с web3.
Для этого сначала мы определим библиотеки, которые мы будем использовать, а именно:
ядро Angular, а также библиотеки truffle-contract и web3, которые вы установили.
Давайте посмотрим на код transfer.service.ts. Сначала мы будем использовать фреймворк Angular 9, а также библиотеку web3, поэтому ее необходимо определить;
import { Injectable } from '@angular/core'; const Web3 = require('web3');
Затем нам нужно определить три переменные, которые мы будем использовать позже: require,
window и tokenAbi.
Обратите внимание, что tokenAbi указывает на файл ABI, который мы скомпилировали из файла контракта SOL в предыдущей статье.
declare let require: any; declare let window: any; const tokenAbi = require('../../../truffle/build/contracts/Transfer.json');
Затем нам нужен доступ к root для взаимодействия с web3, чтобы мы могли внедрить его в наш проект.
@Injectable({ providedIn: 'root' })
Теперь мы можем определить определение класса, учетную запись и переменные web3, которые мы будем использовать, а также init web3.
constructor() { if (window.ethereum === undefined) { alert('Non-Ethereum browser detected. Install MetaMask'); } else { if (typeof window.web3 !== 'undefined') { this.web3 = window.web3.currentProvider; } else { this.web3 = new Web3.providers.HttpProvider('http://localhost:8545'); } console.log('transfer.service :: constructor :: window.ethereum'); window.web3 = new Web3(window.ethereum); console.log('transfer.service :: constructor :: this.web3'); console.log(this.web3); this.enable = this.enableMetaMaskAccount(); } }
В браузерах, поддерживающих MetaMask (если они установлены), для нас будет установлена переменная «window.ethereum». В следующей главе мы установим и настроим MetaMask. В этом методе установите web3 как глобальную переменную, чтобы наш сервисный класс мог с ней взаимодействовать, поэтому вы видите «window.web3». Другая реализация может устанавливать web3 только при необходимости. Я сделал это просто.
обратите внимание, что я также вызываю метод «this .enable», этот метод откроет MetaMask, чтобы наше приложение могло взаимодействовать с нашим кошельком. Вы увидите это в следующей статье. Код ожидает взаимодействия пользователя с MetaMask, поэтому я установил его как обещание с помощью await. Вот код;
private async enableMetaMaskAccount(): Promise<any> { let enable = false; await new Promise((resolve, reject) => { enable = window.ethereum.enable(); }); return Promise.resolve(enable); }
Обратите внимание, что вы обернули сообщения console.log вокруг кода, чтобы вы могли видеть сообщения в разделе сообщений консоли браузера в режиме инструмента разработчика, чтобы помочь вам понять, что происходит.
Для этого откройте браузер в режиме инструмента разработчика. Для Chrome выберите View Developer View ➤ Developer ➤ Developer Tools.
Затем нам понадобится асинхронный метод для получения адреса и баланса учетной записи,
чтобы вы могли использовать функцию обещания. Если ваша учетная запись не была загружена
ранее, вы вызовете web3.eth.getAccounts так же, как вы делали это в Терминале, чтобы
получить данные. Вам также понадобится код ошибки, если что-то пойдет не так.
private async getAccount(): Promise<any> { console.log('transfer.service :: getAccount :: start'); if (this.account == null) { this.account = await new Promise((resolve, reject) => { console.log('transfer.service :: getAccount :: eth'); console.log(window.web3.eth); window.web3.eth.getAccounts((err, retAccount) => { console.log('transfer.service :: getAccount: retAccount'); console.log(retAccount); if (retAccount.length > 0) { this.account = retAccount[0]; resolve(this.account); } else { alert('transfer.service :: getAccount :: no accounts found.'); reject('No accounts found.'); } if (err != null) { alert('transfer.service :: getAccount :: error retrieving account'); reject('Error retrieving account'); } }); }) as Promise<any>; } return Promise.resolve(this.account); }
Точно так же вам понадобится сервисный метод для взаимодействия и получения баланса
учетной записи. Вы используете web3.eth.getBalance так же, как в Терминале
, и выполняете некоторую проверку ошибок. Вы также устанавливаете это как обещание. Причина, по которой
вам нужно обещание, заключается в том, что эти вызовы являются асинхронными, а JavaScript - нет.
public async getUserBalance(): Promise<any> { const account = await this.getAccount(); console.log('transfer.service :: getUserBalance :: account'); console.log(account); return new Promise((resolve, reject) => { window.web3.eth.getBalance(account, function(err, balance) { console.log('transfer.service :: getUserBalance :: getBalance'); console.log(balance); if (!err) { const retVal = { account: account, balance: balance }; console.log('transfer.service :: getUserBalance :: getBalance :: retVal'); console.log(retVal); resolve(retVal); } else { reject({account: 'error', balance: 0}); } }); }) as Promise<any>; }
Наконец, нам нужно установить метод фактического перевода средств;
transferEther(value) { const that = this; console.log('transfer.service :: transferEther to: ' + value.transferAddress + ', from: ' + that.account + ', amount: ' + value.amount); return new Promise((resolve, reject) => { console.log('transfer.service :: transferEther :: tokenAbi'); console.log(tokenAbi); const contract = require('@truffle/contract'); const transferContract = contract(tokenAbi); transferContract.setProvider(that.web3); console.log('transfer.service :: transferEther :: transferContract'); console.log(transferContract); transferContract.deployed().then(function(instance) { return instance.pay( value.transferAddress, { from: that.account, value: value.amount }); }).then(function(status) { if (status) { return resolve({status: true}); } }).catch(function(error) { console.log(error); return reject('transfer.service error'); }); }); }
Полный код нашего transfer.service.ts приведен ниже;
import { Injectable } from '@angular/core'; const Web3 = require('web3'); declare let require: any; declare let window: any; const tokenAbi = require('../../../truffle/build/contracts/Transfer.json'); @Injectable({ providedIn: 'root' }) export class TransferService { private account: any = null; private readonly web3: any; private enable: any; constructor() { if (window.ethereum === undefined) { alert('Non-Ethereum browser detected. Install MetaMask'); } else { if (typeof window.web3 !== 'undefined') { this.web3 = window.web3.currentProvider; } else { this.web3 = new Web3.providers.HttpProvider('http://localhost:8545'); } console.log('transfer.service :: constructor :: window.ethereum'); window.web3 = new Web3(window.ethereum); console.log('transfer.service :: constructor :: this.web3'); console.log(this.web3); this.enable = this.enableMetaMaskAccount(); } } private async enableMetaMaskAccount(): Promise<any> { let enable = false; await new Promise((resolve, reject) => { enable = window.ethereum.enable(); }); return Promise.resolve(enable); } private async getAccount(): Promise<any> { console.log('transfer.service :: getAccount :: start'); if (this.account == null) { this.account = await new Promise((resolve, reject) => { console.log('transfer.service :: getAccount :: eth'); console.log(window.web3.eth); window.web3.eth.getAccounts((err, retAccount) => { console.log('transfer.service :: getAccount: retAccount'); console.log(retAccount); if (retAccount.length > 0) { this.account = retAccount[0]; resolve(this.account); } else { alert('transfer.service :: getAccount :: no accounts found.'); reject('No accounts found.'); } if (err != null) { alert('transfer.service :: getAccount :: error retrieving account'); reject('Error retrieving account'); } }); }) as Promise<any>; } return Promise.resolve(this.account); } public async getUserBalance(): Promise<any> { const account = await this.getAccount(); console.log('transfer.service :: getUserBalance :: account'); console.log(account); return new Promise((resolve, reject) => { window.web3.eth.getBalance(account, function(err, balance) { console.log('transfer.service :: getUserBalance :: getBalance'); console.log(balance); if (!err) { const retVal = { account: account, balance: balance }; console.log('transfer.service :: getUserBalance :: getBalance :: retVal'); console.log(retVal); resolve(retVal); } else { reject({account: 'error', balance: 0}); } }); }) as Promise<any>; } transferEther(value) { const that = this; console.log('transfer.service :: transferEther to: ' + value.transferAddress + ', from: ' + that.account + ', amount: ' + value.amount); return new Promise((resolve, reject) => { console.log('transfer.service :: transferEther :: tokenAbi'); console.log(tokenAbi); const contract = require('@truffle/contract'); const transferContract = contract(tokenAbi); transferContract.setProvider(that.web3); console.log('transfer.service :: transferEther :: transferContract'); console.log(transferContract); transferContract.deployed().then(function(instance) { return instance.pay( value.transferAddress, { from: that.account, value: value.amount }); }).then(function(status) { if (status) { return resolve({status: true}); } }).catch(function(error) { console.log(error); return reject('transfer.service error'); }); }); } }
Теперь, когда у нас есть услуга перевода (transfer.service.ts), мы можем подключить transfer.component, чтобы получить адрес и баланс учетной записи пользователя и иметь возможность переводить средства после заполнения формы.
Во-первых, нам нужно определить созданный нами сервисный компонент.
src/app/component/transfer/transfer.component.ts
Откройте и добавьте оператор импорта вверху документа.
import {TransferService} from ‘../../services/transfer.service’;
Для определения компонента добавьте TransferService в качестве поставщика.
@Component({ selector: 'app-transfer', templateUrl: './transfer.component.html', styleUrls: ['./transfer.component.css'], providers: [TransferService] })
Кроме того, добавьте TransferService в конструктор, чтобы вы могли использовать его в своем классе
.
constructor(private fb: FormBuilder, private transferService: TransferService) { }
Затем обновите метод getAccountAndBalance, чтобы включить вызов
класса обслуживания и получить фактическую учетную запись и баланс пользователя.
getAccountAndBalance = () => { const that = this; this.transferService.getUserBalance(). then(function(retAccount: any) { that.user.address = retAccount.account; that.user.balance = retAccount.balance; console.log('transfer.components :: getAccountAndBalance :: that.user'); console.log(that.user); }).catch(function(error) { console.log(error); }); }
Наконец, обновите submitForm, чтобы вызвать transferEther для перевода и оплаты.
Замените комментарии submitForm TODO, показанные здесь, на вызов вызовов службы
:
// TODO: service call
Затем передайте данные, отправленные пользователем:
// TODO: service call this.transferService.transferEther(this.userForm.value). then(function() {}).catch(function(error) { console.log(error); });
Теперь в терминале запустите ng serve, чтобы убедиться, что у вас нет ошибок;
cd ~/Desktop/ethdapp ng serve : Compiled successfully.
Если вы перейдете по адресу http: // localhost: 4200 для просмотра нашего dapp, вы получите сообщение об ошибке, что MetaMask не установлен. Это ожидается, поскольку мы еще не установили MetaMask. См. Рисунок 2.
Подведем итоги!
В этой статье мы установили web3.js и truffle-contract. Затем мы создали нашу службу передачи в Angular и соединили наш класс компонентов с классом обслуживания.
Куда пойти отсюда
Продолжайте следить за нашими статьями, как в следующей и последней статье, которую мы будем освещать;
Чтобы узнать больше о возможностях Blockchain, а также разработать свой собственный проект, проверьте Разработчик Blockchain.