alpinejs / alpine

A rugged, minimal framework for composing JavaScript behavior in your markup.
https://alpinejs.dev
MIT License
28.17k stars 1.23k forks source link

Nested components cannot access external data #49

Closed SimoTod closed 4 years ago

SimoTod commented 4 years ago

Hi @calebporzio, thanks for the amazing work so far. I was having a go with alpinejs and I noticed that, when there are nested components, the internal component cannot access the scope of the external one.

For example,

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v1.2.0/dist/alpine.js" defer></script>
  </head>
  <body>
    <div x-data="{ foo: 'bar', foo2: 'BAR' }">
      <span x-text="foo"></span>
      <div x-data="{ foo: 'bob' }">
        <span id="s1" x-text="foo"></span>
        <span id="s2" x-text="foo2"></span>
      </div>
    </div>
  </body>
</html>

I would expect span#s2 to display 'BAR' or, in alternative, i would expect to be able to reference foo2 in the internal data structure.

I'm happy to work on a PR for this but I just wanted to check with you first in case this behaviour is expected and you do not want components to access external scopes.

Thanks, Simone

Luddinus commented 4 years ago

Not sure if it fits here but I think I need something similar.

I have a "modal component" (via Laravel components) that I want to have its "own scope"

// html
<div x-data="{ someParentVariable: 'lol' }>
    <button @click="openModal('modal-1')">Open modal</button>

   <div id="modal-1" x-data="modal()" x-init="init()">
      <template x-if="$visible">
         // make an ajax request and show the content here when $visible is true,
         // this way it will only make the request when the modal is opened.

         // If I want to access to "someParentVariable", it will be undefined
      </template>
   </div>
</div>

// modal component
function modal()
{
   return {
      $visible: false,

      init() {
         var id = this.$el.getAttribute('id');

         // the "openModal" function fires this event
         window.addEventListener(`modal:show ${id}`, () => {
            this.$visible = true;
         })
      }
   };
}

What should I do? Thanks.

earthboundkid commented 4 years ago

Change openModal() to take a second argument, which is someParentVariable, send that as part of the event, and change the event listener to get that value from the event.

Luddinus commented 4 years ago

@carlmjohnson That's not the point.

"openModal" is a "global" function and maybe it's not the best example to explain what I want. This won't work neither (I guess it is in the comments too)

<div x-data="{ foo: 'bar' }">
   <div x-data="{ baz: 'baz' }>
       <span v-text="foo"></span> // foo is undefined
   </div>
</div>
Braunson commented 4 years ago

Lots of suggested solutions but this doesn't seem to have a solid solution and is closed? What's the status of this? In development? Shelved?

SimoTod commented 4 years ago

See https://github.com/alpinejs/alpine/issues/49#issuecomment-626251114 Alpine won't implement it, at least not in v2. Basic cases can be implemented using the event pattern. For complex cases, there's is a third party library called spruce.

Braunson commented 4 years ago

Thanks @SimoTod I never knew about Spruce, super handy! I figured it'll be addressed in V3. Thanks.

HugoDF commented 4 years ago

Thanks @SimoTod I never knew about Spruce, super handy! I figured it'll be addressed in V3. Thanks.

The idea is that Alpine V3 might provide global stores (which you currently would have to use Spruce for) that's all subject to change though

ponyjackal commented 3 years ago

What if I want to pass data in x-for to child component?

<template x-for="(pet, index) in pets" :key="index">
   // I want to create a new component for pet card
   <div x-data="{name:''}" x-init={name: pet.name}>
  ..... 
   </div>
</template>

Is there any solution for this?

ponyjackal commented 3 years ago

I found the solution ))

https://twitter.com/hugo__df/status/1310611867954556929?utm_source=alpinejs&utm_medium=email

However, I want to know if this is the best one for now?

KevinBatdorf commented 3 years ago

@PonyJackal Yes that's a fine solution. Or otherwise there are some helpers here you can use ($parent/$component):

https://github.com/alpine-collective/alpine-magic-helpers

And if you want something more to store and access all your data, try Spruce:

https://github.com/ryangjchandler/spruce

ponyjackal commented 3 years ago

I tried to use $parent, but $parent is undefined

KevinBatdorf commented 3 years ago

You have to include the helper's script on your page.

https://cdn.jsdelivr.net/gh/alpine-collective/alpine-magic-helpers@0.5.x/dist/component.min.js

Here's a demo: https://codepen.io/KevinBatdorf/pen/ZEpMeJJ

Note that I used $el.__x_for.c - 1 to get the x-for index. I'm not sure how reliable that is. You may want to not use `x-for and instead implement the insertion manually.

guillermo7227 commented 3 years ago

I solved it like this:

<div x-data="{myVariableInParent: 'blah'}" x-bind:data-my-variable-in-parent="myVariableInParent">
    <div x-data="{myLocalVariable: 'muah'}" x-init="myLocalVariable = $el.closest('[data-my-variable-in-parent]').dataset.myVariableInParent">
         <span x-text="myLocalVariable"></span> <!-- will be 'blah' -->
    </div>
</div>

Based on @KevinBatdorf 's solution