В этой статье из шести частей мы расскажем, как создать Dapp с помощью Angular. В части I, которая служила введением, мы рассмотрели общую информацию о разработке преимуществ и классификации dapp. Преимущества использования Angular, архитектуры Angular. Вам следует начать с этого.

В предыдущих статьях мы начали разработку нашего dapp. В частности,
мы узнали о классификациях и проектах децентрализованных приложений и о том, что вы можете
разбить свой собственный проект децентрализованного приложения на пять этапов.

Затем мы рассмотрели, зачем использовать Angular и его преимущества. Затем мы создали проект Angular, сначала убедившись, что все необходимые компоненты установлены, а затем установили Angular CLI.

Мы рассмотрели элементы, составляющие Angular, такие как компоненты, модули и директивы. Мы также научились стилизовать децентрализованное приложение, изучив архитектуру в стиле Angular и работая с Angular Material.

Мы начали создавать собственные компоненты и контент; мы разделили приложение на нижний колонтитул, заголовок и тело и создали настраиваемый компонент передачи, который вы будете использовать в этой статье. Мы создали смарт-контракт dapp, используя следующие инструменты: Angular CLI, Truffle, ganache-cli и MetaMask.

В этой статье мы интегрируем наш смарт-контракт в проект Angular
dapp.

В последней статье, Часть IV; Связывание и подключение вашего децентрализованного приложения к сети Ethereum
и тестирование
.

Связь с сетью 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.