Getting started

In this tutorial how to make your first Vue app with Typescript. Make sure you have node.js and vue-cli installed.

Creating app

To create a new project, run:

vue create name-of-my-app
1

You will be prompted to pick a preset. Choose "Manually select features" to pick the features you need. Preset

From features we pick Babel, Typescript. We also recommend to pick Vuex (for vue-settings), Router (for subpages) and linter (code checker, but it can be very tricky) Preset

After picking features you will be prompted to choose class component or not. We definitelly recommend it as it is very easy to use. Press Y for yes Preset

You will then asked if you want to use Babel alongside with Typescript press Y for yes. We also prefer to place config in dedicated files. It makes code more cleaner Preset

After installation just change directory with

cd name-of-my-app
1

and run with

yarn serve
1

open the source code in your favourite text editor (We love Visual Studio Code).

Creating first component

We will create our first Vue Typescript component using vue-identicon

in src/components/ we will create IdenticonImage.vue you can copy the snippet bellow

<template>
  <div>
    IdenticonImage works!
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class IdenticonImage extends Vue {
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

In App.vue we will now import IdenticonImage under HelloWorld import.

import IdenticonImage from './components/IdenticonImage.vue';
1

also we can't forget to register our component, so simply add IdenticonImage inside @Component annotation like:

@Component({
  components: {
    HelloWorld,
    IdenticonImage
  },
})
1
2
3
4
5
6

now we can show IdenticonImage inside our template, replace <img> tag with:

<IdenticonImage />
1

How should App.vue look like.

<template>
  <div id="app">
    <IdenticonImage />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
import IdenticonImage from './components/IdenticonImage.vue';

@Component({
  components: {
    HelloWorld,
    IdenticonImage
  },
})
export default class App extends Vue {}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Adding vue-identicon to project

Now open another terminal and type down

yarn add @vue-polkadot/vue-identicon
1

this command will install vue-identicon package which we can use in our IdenticonImage.vue. We will import vue-identicon like:

import Identicon from '@vue-polkadot/vue-identicon';
1

register the Identicon component in @Component annotation:

@Component({
  components: {
    Identicon
  }
})
1
2
3
4
5

and add to teplate. There are 3 properties for Identicon:

  • size
  • theme
  • value: represents polkadot's ss58 address -> for generation use Subkey

Our final IdenticonImage.vue component:

<template>
  <div>
    <Identicon
      size="128"
      value='5F1aGzHi5uJkRdegzBNj4fhG81ZtrWS79TrErTa7qL7ih4ik'
      theme='beachball'
    />
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import Identicon from '@vue-polkadot/vue-identicon';

@Component({
  components: {
    Identicon
  }
})
export default class IdenticonImage extends Vue {
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

What we should see now:

Preset

Generation vue-identicon with vue-keyring

We used static address to try if our identicon works but, it would be more fun to generate identicon based on given address.

We will install vue-keyring which helps us with account generation.

yarn add @vue-polkadot/vue-keyring
1

We will need two new components in this part. First component AccountsWrapper is wrapper for crypto-wasm library. Second component Accounts creates new account with metadata and shows us Identicon based on address.

So let's create new AccountsWrapper.vue component in ./src/components

<template>
  <div>
    AccountsWrapper works!
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class AccountsWrapper extends Vue {
  public keyringLoaded: boolean = false;
  public keys: any = '';
  public keyringAccounts: any = [
    { address: '', meta: { name: ''}, publicKey: '', type: '' },
  ];

}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

then we'll import couple of important libraries.

import keyring from '@vue-polkadot/vue-keyring'
import { keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
import { waitReady } from '@polkadot/wasm-crypto';
1
2
3

First of all we need to create a function that will be called automatically after component is mounted. Vue has function mounted for that. Inside this function mountWasmCrypto is called

public mounted(): void {
    this.mountWasmCrypto();
  }
1
2
3

Function mountWasmCrypto calls asynchronously waitReady which loads Web-assembly into our project.

  public async mountWasmCrypto(){
    await waitReady();
    this.loadKeyring();
  }
1
2
3
4

After that we'll initialize loadKeyring. loadAll function of keyring will initialize keyring. It should be called from api on connect, because it needs also genesis hash and it will initialize localStorage for saving accounts. isDevelopment will preload our keyring with 10 testing accounts (Alice, Bob, Bob_stash,...). getPairs will return all accounts which are loaded in keyring.

  public loadKeyring(): void {
    keyring.loadAll({
      ss58Format: 42, type: 'sr25519',
      isDevelopment: true });
    this.keyringLoaded = true;
    this.keyringAccounts = keyring.getPairs();
  }
1
2
3
4
5
6
7

now we will create template in Accounts.vue

<template>
  <div>
    Accounts works!
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Accounts extends Vue {
  public keypairType: any = {
    selected: 'sr25519',
    options: [
      { text: 'Schnorrkel (sr25519)', value: 'sr25519' },
      { text: 'Edwards (ed25519)', value: 'ed25519' },
    ],
  };
  public isValidMnemonic: boolean = false;
  public isPassValid: boolean = false;
  public newAccount: any = {
    password: '',
    name: 'new account',
    tags: '',
    mnemonicSeed: '',
    keypairType: this.keypairType,
    derivationPath: '',
    address: '',
  };
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Inside Accounts we will create 4 new functions. Also we need to call mounted as before. Function coldStart is pretty straightforward. It generates mnemonic seed (those 12 words) then address from these 12 words and after it validates that mnemonic

  public mounted(): void {
    this.coldStart();
  }

  public coldStart(): void {
    this.generateSeed();
    this.addressFromSeed();
    this.validateMnemonic();
  }

  public generateSeed(): string {
    return this.newAccount.mnemonicSeed = mnemonicGenerate();
  }

    public validateMnemonic(): boolean {
    return this.isValidMnemonic = mnemonicValidate(this.newAccount.mnemonicSeed);
  }

  public addressFromSeed(): any {
    return this.newAccount.address = keyring.createFromUri(`${this.newAccount.mnemonicSeed.trim()}${this.newAccount.derivationPath}`,
      {}, this.keypairType.selected).address;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

now let's connect everyting together, inside IdenticonImage we add @Prop value which value is passed from Accounts component

<template>
  <div>
    <Identicon
      :size="128"
      :value='value'
      theme='beachball'
    />
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import Identicon from '@vue-polkadot/vue-identicon';

@Component({
  components: {
    Identicon
  }
})
export default class IdenticonImage extends Vue {
  @Prop() private value!: string;
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

In Accounts component we'll import IdenticonImage and pass prop :value="address". : means value of value will be variable not direct value. We've got address from getter which will automatically updates value passed to IdenticonImage.

get address() {
  return this.newAccount && this.newAccount.address
}
1
2
3
<template>
  <div>
    <IdenticonImage :value="address" />
    <div>{{address}}</div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import keyring from '@vue-polkadot/vue-keyring'
import { keyExtractSuri, mnemonicGenerate,
  mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
  import IdenticonImage from './IdenticonImage.vue';

@Component({
  components: {
    IdenticonImage,
  }
})
export default class Accounts extends Vue {
  public keypairType: any = {
    selected: 'sr25519',
    options: [
      { text: 'Schnorrkel (sr25519)', value: 'sr25519' },
      { text: 'Edwards (ed25519)', value: 'ed25519' },
    ],
  };
  public isValidMnemonic: boolean = false;
  public isPassValid: boolean = false;
  public newAccount: any = {
    password: '',
    name: 'new account',
    tags: '',
    mnemonicSeed: '',
    keypairType: this.keypairType,
    derivationPath: '',
    address: '',
  };

  get address() {
    return this.newAccount && this.newAccount.address
  }

  public coldStart(): void {
    this.generateSeed();
    this.addressFromSeed();
    this.validateMnemonic();
  }
  public mounted(): void {
    this.coldStart();
  }

  public generateSeed(): string {
    return this.newAccount.mnemonicSeed = mnemonicGenerate();
  }

    public validateMnemonic(): boolean {
    return this.isValidMnemonic = mnemonicValidate(this.newAccount.mnemonicSeed);
  }

  public addressFromSeed(): any {
    return this.newAccount.address = keyring.createFromUri(`${this.newAccount.mnemonicSeed.trim()}${this.newAccount.derivationPath}`,
      {}, this.keypairType.selected).address;
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

Snippet of AccountsWrapper

<template>
  <div>
   <Accounts  v-if="keyringLoaded" />
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import keyring from '@vue-polkadot/vue-keyring'
import { keyExtractSuri, mnemonicGenerate,
  mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
  import { waitReady } from '@polkadot/wasm-crypto';
  import Accounts from './Accounts.vue'

@Component({
  components: {
    Accounts,
  }
})
export default class AccountsWrapper extends Vue {
  public keyringLoaded: boolean = false;
  public keys: any = '';
  public keyringAccounts: any = [
    { address: '', meta: { name: ''}, publicKey: '', type: '' },
  ];


  public mapAccounts(): void {
    this.keyringAccounts = keyring.getPairs();
  }

  public loadKeyring(): void {
    keyring.loadAll({
      ss58Format: 42, type: 'sr25519',
      isDevelopment: true });
    this.keyringLoaded = true;
    this.mapAccounts();
  }

  public async mountWasmCrypto(): Promise<void> {
    await waitReady();
    console.log('wasmCrypto loaded');
    this.loadKeyring();
    console.log('keyring init');
  }
  public mounted(): void {
    this.mountWasmCrypto();
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

Finally we'll replace IdenticonImage in App.vue with AccountsWrapper

<template>
  <div id="app">
    <AccountsWrapper />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
import AccountsWrapper from './components/AccountsWrapper.vue';

@Component({
  components: {
    HelloWorld,
    AccountsWrapper
  },
})
export default class App extends Vue {}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Summary

We learned:

  • How to create our own component in Vue with Typescript
  • How to install add package to our project