Repository: vuejs/vue
Pull request: close #6097: Allow defining optional inject dependency with default values

Allow to define inject as object with name and default properties. The default is used if no provide
is found, and no warning is emitted.

close #6097

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [ ] Bugfix
- [X] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [X] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [X] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [X] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [X] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [X] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [X] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**

In case of components that can work both as standalone and as children, it would be nice to be able to provide default values for the not provided dependencies.
It would also avoid the warning for missing inject in case this is one of the expected use case.

inject: Array | { [key: string]: string | Symbol | { name?: string | Symbol, default?: any } }

Changed files:
filename: src/core/instance/inject.js
patch: @@ -49,7 +49,7 @@ export function resolveInject (inject: any, vm: Component): ?Object {
 
     for (let i = 0; i < keys.length; i++) {
       const key = keys[i]
-      const provideKey = inject[key]
+      const provideKey = inject[key].name
       let source = vm
       while (source) {
         if (source._provided && provideKey in source._provided) {
@@ -58,8 +58,15 @@ export function resolveInject (inject: any, vm: Component): ?Object {
         }
         source = source.$parent
       }
-      if (process.env.NODE_ENV !== 'production' && !source) {
-        warn(`Injection "${key}" not found`, vm)
+      if (!source) {
+        if ('default' in inject[key]) {
+          const provideDefault = inject[key].default
+          result[key] = typeof provideDefault === 'function'
+            ? provideDefault.call(vm)
+            : provideDefault
+        } else if (process.env.NODE_ENV !== 'production') {
+          warn(`Injection "${key}" not found`, vm)
+        }
       }
     }
     return result

filename: src/core/util/options.js
patch: @@ -270,10 +270,17 @@ function normalizeProps (options: Object) {
  */
 function normalizeInject (options: Object) {
   const inject = options.inject
+  const normalized = options.inject = {}
   if (Array.isArray(inject)) {
-    const normalized = options.inject = {}
     for (let i = 0; i < inject.length; i++) {
-      normalized[inject[i]] = inject[i]
+      normalized[inject[i]] = { name: inject[i] }
+    }
+  } else if (isPlainObject(inject)) {
+    for (const key in inject) {
+      const val = inject[key]
+      normalized[key] = isPlainObject(val)
+        ? extend({ name: key }, val)
+        : { name: val }
     }
   }
 }

filename: test/unit/features/options/inject.spec.js
patch: @@ -370,6 +370,45 @@ describe('Options provide/inject', () => {
     expect(`Injection "__ob__" not found`).not.toHaveBeenWarned()
   })
 
+  // Github issue #6097
+  it('should not warn when injections cannot be found but have default value', () => {
+    const vm = new Vue({})
+    new Vue({
+      parent: vm,
+      inject: {
+        foo: { default: 1 },
+        bar: { default: false },
+        baz: { default: undefined }
+      },
+      created () {}
+    })
+    expect(`Injection "foo" not found`).not.toHaveBeenWarned()
+    expect(`Injection "bar" not found`).not.toHaveBeenWarned()
+    expect(`Injection "baz" not found`).not.toHaveBeenWarned()
+  })
+
+  it('should use provided value even if inject has default', () => {
+    const vm = new Vue({
+      provide: {
+        foo: 1,
+        bar: false,
+        baz: undefined
+      }
+    })
+    new Vue({
+      parent: vm,
+      inject: {
+        foo: { default: 2 },
+        bar: { default: 2 },
+        baz: { default: 2 }
+      },
+      created () {
+        injected = [this.foo, this.bar, this.baz]
+      }
+    })
+    expect(injected).toEqual([1, false, undefined])
+  })
+
   // Github issue #6008
   it('should merge provide from mixins (objects)', () => {
     const mixinA = { provide: { foo: 'foo' }}