|
| 1 | +# Provide / inject |
| 2 | + |
| 3 | +> This page assumes you've already read the [Components Basics](components.md). Read that first if you are new to components. |
| 4 | +
|
| 5 | +Usually, when we need to pass data from the parent to child component, we use [props](component-props.md). Imagine the structure where you have some deeply nested components and you only need something from the parent component in the deep nested child. In this case, you still need to pass the prop down the whole component chain which might be annoying. |
| 6 | + |
| 7 | +For such cases, we can use the `provide` and `inject` pair. Parent components can serve as dependency provider for all its children, regardless how deep the component hierarchy is. This feature works on two parts: parent component has a `provide` option to provide data and child component has an `inject` option to start using this data. |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +For example, if we have a hierarchy like this: |
| 12 | + |
| 13 | +``` |
| 14 | +Root |
| 15 | +└─ TodoList |
| 16 | + ├─ TodoItem |
| 17 | + └─ TodoListFooter |
| 18 | + ├─ ClearTodosButton |
| 19 | + └─ TodoListStatistics |
| 20 | +``` |
| 21 | + |
| 22 | +If we want to pass the length of todo-items directly to `TodoListStatistics`, we would pass the prop down the hierarchy: `TodoList` -> `TodoListFooter` -> `TodoListStatistics`. With provide/inject approach, we can do this directly: |
| 23 | + |
| 24 | +```js |
| 25 | +const app = Vue.createApp({}) |
| 26 | + |
| 27 | +app.component('todo-list', { |
| 28 | + data() { |
| 29 | + return { |
| 30 | + todos: ['Feed a cat', 'Buy tickets'] |
| 31 | + } |
| 32 | + }, |
| 33 | + provide: { |
| 34 | + user: 'John Doe' |
| 35 | + }, |
| 36 | + template: ` |
| 37 | + <div> |
| 38 | + {{ todos.length }} |
| 39 | + <!-- rest of the template --> |
| 40 | + </div> |
| 41 | + ` |
| 42 | +}) |
| 43 | + |
| 44 | +app.component('todo-list-statistics', { |
| 45 | + inject: ['foo'], |
| 46 | + created() { |
| 47 | + console.log(`Injected property: ${this.user}`) // > Injected property: John Doe |
| 48 | + } |
| 49 | +}) |
| 50 | +``` |
| 51 | + |
| 52 | +However, this won't work if we try to provide some Vue instance property here: |
| 53 | + |
| 54 | +```js |
| 55 | +app.component('todo-list', { |
| 56 | + data() { |
| 57 | + return { |
| 58 | + todos: ['Feed a cat', 'Buy tickets'] |
| 59 | + } |
| 60 | + }, |
| 61 | + provide: { |
| 62 | + todoLength: this.todos.length // this will result in error 'Cannot read property 'length' of undefined` |
| 63 | + }, |
| 64 | + template: ` |
| 65 | + ... |
| 66 | + ` |
| 67 | +}) |
| 68 | +``` |
| 69 | + |
| 70 | +To access Vue instance properties, we need to convert `provide` to be a function returning an object |
| 71 | + |
| 72 | +```js |
| 73 | +app.component('todo-list', { |
| 74 | + data() { |
| 75 | + return { |
| 76 | + todos: ['Feed a cat', 'Buy tickets'] |
| 77 | + } |
| 78 | + }, |
| 79 | + provide() { |
| 80 | + return { |
| 81 | + todoLength: this.todos.length |
| 82 | + } |
| 83 | + }, |
| 84 | + template: ` |
| 85 | + ... |
| 86 | + ` |
| 87 | +}) |
| 88 | +``` |
| 89 | + |
| 90 | +This allows us to more safely keep developing that component, without fear that we might change/remove something that a child component is relying on. The interface between these components remains clearly defined, just as with props. |
| 91 | + |
| 92 | +In fact, you can think of dependency injection as sort of “long-range props”, except: |
| 93 | + |
| 94 | +- parent components don’t need to know which descendants use the properties it provides |
| 95 | +- child components don’t need to know where injected properties are coming from |
| 96 | + |
| 97 | +## Working with reactivity |
| 98 | + |
| 99 | +In the example above, if we change the list of `todos`, this change won't be reflected in the injected `todoLength` property. This is because `provide/inject` bindings are _not_ reactive by default. We can change this behavior by passing a `ref` property or `reactive` object to `provide`. In our case, if we want to react to changes in the ancestor component, we need to assign a Composition API `computed` property to our provided `todoLength`: |
| 100 | + |
| 101 | +```js |
| 102 | +app.component('todo-list', { |
| 103 | + // ... |
| 104 | + provide() { |
| 105 | + return { |
| 106 | + todoLength: Vue.computed(() => this.todos.length) |
| 107 | + } |
| 108 | + } |
| 109 | +}) |
| 110 | +``` |
| 111 | + |
| 112 | +In this, any change to `todos.length` will be reflected correctly in the components, where `todoLength` is injected. Read more about `reactive` provide/inject in the [Composition API section](TODO) |
0 commit comments