├── .gitignore
├── component-validation
├── README.md
├── app.js
└── index.html
├── components
├── README.md
├── app.js
└── index.html
├── routing
├── README.md
├── app.js
├── index.html
└── named-views
│ ├── app.js
│ └── index.html
├── setup
├── README.md
├── app.js
└── index.html
├── structural-directives
├── README.md
├── app.js
└── index.html
└── vuex-examples
├── actions
├── README.md
├── app.css
├── app.js
└── index.html
├── hello-world
├── README.md
├── app.css
├── app.js
└── index.html
└── modules
├── README.md
├── app.css
├── app.js
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 |
--------------------------------------------------------------------------------
/component-validation/README.md:
--------------------------------------------------------------------------------
1 | # Components validation
2 |
3 | There is a way for us to set some validation on our props. Why would we want that you ask? Well, the main reason is that we want to make sure the consumers of our component use it correctly, so what we can do is to set up a bunch of validation and give out a warning in the console if the validation fails.
4 |
5 | ## Required
6 |
7 | If we don't explicitly set a property as `required` it will be optional. What we do to make it so is to assign an object to our property instead of using the string syntax `['prop']` that we are used to, like the below:
8 |
9 | ```
10 | Vue.component('validate-test', {
11 | template: `
12 |
13 |
14 | Year {{ year }}
15 |
16 |
17 | Title {{ title }}
18 |
19 | `,
20 |
21 | props : {
22 | year: Number,
23 | title : {
24 | type: String,
25 | required: true
26 | },
27 | }
28 | });
29 | ```
30 |
31 | Let's highlight the interesting code:
32 |
33 | ```
34 | title : {
35 | type: String,
36 | required: true
37 | }
38 | ```
39 |
40 | Above we give it a type `String` and set the `required` property to `true`.
41 |
42 | ## Validating an object
43 |
44 | Objects are interesting in that they have many different properties to validate. One thing we can do is to rely on a factory function `default()`. If we leave out the property in question then the factory function will be invoked and whatever it returns will take the place of where the bound object should have been. Let's look at some code:
45 |
46 | ```
47 | Vue.component('validate-test', {
48 | template: `
49 |
50 |
51 | Year: {{ year }}
52 |
53 |
54 |
55 | Title: {{ title }}
56 |
57 |
58 | Product: {{ product.name }}
59 |
60 |
61 | `,
62 |
63 | props : {
64 | year: Number,
65 | title : {
66 | type: String,
67 | required: true
68 | },
69 | product: {
70 | type: Object,
71 | default: function() {
72 | return {
73 | name: 'Unknown product'
74 | }
75 | }
76 | }
77 | }
78 | });
79 | ```
80 |
81 | And using said component like so:
82 |
83 | ```
84 |
85 | ```
86 |
87 | As you can see above we are clearly omitting the `product` property in our component and this is where the `default()` method kicks in and returns its object instead. Our template therefore renders
88 |
89 | ```
90 | Product: Unknown product
91 | ```
92 |
93 | ## Validator function
94 |
95 | Not only are we able to ensure that the input reaching our prop is of the right data type but we are also ablo to inspect the value and ensure it fulfills the necessary criteria. Let's have a look at what that looks like in code:
96 |
97 | ```
98 | Vue.component('validate-test', {
99 | template: `
100 |
101 |
102 | Year: {{ year }}
103 |
104 |
105 |
106 | Title: {{ title }}
107 |
108 |
109 | Product: {{ product.name }}
110 |
111 |
112 | `,
113 |
114 | props : {
115 | year: Number,
116 | title : {
117 | type: String,
118 | required: true
119 | },
120 | product: {
121 | type: Object,
122 | default: function() {
123 | return {
124 | name: 'Unknown product'
125 | }
126 | }
127 | },
128 | age: {
129 | validate: function(value) {
130 | return value >=18;
131 | }
132 | }
133 | }
134 | });
135 | ```
136 |
137 | Let's highlight the interesting code:
138 |
139 | ```
140 | age: {
141 | validate: function(value) {
142 | return value >=18;
143 | }
144 | }
145 | ```
146 |
147 | Above we can see that we added the function `validate()` and we define a function body where we end up with a response that either evaluates to `true` or `false`. If it returns `false` then a warning will be raised in the console that this value is not valid.
148 |
--------------------------------------------------------------------------------
/component-validation/app.js:
--------------------------------------------------------------------------------
1 | Vue.component('validate-test', {
2 | template: `
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/components/README.md:
--------------------------------------------------------------------------------
1 | # Components
2 |
3 | We declare a component by using the `Vue` object and calling its component method with two arguments:
4 |
5 | * component name
6 | * component definition object
7 |
8 | ```
9 | Vue.component('person-detail', {
10 | template: `
a component
`
11 | })
12 | ```
13 |
14 | Declaring the above means we can start adding this component wherever we need it, like so:
15 |
16 | ```
17 |
18 | ```
19 |
20 | ## Adding input property
21 |
22 | We are going to want to add an input property to the component so we can pass data for it to render. We do that by doing the following:
23 |
24 | * adding v-bind directive when creating the component
25 | * adding a `props` property to component definition
26 |
27 | ```
28 | Vue.component('person-detail', {
29 | template: `
{{person.name}}
`,
30 | props: ['person']
31 | })
32 | ```
33 |
34 | We did two things above, we added the `props` to our component definition object. We also updated the template to render our property like so:
35 |
36 | ```
37 | template: `
{{person.name}}
`
38 | ```
39 |
40 | At this point we need to creating the binding in the markup. We create this binding by using the structural directive `v-bind`, like so:
41 |
42 | ```
43 |
44 | ```
45 |
46 | For our component it means we change it to the following:
47 |
48 | ```
49 |
50 | ```
51 |
52 | This of course means that the context this component lives in knows about `person`, like so:
53 |
54 | ```
55 | var app = new Vue({
56 | el: '#app',
57 | data: {
58 | person: {
59 | name: 'chris'
60 | }
61 | }
62 | })
63 | ```
64 |
65 | ### Naming
66 |
67 | So far we have been using the `v-bind:propertyInComponent` binding syntax, but it is a bit verbose. We can shorten this to `:propertyInComponent`. There is another naming convention that we need to know about. When we name things in our props of our component like so:
68 |
69 | ```
70 | Vue.component('component', {
71 | props : ['title','longTitle']
72 | });
73 | ```
74 |
75 | The casing we use matter. When we create our component in markup, we need to name it correctly
76 |
77 | * title becomes title
78 | BUT
79 | * longTitle becomes long-title when used in the markup
80 |
81 | ```
82 |
83 | ```
84 |
85 | ### More on binding
86 |
87 | We can bind anything to a component, a primitive like a string, number or boolean or a list and even an object. However there is a difference in how different types behave. Primitives are being one-time binded so binding against the following primitives, would be one-time:
88 |
89 | ```
90 |
91 | ```
92 |
93 | Binding to an object would be another thing though:
94 |
95 | ```
96 |
97 | ```
98 |
99 | The above shows how we bind towards the property `person` and binds it to an object that may look like this:
100 |
101 | ```
102 | {
103 | name: 'chris'
104 | }
105 | ```
106 |
107 | Changing the property inside of the component with a `v-model`, like so:
108 |
109 | ```
110 |
111 | ```
112 |
113 | would lead to even the parent object being affected.
114 |
115 | ### fixing the double binding
116 |
117 | What you usually want when you bind in a list or object is to change things in the component first and when you are happy with it you invoke an action so the parent knows all about this updated list or object. You usually don't want the parent to be alerted about every change that you do. You can fix this by using the `data()` method with the component like so:
118 |
119 | ```
120 | data() {
121 | const copyObject = Object.assign({}, this.object);
122 |
123 | const copyList = this.list.map(item => Object.assign(item));
124 |
125 | return {
126 | copyObject,
127 | copyList
128 | };
129 |
130 | }
131 | ```
132 |
133 | ## Output
134 |
135 | How do we communicate with upwards, i.e how do we invoke a method, simple, we can just bind a method to it like so:
136 |
137 | ```
138 |
139 | ```
140 |
141 | The above tells us that we can call the event whatever we want, as indicated by the name `customEVentName`.
142 |
143 | And of course our parent component needs updating, like so:
144 |
145 | ```
146 | var app = new Vue({
147 | el: '#app',
148 | data: {
149 | person: {
150 | name: 'chris'
151 | }
152 | },
153 | methods: {
154 | method: () => {
155 | alert('called from component')
156 | }
157 | }
158 | })
159 | ```
160 |
161 | The interesting part happens when we invoke the method from the component itself, then it looks like this:
162 |
163 | ```
164 | Vue.component('person-detail',{
165 | template: `
166 |
167 |
168 |
`,
169 | props: ['person'],
170 | methods: {
171 | increment() {
172 | this.counter += 1 ;
173 | }
174 | }
175 | })
176 | ```
177 |
178 | ### Actual example
179 |
180 | Not how we below call our custom event `save`. We will soon see how this matches up in our component.
181 |
182 | ```
183 |
184 | ```
185 |
186 | Let's now turn to our component and look at how we can invoke the event we just set up:
187 |
188 | ```
189 |
190 | ```
191 |
192 | Zooming in we see `v-on:click` and that it invokes `$emit`.
193 |
--------------------------------------------------------------------------------
/components/app.js:
--------------------------------------------------------------------------------
1 | Vue.component('person-detail',{
2 | template: `
3 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/routing/README.md:
--------------------------------------------------------------------------------
1 | # Routing
2 |
3 | There is a reason a SPA is called a SPA. The term itself means Single Page Application. Our app can be logically divided into many sections or pages. A router can help us to just that. What changes isn't the exact URL but something called the hashban, #.
4 |
5 | ## Set up
6 |
7 | The set up for the router is quite easy, you can either:
8 |
9 | * install it using NPM:
10 | * add a script tag that points to an unpkg link
11 |
12 | ### Install
13 |
14 | Install it via NPM
15 |
16 | ```
17 | npm install vue-router
18 | ```
19 |
20 | We can also just add a script tag, like so:
21 |
22 | ```
23 |
24 | ```
25 |
26 | ### Define routes
27 |
28 | At this point we are ready to go but we need to define the routes we need to support. What we need is a list of objects containing the `path` to match and what `component` that should handle it. A routing list can therefore look like this:
29 |
30 | ```
31 | const routes = [
32 | { path: '/products', component: Products },
33 | { path: '/about', component: About },
34 | { path: '/', component: Home }
35 | ]
36 | ```
37 |
38 | There are two ways for us to create a component:
39 |
40 | * Vue.component
41 | * shorthand version
42 |
43 | Let's show the `Vue.component()` version first:
44 |
45 | ```
46 | const Products = Vue.component('products', {
47 | template : `
48 |
products
49 | `
50 | });
51 | ```
52 |
53 | Above is what we are used to, but there is an even shorter version:
54 |
55 | ```
56 | const Products = {
57 | template: `
58 |
Products
59 | `
60 | }
61 | ```
62 |
63 | ### Inject router
64 | At this point we have set up our router list so we support `/products`, `/about` and `/`. Last step is to instantiate the router and inject it into our main Vue object.
65 |
66 | Instantation of the router looks like this:
67 |
68 | ```
69 | const routes = [
70 | { path: '/products', component: Products },
71 | { path: '/about', component: About },
72 | { path: '/', component: Home }
73 | ]
74 |
75 | const router = new VueRouter({
76 | routes
77 | })
78 | ```
79 |
80 | Last step is injecting it to the main Vue object:
81 |
82 | ```
83 | const app = new Vue({
84 | router
85 | }).$mount('#app')
86 | ```
87 |
88 | ### creating navigation routes
89 |
90 | We want to be able to actually get to the routes we set up so therefore we need to add those in the markup in `index.html`:
91 |
92 | ```
93 |
94 |
Hello App!
95 |
96 | Products
97 | About
98 |
99 |
100 | ```
101 |
102 | As you can see above we use the component `router-link` to create navigation links and we set its `to` attribute to the path we want to navigate to.
103 |
104 | ### route outlet
105 | The route outlet is the part of your page that should be switched out when you navigate. Remember you are still on the same page you just replace part of your page when you router from one page to the next. In Vue we use a component called `router-view` to do this, like so:
106 |
107 | ```
108 |
109 | ```
110 |
111 | The full code for setting up our links and route outlet therefore looks like this:
112 |
113 | ```
114 |
115 |
Hello App!
116 |
117 | Products
118 | About
119 |
120 |
121 |
122 | ```
123 |
124 | ## Router parameters
125 |
126 | To use router parameters you need to set up your route to support wild card matching. Let's say you start with the router `/products/` then you need to change it `/products/:id`. The `id` here is the wildcard, the value that may change depending on what product you are looking at. This route will match routes like `/products/1` or `/products/11`. When we register our route in code it should now look like the following:
127 |
128 | ```
129 | const routes = [
130 | { path: '/products', component: Products },
131 | { path: '/products/:id', component: ProductDetail }
132 | ]
133 | ```
134 |
135 | How do we access this parameter? Well, we can talk to a built in `$route`. You will find you parameter under `$router.params.id`. Let's show this in an example:
136 |
137 | ```
138 | const ProductDetail = {
139 | template: `
140 |
141 | Your product is: {{$route.params.id}}
142 | Product {{ product.name }}
143 |
144 | `,
145 | data() {
146 | let product = products.find(p => this.$route.params.id);
147 | if(!product) {
148 | product = { name: 'unknown product' };
149 | }
150 |
151 | console.log(this.$route.params.id);
152 | return {
153 | a: 'a',
154 | product
155 | };
156 | }
157 | };
158 | ```
159 |
160 | The example above sohw how we access the router param value like this:
161 |
162 | ```
163 | Your product is: {{$route.params.id}}
164 | ```
165 |
166 | But we also show another interesting thing, namely how we can access the value and use it to find the data we need and assign the found data as a property in our `data()` method:
167 |
168 | ```
169 | data() {
170 | let product = products.find(p => this.$route.params.id);
171 | if(!product) {
172 | product = { name: 'unknown product' };
173 | }
174 |
175 | console.log(this.$route.params.id);
176 | return {
177 | a: 'a',
178 | product
179 | };
180 | }
181 | ```
182 |
183 | ## Programmatic navigation
184 | So far we have been using the component `router-link` to navigate but we can also do so programmatically. To do so we use the built in `$router` and call `$router.push(newUrl)`, like so:
185 |
186 | ```
187 | // literal string path
188 | $router.push('home')
189 |
190 | // object
191 | $router.push({ path: 'home' })
192 |
193 | // named route
194 | $router.push({ name: 'user', params: { userId: 123 }})
195 | ```
196 |
197 | As can be seen above there exists different versions that we can use to perform navigation. One where we just give it a literal string `products`, one wgere give it an object `{ path: 'products' }` and the third where we give it an object with the keys `name` and `params`, `{ name: 'user', params: { userId: 123 }}`.
198 |
199 | ### Named routes
200 |
201 | The third version of routing programmatically `{ name: 'user', params: { userId: 123 }}` uses something called named routing. Named routing is us creating a `name` association to the route rather than using the `path`. You can do so when you declare the route by typing the following:
202 |
203 | ```
204 | routes: [
205 | {
206 | path: '/products/:id',
207 | name: 'product',
208 | component: ProductDetail
209 | }
210 | ]
211 | ```
212 |
213 | ## Query parameters
214 |
215 | Query parameters are something we add to our url to filter our resource and is something that may be interesting to keep when we save the url as a bookmark. Example of query parameters is:
216 |
217 | ```
218 | /products?pageSize=10&page=2
219 | ```
220 |
221 | Here we convey that we want a part of the dataset, a page size of 10 and the results from page 2. How do we retrieve this parameter in Vue? Simple, we access the `$route.query` object. This object contains our parameters if we have any. In the example url above `/products?pageSize=10&page=2` we would access the parameters by typing:
222 |
223 | ```
224 | $route.query.pageSize
225 | $router.query.page
226 | ```
227 |
228 | If we want to build up our url by using programmatic navigation we can easily do so by using the `$route.push()` method and specifying an object where the `query` property is set, like so:
229 |
230 | ```
231 | this.$route.push({ path: '/products', query: { pageSize: } })
232 | ```
233 |
234 | ## Named views
235 |
236 | So far we settled on having one `router-view` component, that is one place where our routed content would render, also called a viewport. It is possible for us to have several viewports. You might need this in your application as you might for example have the following visual set up of your app:
237 |
238 | ```
239 | body
240 | footer
241 | ```
242 |
243 | `body` this is where your page content normally is rendered
244 |
245 | `footer` this is an area that much like the `header` could look different depending on what page you are on
246 |
247 | So how would you set this up? Simple, we use the `router-view` component but we need to use so called `named views` so we can identify each viewport we mean to use. The above suggested visual page set up could therefore look like this:
248 |
249 | ```
250 |
251 |
252 |
253 |
254 |
255 |
256 | ```
257 |
258 | ## Passing props to components
259 | Sometimes we want our route to have some pre knowledge of what it shoud render, a common thing you want to pass it is the `title` of the page or maybe the contents of a menu `menu`. We can easily do this by adding a `props` in our route declaration, like so:
260 |
261 | ```
262 | { path: '/',
263 | component: Home,
264 | props: { title: 'My Home' }
265 | }
266 | ```
267 |
268 | Then we can easily let our component know about this `props` and render it out in the template, like so:
269 |
270 | ```
271 | const Home = {
272 | template : `
273 |
11 | ```
12 |
13 | ## v-for
14 |
15 | Loop out a list of items
16 |
17 | ```
18 |
19 | {{ product.name }}
20 |
21 | ```
22 |
23 | ## v:on
24 |
25 | This how we attach methods to events.
26 |
27 | To attach to a specific event we need to use a syntax like this:
28 |
29 | ```
30 | v-on:eventName
31 | ```
32 |
33 | Listening to a click would therefore look like the following:
34 |
35 | ```
36 |
37 | ```
38 |
39 | In the component we need to declare a `methods` property and add our `handleClicked` to it, like so:
40 |
41 | ```
42 | var app = new Vue({
43 | el: '#app',
44 | data: {},
45 | methods : {
46 | handleClicked : () => {
47 | alert('something clicked me');
48 | }
49 | }
50 | })
51 | ```
52 |
53 | ### passing data to code
54 |
55 | How would pass the data from the markup to the js code? Simple, invoke the method with the data, like so:
56 |
57 | ```
58 |
59 |
60 |
61 | ```
62 |
63 | and the result js code now needs to look like this:
64 |
65 | ```
66 | var app = new Vue({
67 | el: '#app',
68 | data: {},
69 | methods : {
70 | handleClicked : (product) => {
71 | alert(`show product data ${product.name}`);
72 | }
73 | }
74 | })
75 | ```
76 |
77 | ## v-model
78 |
79 | This is how we change values, this creates a double binding. Use it like so:
80 |
81 | ```
82 | {{ person.name }}
83 |
84 | ```
85 |
86 | and in the js code we just type like so:
87 |
88 | ```
89 | var app = new Vue({
90 | el: '#app',
91 | data: {
92 | person : {
93 | name : 'chris'
94 | }
95 | }
96 | })
97 | ```
98 |
--------------------------------------------------------------------------------
/structural-directives/app.js:
--------------------------------------------------------------------------------
1 | var app = new Vue({
2 | el: '#app',
3 | data: {
4 | message: 'Hello Vue!',
5 | show: true,
6 | products: [{
7 | id: 1,
8 | name: 'tomato'
9 | },
10 | {
11 | id: 2,
12 | name: 'paprika'
13 | }]
14 | },
15 | methods : {
16 | showDetail : (product) => {
17 | alert('detail ' + product.name);
18 | }
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/structural-directives/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ message }}
6 |
7 | Show me...
8 |
9 |
10 | {{ product.name }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/vuex-examples/actions/README.md:
--------------------------------------------------------------------------------
1 | # Vuex - Using actions
2 |
3 | Actions commit mutations rather than mutating state.
4 |
5 | In this section we will explore `actions` what they are and how to use them.
6 |
7 | In Vuex we have gotten used to using `store.commit()` to persist changes to our store and its state. There is another way to do things though and that is using actions. An actions is simply an intention that can be asynchronous where `store.commit()` is synchronous.
8 |
9 | ## Creating a simple action
10 | An action is just a method on the `actions` property in your store, like so:
11 |
12 | ```
13 | const store = new Vuex.Store({
14 | state: {
15 | count: 0
16 | },
17 | actions: {
18 | increment(context) {
19 | context.commit('increment')
20 | }
21 | }
22 | })
23 | ```
24 |
25 | Above we have added the following method:
26 |
27 | ```
28 | increment(context) {}
29 | ```
30 | This method internally calls
31 |
32 | ```
33 | context.commit('name of mutations function')
34 | ```
35 |
36 | The above will invoke a method in the `mutatations` object.
37 |
38 | ### Adding a mutations function
39 |
40 | We need to create function that matches what we write in `context.commit('mutationsName')`, so we update our store to look like this:
41 |
42 | ```
43 | const store = new Vuex.Store({
44 | state: {
45 | count: 0
46 | },
47 | mutations: {
48 | // called 2nd
49 | mutations() {
50 | this.state.count++;
51 | }
52 | },
53 | actions: {
54 | // called 1st on dispatch()
55 | increment(context) {
56 | context.commit('increment')
57 | }
58 | }
59 | })
60 | ```
61 |
62 | ### Have component call the action
63 |
64 | For the component to use this action we need to use the method `dispatch('actionName')`
65 |
66 | We therefore create a component to look like the following:
67 |
68 | ```
69 | Vue.component('product', {
70 | template: `
71 |
72 | {{ count }}
73 |
74 |
75 | `,
76 | methods: {
77 | increment() {
78 | this.$store.dispatch('increment');
79 | }
80 | }
81 | })
82 | ```
83 |
84 | ## Creating asynchronous action
85 |
86 | So to make an action pay for itself we need to use its asynchronous nature. The whole point of using an action is to have it carry out a bunch of asynchronous things so that when done we can call `commit()` on it.
87 |
88 | A synchronous scenario can look in the following way:
89 |
90 | - fetch some data
91 | - update the store with the fetched data
92 |
93 | Of course to have the best possible user experience we would probably revise the above list a bit to read:
94 |
95 | - set loading spinner to indicate we are fetching data
96 | - fetch some data
97 | - if successful, update the store with the fetched data
98 | - if error, update the store with error message
99 | - hide loading spinner
100 |
101 | We would implement the above list of actions with the below code:
102 |
103 | ```
104 | state: {
105 | loading: false;
106 | products: [],
107 | error: ''
108 | },
109 | mutations: {
110 | products(state, products) {
111 | this.state.products = products;
112 | },
113 | loading(state, isLoading) {
114 | this.loading = isLoading;
115 | },
116 | error(state, error) {
117 | this.error = error;
118 | }
119 | }
120 | actions: {
121 | loadProducts({ commit, state }) {
122 | commit('loading');
123 | try{
124 | const products = await api.getProducts();
125 | commit('products', products);
126 | } catch (err) {
127 | commit('error', err);
128 | }
129 | }
130 | }
131 | ```
132 |
133 | Not especially how the `loadProducts()` calls `commit('loading')` and then calls `api.getProducts()` to fetch the data and depending on how we fare we either call `commit('products')` or `commit('error')`.
134 |
135 | ## Calling multiple actions
136 | So far we have only dispatched one action from a component but we can dispatch several. Most likely we want to do this because we care about loading data in a specific order.
137 |
138 | ### Returning a Promise
139 | Actions helps us with this by allowing us to return a Promise from an action. This means we can carry out a `dispatch()` and we would know when it is done. This is demonstrated in the below code:
140 |
141 | ```
142 | actions : {
143 | loadProduct({ state, commit }) {
144 | return new Promise((resolve, reject) => {
145 | setTimeout(()=> {
146 | resolve('data')
147 | }, 3000)
148 | })
149 | }
150 | }
151 | ```
152 |
153 | And to use that in a component
154 |
155 | Vue.component('data', {
156 | template: ``,
157 | methods: {
158 | load() {
159 | this.$store.dispatch('loadProduct').then( data => {
160 | console.log('data has arrived', data);
161 | })
162 | }
163 | }
164 | })
165 |
166 | We can also of course use that inside of another action, like so:
167 |
168 | ```
169 | actions: {
170 | async action1() {
171 | commit('data', await api.getData())
172 | },
173 | async action2() {
174 | await dispatch('action1');
175 | commit('moredata', await, api.getMoreData);
176 | }
177 | }
178 | ```
179 |
180 | Above we can see that we are clearly waiting for `dispatch('action1`) to finish. Internally the method `action1()` fetches data by calling `api.getData()`.
181 | Once that is done we continue in method `action2()` and call `commit()` where to a call to `api.getMoreData()`;
182 |
183 |
--------------------------------------------------------------------------------
/vuex-examples/actions/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/vuejs-book/9412bf1b3386c2a7d847f050b45d59c81c7578f8/vuex-examples/actions/app.css
--------------------------------------------------------------------------------
/vuex-examples/actions/app.js:
--------------------------------------------------------------------------------
1 | const api = {
2 | getData: () => {
3 | return new Promise(resolve => {
4 | setTimeout(() => {
5 | resolve('data');
6 | }, 3000);
7 | });
8 | },
9 | getOtherData: () => {
10 | return new Promise(resolve => {
11 | setTimeout(() => {
12 | resolve('other data');
13 | }, 3000);
14 | });
15 | }
16 | }
17 |
18 | const store = new Vuex.Store({
19 | state: {
20 | count: 0,
21 | data: void 0,
22 | otherdata: void 0,
23 | loading: false
24 | },
25 | mutations: {
26 | increment() {
27 | this.state.count++;
28 | },
29 | data(state, data) {
30 | this.state.data = data;
31 | },
32 | otherdata(state, data) {
33 | this.state.otherdata = data;
34 | },
35 | loading(state, isLoading) {
36 | this.state.loading = isLoading;
37 | }
38 | },
39 | actions: {
40 | async loadData({ dispatch, commit }) {
41 | commit('loading', true);
42 | commit('data', await api.getData());
43 | commit('loading', false);
44 | },
45 | async loadOtherData({ dispatch, commit }) {
46 | await dispatch('loadData');
47 | commit('loading', true);
48 | commit('otherdata', await api.getOtherData());
49 | commit('loading', false);
50 | },
51 | increment (context) {
52 | setTimeout(() => {
53 | context.commit('increment')
54 | }, 2000);
55 | }
56 | }
57 | })
58 |
59 | Vue.component('test', {
60 | template: `
61 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/vuex-examples/hello-world/README.md:
--------------------------------------------------------------------------------
1 | # Vuex
2 |
3 | Vuex is about state management.
4 |
5 | To use it we essentially need to do the following:
6 |
7 | * create and configure our store
8 | * create mutators
9 | * create getters ( optional )
10 | * read from the store
11 | * write to the store
12 |
13 | # Set up the store
14 |
15 | To set up the store we need to first install it. There are two options here:
16 |
17 | * install it via npm
18 | * add script tag with unpkg url
19 |
20 | The first is achieved by typing:
21 |
22 | ```
23 | npm install vuex
24 | ```
25 |
26 | The second version is achieved by typing:
27 |
28 | ```
29 |
30 | ```
31 |
32 | ## Creating the store
33 |
34 | We create the store by creating a new instance of `Vuex.Store`, in the process we provide it with an object. The object takes many properties like:
35 |
36 | * state, our stores state, just a simple object
37 | * mutators, a property that points to an object that contains methods that can change our state
38 | * getters, a property that points to an object that reads from our state, this is used to create sub queries based on what it is in the store.
39 |
40 | Below is the code needed to create the store:
41 |
42 | ```
43 | const store = new Vuex.Store({
44 | state: {
45 | count: 0,
46 | }
47 | }
48 | ```
49 |
50 | Above we have declare the store to hold the property `state` which contains one property `count`. `count` is a property that we can now display in a UI if we want.
51 |
52 | ## Reading from the store
53 |
54 | To access the store we need to either:
55 |
56 | * access the store instance we created
57 | * inject the store in our application object
58 |
59 | ### Refer to the store instance
60 | In this case we need to have access to our store instance and pass that around. We first off create the store instance like so:
61 |
62 | ```
63 | const store = new Vuex.Store({
64 | state: {
65 | count: 0,
66 | }
67 | }
68 | ```
69 |
70 | Then we need to refer to that store instance inside of component when we need to read from stores state. We read from the state by setting up a computed property and return the slice of state we need, like so:
71 |
72 | ```
73 | Vue.component('counter', {
74 | template : `
75 |
{{count}}
76 | `,
77 | computed: {
78 | count() {
79 | return store.state.count;
80 | }
81 | }
82 | })
83 | ```
84 |
85 | ### Injecting the store
86 |
87 | The first case is accomplished by doing the following:
88 |
89 | ```
90 | const store = new Vuex.Store({
91 | state: {
92 | count: 0,
93 | }
94 | }
95 |
96 | var app = new Vue({
97 | el: '#app',
98 | data: {
99 | message: 'Hello Vue!'
100 | },
101 | store
102 | })
103 | ```
104 |
105 | Reading from the state at this point looks very similar to when we are dealing with a store instance but there is a suttle difference, we know refer to the store on `this`. So instead of writing `store.state.someProperty`, we can write `this.$store.state.someProperty`, note the use of the `$`. Our code now becomes:
106 |
107 | ```
108 | Vue.component('counter', {
109 | template : `
110 |
{{count}}
111 | `,
112 | computed: {
113 | count() {
114 | return this.$store.state.count;
115 | }
116 | }
117 | })
118 | ```
119 |
120 | ## Writing to the store
121 |
122 | Ok, so we learned how we can read from the the store, so how can we write to the store? The answer is that we can use a method called `commit()` on the `store` object. `commit()` takes two parameters:
123 |
124 | * first parameter is a string, which is the name of mutators function on the store object
125 | * second parameter is an object, which is the payload, the data that you wish change the state with
126 |
127 | We introduced a concept called `mutators` which is a property on the store, that we set up like so:
128 |
129 | ```
130 | const store = new Vuex.Store({
131 | state: {
132 | count: 0,
133 | },
134 | mutators: {
135 | increment(state) {
136 | this.state.count++;
137 | }
138 | }
139 | }
140 | ```
141 |
142 | We can see above how we define the method `increment()` which increments the value of the property `count` in our state.
143 |
144 | ### Adding a payload
145 |
146 | So far we have shown how we can add a mutator function to our store and thereby a way to change the state of the store. The example we looked at was very simple, what if we wanted to do something more complex like adding an item to a list? That's almost as simple, we need to do the following to make that happen:
147 |
148 | * extends our state with a list property
149 | * write a mutators function to allow for us to add an item to a list
150 |
151 | Starting with the first bullet we first extends our state, like so:
152 |
153 | ```
154 | const store = new Vuex.Store({
155 | state: {
156 | count: 0,
157 | list : []
158 | },
159 | mutators: {
160 | increment(state) {
161 | this.state.count++;
162 | }
163 | }
164 | }
165 | ```
166 |
167 | We initialise our property `list` to an empty array. Then it's time to write our mutator function like so:
168 |
169 | ```
170 | const store = new Vuex.Store({
171 | state: {
172 | count: 0,
173 | list : []
174 | },
175 | mutators: {
176 | increment(state) {
177 | this.state.count++;
178 | },
179 | add(state, item) {
180 | this.state.list.push(item);
181 | }
182 | }
183 | }
184 | ```
185 |
186 | ## Creating a CRUD example for a list
187 |
188 | Now it's time to put things in practice. Let's create a component `products` that supports the adding and removal of items. We start off with the component:
189 |
190 | ```
191 | Vue.component('products', {
192 | template : `
193 |
194 | {{ item.title }}
195 |
196 | `,
197 | computed() {
198 | items() {
199 | return this.$store.state.list;
200 | }
201 | }
202 | })
203 | ```
204 |
205 | ### Add an item
206 |
207 | Above we read the state from the store and we render out our list by using the `v-for` directive and creating the computed property `items`.
208 |
209 | Next up let's add some support for adding an item, so we extend our component with some markup and method for talking to our store:
210 |
211 | ```
212 | Vue.component('products', {
213 | template : `
214 |
215 |
216 |
217 | {{ item.title }}
218 |
219 |
220 | `,
221 | data() {
222 | return {
223 | newItem: ''
224 | }
225 | },
226 | methods: {
227 | add() {
228 | this.$store.commit('add', { title: this.newItem });
229 | }
230 | },
231 | computed() {
232 | items() {
233 | return this.$store.state.list;
234 | }
235 | }
236 | })
237 | ```
238 |
239 | We have done the following additions:
240 |
241 | * added an input element that captures our new item
242 | * added a data property `newItem`
243 | * added the method `add()` that commited the new item to the store. We can see that the string `add` matches the method under `mutators` in our store.
244 |
245 | ### Remove an item
246 |
247 | To remove an item we need to do the following:
248 |
249 | * add a remove button per item
250 | * add a `remove()` method under methods
251 | * extend our store with another mutators method `remove()`
252 |
253 | Let's do the necessary changes to the component first:
254 |
255 | ```
256 | Vue.component('products', {
257 | template : `
258 |
259 |
260 |
261 | {{ item.title }}
262 |
263 |
264 | `,
265 | data() {
266 | return {
267 | newItem: ''
268 | }
269 | },
270 | methods: {
271 | add() {
272 | this.$store.commit('add', { title: this.newItem, id: idCounter++ });
273 | },
274 | remove() {
275 | this.$store.commit('remove', { id });
276 | }
277 | },
278 | computed() {
279 | items() {
280 | return this.$store.state.list;
281 | }
282 | }
283 | })
284 | ```
285 |
286 | Now let's turn to our store and update that:
287 |
288 | ```
289 | const store = new Vuex.Store({
290 | state: {
291 | count: 0,
292 | list : []
293 | },
294 | mutators: {
295 | increment(state) {
296 | this.state.count++;
297 | },
298 | add(state, item) {
299 | this.state.list.push(item);
300 | },
301 | remove(state, item) {
302 | this.state.list = this.state.list.filter( i=> i.id !== item.id);
303 | }
304 | }
305 | }
306 | ```
307 |
308 | ### Edit an item
309 |
310 | Now this is slightly more complicated but still fairly easy. We need the following:
311 |
312 | * A component in which we can edit our item
313 | * a state property in our store that will hold the current item we are editing
314 | * a mutators function that edits the correct item when we decide to save our changes
315 |
316 | Let's start by building our edit component
317 |
318 | ```
319 | Vue.component('product-edit', {
320 | template: `
321 |
322 |
323 |
324 |
325 | `,
326 | methods : {
327 | save() {
328 | this.$store.commit('updateProduct', this.item);
329 | }
330 | },
331 | data() {
332 | return {
333 | localProduct: Object.assign({}, this.$store.state.selectedProduct)
334 | };
335 | },
336 | props: ['product']
337 | })
338 | ```
339 |
340 | Ok, so let's now support this by altering `products` component. What we need to add is the ability to select a product from our list of products. The component should therefore now look like this:
341 |
342 | ```
343 | Vue.component('products', {
344 | template : `
345 |
346 |
347 |
348 | {{ item.title }}
349 |
350 |
351 |
352 |
353 | `,
354 | data() {
355 | return {
356 | newItem: ''
357 | }
358 | },
359 | methods: {
360 | add() {
361 | this.$store.commit('add', { title: this.newItem, id: idCounter++ });
362 | },
363 | remove() {
364 | this.$store.commit('remove', { id });
365 | },
366 | select(id) {
367 | this.$store.commit('selectProduct', { id });
368 | }
369 | },
370 | computed() {
371 | items() {
372 | return this.$store.state.list;
373 | }
374 | }
375 | })
376 | ```
377 |
378 | What we did above was adding a new method `select()` to the `methods` property, like so:
379 |
380 | ```
381 | select(id) {
382 | this.$store.commit('selectProduct', { id });
383 | }
384 | ```
385 |
386 | Now we need to update our store so we support the `selectProduct()` method, like so:
387 |
388 | ```
389 | const store = new Vuex.Store({
390 | state: {
391 | count: 0,
392 | list : [],
393 | selectedProduct
394 | },
395 | mutators: {
396 | increment(state) {
397 | this.state.count++;
398 | },
399 | add(state, item) {
400 | this.state.list.push(item);
401 | },
402 | remove(state, item) {
403 | this.state.list = this.state.list.filter( i=> i.id !== item.id);
404 | },
405 | selectProduct(state, item) {
406 | this.state.selectedProduct = this.state.list.find( i => i.id === item.id )
407 | }
408 | }
409 | }
410 | ```
411 |
412 | At this point you notice everything works well and fine when you select a product in the list, once. As soon as you select another item in the list your `product-detail` component is NOT updated. Why is that? Well we don't listen to prop changes in our `product-edit` component. Let's do so by introducing the `watch` property. The `watch` property will allow us to listen to prop changes and reset our `localProduct` to our new selection. Let's add that to the component:
413 |
414 | ```
415 | Vue.component('product-edit', {
416 | template: `
417 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/vuex-examples/modules/README.md:
--------------------------------------------------------------------------------
1 | # Modules
2 | Modules is a way to split up your store state in different parts. Why do we want to this? Well declaring everything in one store might clutter visibility. You may also want to load in partial states as you lazy load routes and components.
3 |
4 | ## Set up
5 |
6 | Getting started with modules is pretty straight forward. What you need is to add the `modules` property to your store defintion, like so:
7 |
8 | ```
9 | const store = new Vuex.Store({
10 | state: {
11 | count: 0
12 | },
13 | modules : {
14 | moduleA,
15 | moduleB
16 | }
17 | })
18 | ```
19 |
20 | `modules` is an object and contains properties. Each property represents a module and its definition. Each definition looks like a normal store object, like so:
21 |
22 | ```
23 | const moduleB = {
24 | state: {
25 | b: 'bbbb'
26 | },
27 | mutations: {...},
28 | actions: {...},
29 | getters: { ... }
30 | }
31 | ```
32 |
33 | ## Accessing module values
34 | Accessing a value in a module can be done by referring to the `state` and drill down to the module name and eventually the property. Let's have a look at how in the below app component:
35 |
36 | ```
37 | var app = new Vue({
38 | el: '#app',
39 | data: {
40 | message: 'Hello Vue!',
41 | input: ''
42 | },
43 | computed: {
44 | a() {
45 | return this.$store.state.moduleA.a;
46 | },
47 | b() {
48 | return this.$store.state.moduleB.b;
49 | }
50 | },
51 | store
52 | })
53 | ```
54 |
55 | Note especially above how we register the computed properties `a` and `b` and how we return the state property for each:
56 |
57 | ```
58 | a() {
59 | return this.$store.state.moduleA.a;
60 | }
61 | ```
62 | Here we are drilling into our `$store.state` and we find our property by referring to our module name so the full way we access a state in another module is by typing `$store.state.moduleName.statePropertyName`, which in this case is `$store.state.moduleA.a`. Ok, so we learned to access state in module, next up let's look at how to change a value.
63 |
64 |
65 | ## Change value in a module store
66 | We have learned before how we can change the state in the store by using either `commit()` or by calling `dispatch()` and thereby use an action to change the state. Let's register some `mutations` functions on each store module, like so:
67 |
68 | ```
69 | const moduleA = {
70 | namespaced: true,
71 | state: {
72 | a: 'aaaa'
73 | },
74 | mutations: {
75 | change(state, val) {
76 | this.state.moduleA.a = val;
77 | }
78 | }
79 | }
80 | ```
81 |
82 | and for `moduleB` we do:
83 |
84 | ```
85 | const moduleB = {
86 | state: {
87 | b: 'bbbb'
88 | },
89 | mutations: {
90 | change(state, val) {
91 | this.state.moduleB.b = val;
92 | }
93 | }
94 | }
95 | ```
96 |
97 | NOTE, we can access the state in a bit better way, we don't have to use the fully qualified name `this.state.moduleB.b` we can instead use the input parameter `state`, which is the state bound to our module. If we use that instead we can type `state.b` which looks way better.
98 |
99 | ### Calling commit() from component
100 |
101 | Ok, we are all set up and ready for our component to call `commit()`. Let's add that bit to our app component, like so:
102 |
103 | ```
104 | var app = new Vue({
105 | el: '#app',
106 | data: {
107 | message: 'Hello Vue!',
108 | input: ''
109 | },
110 | methods : {
111 | save() {
112 | this.$store.commit('change', this.input);
113 | }
114 | },
115 | computed: {
116 | a() {
117 | return this.$store.state.moduleA.a;
118 | },
119 | b() {
120 | return this.$store.state.moduleB.b;
121 | }
122 | },
123 | store
124 | })
125 | ```
126 |
127 | Invoking our method `save()` will trigger a call to `this.$store.commit('change', this.input)`. What will happen, what mutation function will react to this? The answer is all of them will react, both the one in `moduleA` as well as the one in `moduleB`. This may or may not be what we intended. If we me mean for only one of them to be called we will either need to make sure the mutation method name is unique or use something called namespaced modules. Let's look at namespaced modules next.
128 |
129 | ## Namespaced modules
130 | So far everything has been accessible in the global context which is the reason why when we do a `commit()` both `change()` functions were called. We can isolate ourselves a bit by using namespaced modules. We just need to add the property `namespaced` to our module definition and give it the value `true`, like so:
131 |
132 | ```
133 | const moduleB = {
134 | namespaced: true
135 | state: {
136 | b: 'bbbb'
137 | },
138 | mutations: {
139 | change(state, val) {
140 | this.state.moduleB.b = val;
141 | }
142 | }
143 | }
144 | ```
145 |
146 | Trying invoke our `save()` method again will not call the `change()` method for this module anymore, we have isolated ourselves. Any module missing the `namespaced` property will still be part of the global context. So how do we call the `change()` method on this module? We can still call it if we specify its fully qualified name namely `commit('moduleB/change')`. The rule here is `moduleName/mutationFunctionName` as the full name.
147 |
148 | This will also affect other things like getters. Let'start off my adding a getter to `moduleB`, like so:
149 |
150 | ```
151 | const moduleB = {
152 | namespaced: true
153 | state: {
154 | b: 'bbbb'
155 | },
156 | getter: {
157 | b(state) {
158 | return state.b;
159 | }
160 | },
161 | mutations: {
162 | change(state, val) {
163 | this.state.moduleB.b = val;
164 | }
165 | }
166 | }
167 | ```
168 |
169 | We follow this up by creating a new computed property `bGet()` where we make a call to our getter:
170 |
171 | ```
172 | var app = new Vue({
173 | el: '#app',
174 | data: {
175 | message: 'Hello Vue!',
176 | input: ''
177 | },
178 | methods : {
179 | save() {
180 | this.$store.commit('change', this.input);
181 | }
182 | },
183 | computed: {
184 | a() {
185 | return this.$store.state.moduleA.a;
186 | },
187 | b() {
188 | return this.$store.state.moduleB.b;
189 | },
190 | bGet() {
191 | return this.$store.getter['moduleB/b']
192 | }
193 | },
194 | store
195 | })
196 | ```
197 |
198 | NOTE, how we call our getter with its fully qualified name `moduleB/b`, like so:
199 |
200 | ```
201 | bGet() {
202 | return this.$store.getter['moduleB/b']
203 | }
204 | ```
205 |
206 | ## Dynamic registration
207 |
208 | What does it mean to dynamically register a module? It means that we are able to add said module after the store has been created. This is often the case when dealing with plugins or if you want to add store state on a lazy load for example.
209 |
210 | You add the module by calling `store.registerModule('moduleName', {
211 | ... module definition object
212 | })`
213 |
214 | There might also be situations where you want to deregister your module, you do that by calling `store.unregisterModule('moduleName')`
215 |
--------------------------------------------------------------------------------
/vuex-examples/modules/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/vuejs-book/9412bf1b3386c2a7d847f050b45d59c81c7578f8/vuex-examples/modules/app.css
--------------------------------------------------------------------------------
/vuex-examples/modules/app.js:
--------------------------------------------------------------------------------
1 | const moduleA = {
2 | namespaced: true,
3 | state: {
4 | a: 'aaaa'
5 | },
6 | getters: {
7 | a(state) {
8 | return state.a;
9 | }
10 | },
11 | mutations: {
12 | change(state, val) {
13 | this.state.moduleA.a = val;
14 | }
15 | }
16 | }
17 |
18 | const moduleB = {
19 | state: {
20 | b: 'bbbb'
21 | },
22 | mutations: {
23 | change(state, val) {
24 | console.log('local state', state.b);
25 | console.log('global state', this.state.moduleB.b);
26 | this.state.moduleB.b = val;
27 | }
28 | }
29 | }
30 |
31 | const store = new Vuex.Store({
32 | state: {
33 | count: 0
34 | },
35 | modules : {
36 | moduleA,
37 | moduleB
38 | }
39 | })
40 |
41 | var app = new Vue({
42 | el: '#app',
43 | data: {
44 | message: 'Hello Vue!',
45 | input: ''
46 | },
47 | methods : {
48 | save() {
49 | this.$store.commit('moduleA/change', this.input);
50 | this.$store.commit('change', this.input);
51 | }
52 | },
53 | computed: {
54 | a() {
55 | return this.$store.state.moduleA.a;
56 | },
57 | b() {
58 | return this.$store.state.moduleB.b;
59 | },
60 | aget() {
61 | return this.$store.getters['moduleA/a'];
62 | }
63 | },
64 | store
65 | })
66 |
--------------------------------------------------------------------------------
/vuex-examples/modules/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |