How to access a component from another component

I want to set properties and call metgods of one component from another component. How do we do this if they dont know if each other exists and also we cant use any service. Both of these component will be rendered within the same route.

Can you describe what you’re trying to accomplish a little more? There’s probably a better pattern you could use. Component state and methods are isolated for a reason, as components reaching into each other can lead to confusion, indeterminate state, etc.

Typically the way to handle this would be do move the state and actions up to the parent (in this case the route’s controller) and use the Data Down Actions Up approach (pass the data down into the components, and fire actions back out of the components to mutate the data). Or using a service though services can sometimes be used to enable bad patterns rather than good.

1 Like

I have used a common DOM storage to communicate between components, would that be a recommended way if applicable?

Hi Tore, welcome to the Ember forum!

I would definitely not recommend that pattern for sharing state—in fact, one of the big advantages that modern component-driven frameworks (like Ember, React, Vue, etc.) have over a lot of patterns that were common in the previous generation of approaches (jQuery/Prototype/MooTools, Backbone, etc.) is that they have tools for managing shared state that don’t require putting it in the DOM—because putting it in the DOM can be pretty fragile and is hard to get right performance-wise, etc.

Most of the time when I see folks thinking about “communicating between components” (and from what you’ve said here, this seems to match) they’re actually talking about sharing state between components, in such a way that updates from one component become visible to the other. In general, the way to solve this problem is to have a shared source of truth for that data for both of them. (This is what your pattern with the DOM was doing, but in a way that’s not really how Ember approaches these things.) The options for managing that shared state:

  • If the components are both children of the same parent, have the parent manage it and hand it down to both of them. The parent would also supply an “action” to update that state.

    import Component from '@glimmer/component';
    import { action } from '@ember/object';
    
    class ParentComponent extends Component {
      @tracked someSharedState;
    
      @action updateIt(newSharedState) {
        this.someSharedState = newSharedState;
      }
    }
    

    Parent component template:

    <FirstChild
      @state={{this.someSharedState}}
      @onChange={{this.updateIt}}
    />
    <SecondChild
      @state={{this.someSharedState}}
      @onChange={{this.updateIt}}
    />
    

    Now both components will stay in sync “automatically”: both refer to the same data in the parent component, so when it gets updated, both components get updated.

  • If the components are in totally separate parts of the app, I would reach for a Service, which is Ember’s go-to solution for shared app-level state. (This is why the Ember Data “store” is a service: it represents shared app-level state!) In that case, the service would own the shared state, and your child components would both inject the service and call updates on its methods. That could look something like this:

    import Service from '@ember/service';
    import { action } from '@ember/object';
    
    export default class MyService extends Service {
      @tracked someSharedState;
    
      @action updateIt(newSharedState) {
        this.someSharedState = newSharedState;
      }
    }
    

    Either of the components might look roughly like this:

    import Component from '@glimmer/component';
    import { action } from '@ember/object';
    import { inject as service } from '@ember/service';
    
    export default class FirstChild extends Component {
      @inject myService;
      
      @action update(event) {
      	this.myService.updateIt(event.target.value);
      }
    }
    
    <div>
      Current state: {{this.myService.someSharedState}}
      <label>
      	State:
      	<input
      	  value={{this.myService.someSharedState}}
      	  {{on "input" this.update}}
        />
      </label>
    </div>
    

In both of these cases, if the shared state is something complex which should be its own object, you can also extract it to its own class which in turn uses @tracked for making its data reactive, and then you can expose that either on a parent component or in a service and hand it down, just the same.

The key is that you manage state via normal JS constructs, which are shared in the appropriate way based on the relationship between the components, rather than trying to store it in the DOM (which is now wholly managed by a modern component layer like Ember).

1 Like

Thank you, Chris, for the thorough answer! (I’m sorry being delayed commenting)

Please just add your opinion whether there may exist some (maybe rare) circumstances motivating my proposed use of a simple straightforward “global” DOM storage.

All the best / Tore

In the context of any contemporary freeing and framework, I would say no, because you don’t actually own the DOM! You’re delegating that responsibility to the framework in exchange for it handling everything about it for you. (That’s equally true of React, Vue, Angular, Elm, Aurelia, Ember… you name it.) But since you also have other tools to solve shared state, using the DOM for that is also unnecessary!