> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ably/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Avatar stack

Avatar stacks are the most common way of showing the online status of members in an application by displaying an avatar for each member. Events are emitted whenever a member enters or leaves a space, or updates their profile data. Additional information can also be provided, such as a profile picture and email address.

Subscribe to the `space.members` namespace in order to keep your avatar stack updated in realtime.

## Event types

The following four event types are emitted by members:

| Event           | Description                                                                                                                                                                                                                                                                                      |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enter`         | A new member has entered the space. The member has either entered explicitly by calling [`space.enter()`](/docs/spaces/space#enter), or has attempted to update their profile data before entering a space, which will instead emit an `enter` event.                                            |
| `updateProfile` | A member has updated their profile data by calling [`space.updateProfileData()`](/docs/spaces/space#update-profile).                                                                                                                                                                             |
| `leave`         | A member has left the space. The member has either left explicitly by calling [`space.leave()`](/docs/spaces/space#leave), or has abruptly disconnected and not re-established a connection within 15 seconds.                                                                                   |
| `remove`        | A member has been removed from the members list after the [`offlineTimeout`](/docs/spaces/space#options) period has elapsed. This enables members to appear greyed out in the avatar stack to indicate that they recently left for the period of time between their `leave` and `remove` events. |
| `update`        | This event is emitted whenever any one of the above events is emitted.                                                                                                                                                                                                                           |

<Aside data-type="note">
  Members [enter](/docs/spaces/space#enter), [leave](/docs/spaces/space#leave), and [update](/docs/spaces/space#update-profile) a [space](/docs/spaces/space) directly. The `members` namespace is used to subscribe to these updates.
</Aside>

## Subscribe to member events

Subscribe to members' online status and profile updates by registering a listener. Member events are emitted whenever a member [enters](/docs/spaces/space#enter) or [leaves](/docs/spaces/space#leave) the space, or updates their profile data. Use the [`subscribe()`](https://sdk.ably.com/builds/ably/spaces/main/typedoc/classes/Members.html#subscribe) method on the `members` object of a space to receive updates.

<Code>
  ```javascript theme={null}
  // Subscribe to member enters in a space
  space.members.subscribe('enter', (memberUpdate) => {
    console.log(memberUpdate);
  });

  // Subscribe to member profile data updates in a space
  space.members.subscribe('updateProfile', (memberUpdate) => {
    console.log(memberUpdate);
  });

  // Subscribe to member leaves in a space
  space.members.subscribe('leave', (memberUpdate) => {
    console.log(memberUpdate);
  });

  // Subscribe to member removals in a space
  space.members.subscribe('remove', (memberUpdate) => {
    console.log(memberUpdate);
  });
  ```
</Code>

It's also possible to subscribe to multiple event types with the same listener by using an array:

<Code>
  ```javascript theme={null}
  space.members.subscribe(['enter', 'updateProfile'], (memberUpdate) => {
    console.log(memberUpdate);
  });
  ```
</Code>

Or subscribe to all event types:

<Code>
  ```javascript theme={null}
  space.members.subscribe('update', (memberUpdate) => {
    console.log(memberUpdate);
  });
  ```
</Code>

The following is an example payload of a member event. The `lastEvent.name` describes which [event type](#events) a payload relates to.

<Code>
  ```json theme={null}
  {
    "clientId": "clemons#142",
    "connectionId": "hd9743gjDc",
    "isConnected": true,
    "lastEvent": {
      "name": "enter",
      "timestamp": 1677595689759
    },
    "location": null,
    "profileData": {
      "username": "Claire Lemons",
      "avatar": "https://slides-internal.com/users/clemons.png"
    }
  }
  ```
</Code>

The following are the properties of a member event payload:

| Property            | Description                                                                                                                   | Type    |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------- |
| clientId            | The [client identifier](/docs/auth/identified-clients) for the member.                                                        | String  |
| connectionId        | The unique identifier of the member's [connection](/docs/connect).                                                            | String  |
| isConnected         | Whether the member is connected to Ably or not.                                                                               | Boolean |
| profileData         | The optional [profile data](#profile-data) associated with the member.                                                        | Object  |
| location            | The current [location](/docs/spaces/member-location) of the member. Will be `null` for `enter`, `leave`, and `remove` events. | Object  |
| lastEvent.name      | The most recent event emitted by the member.                                                                                  | String  |
| lastEvent.timestamp | The timestamp of the most recently emitted event.                                                                             | Number  |

<Aside data-type="further-reading">
  Avatar stack subscription listeners only trigger on events related to members' online status and profile updates. Each event only contains the payload of the member that triggered it. Alternatively, [space state](/docs/spaces/space) can be subscribed to which returns an array of all members with their latest state every time any event is triggered.
</Aside>

### Unsubscribe from member events

Unsubscribe from member events to remove previously registered listeners.

The following is an example of removing a listener for one member event type:

<Code>
  ```javascript theme={null}
  space.members.unsubscribe('enter', listener);
  ```
</Code>

It's also possible to remove listeners for multiple member event types:

<Code>
  ```javascript theme={null}
  space.members.unsubscribe(['enter', 'leave'], listener);
  ```
</Code>

Or remove all listeners:

<Code>
  ```javascript theme={null}
  space.members.unsubscribe();
  ```
</Code>

## Retrieve members

Space membership can be retrieved in one-off calls. These are local calls and retrieve the membership retained in memory by the SDK. One-off calls to retrieve membership can be used for operations such as displaying a member's own profile data to them, or retrieving a list of all other members to use to [update their profile data](/docs/spaces/space#update-profile).

The following is an example of retrieving a member's own member object:

<Code>
  ```javascript theme={null}
  const myMemberInfo = await space.members.getSelf();
  ```
</Code>

The following is an example payload returned by `space.members.getSelf()`:

<Code>
  ```json theme={null}
  {
    "clientId": "clemons#142",
    "connectionId": "hd9743gjDc",
    "isConnected": true,
    "lastEvent": {
      "name": "enter",
      "timestamp": 1677595689759
    },
    "location": null,
    "profileData": {
      "username": "Claire Lemons",
      "avatar": "https://slides-internal.com/users/clemons.png"
    }
  }
  ```
</Code>

The following is an example of retrieving an array of member objects for all members other than the member themselves. Ths includes members that have recently left the space, but have not yet been removed.

<Code>
  ```javascript theme={null}
  const othersMemberInfo = await space.members.getOthers();
  ```
</Code>

The following is an example payload returned by [`space.members.getOthers()`](https://sdk.ably.com/builds/ably/spaces/main/typedoc/classes/Members.html#getOthers):

<Code>
  ```json theme={null}
  [
    {
      "clientId": "torange#1",
      "connectionId": "tt7233ghUa",
      "isConnected": true,
      "lastEvent": {
        "name": "enter",
        "timestamp": 167759566354
      },
      "location": null,
      "profileData": {
        "username": "Tara Orange",
        "avatar": "https://slides-internal.com/users/torange.png"
      }
    },
    {
      "clientId": "amint#5",
      "connectionId": "hg35a4fgjAs",
      "isConnected": true,
      "lastEvent": {
        "name": "update",
        "timestamp": 173459567340
      },
      "location": null,
      "profileData": {
        "username": "Arit Mint",
        "avatar": "https://slides-internal.com/users/amint.png"
      }
    }
  ]
  ```
</Code>

The following is an example of retrieving an array of all member objects, including the member themselves. Ths includes members that have recently left the space, but have not yet been removed.

<Code>
  ```javascript theme={null}
  const allMembers = await space.members.getAll();
  ```
</Code>

The following is an example payload returned by [`space.members.getAll()`](https://sdk.ably.com/builds/ably/spaces/main/typedoc/classes/Members.html#getAll):

<Code>
  ```json theme={null}
  [
    {
      "clientId": "clemons#142",
      "connectionId": "hd9743gjDc",
      "isConnected": false,
      "lastEvent": {
        "name": "enter",
        "timestamp": 1677595689759
      },
      "location": null,
      "profileData": {
        "username": "Claire Lemons",
        "avatar": "https://slides-internal.com/users/clemons.png"
      }
    },
    {
      "clientId": "amint#5",
      "connectionId": "hg35a4fgjAs",
      "isConnected": true,
      "lastEvent": {
        "name": "update",
        "timestamp": 173459567340
      },
      "location": null,
      "profileData": {
        "username": "Arit Mint",
        "avatar": "https://slides-internal.com/users/amint.png"
      }
    },
    {
      "clientId": "torange#1",
      "connectionId": "tt7233ghUa",
      "isConnected": true,
      "lastEvent": {
        "name": "enter",
        "timestamp": 167759566354
      },
      "location": null,
      "profileData": {
        "username": "Tara Orange",
        "avatar": "https://slides-internal.com/users/torange.png"
      }
    }
  ]
  ```
</Code>

## Example usage

The following is an example of the steps involved in implementing an avatar stack.

<Aside data-type="note">
  There is also an [interactive example](https://examples.ably.dev/avatar-stack) demonstrating the functionality of avatar stacks and a [demo slideshow application](https://space.ably.dev/) available that highlights all the features of the Spaces SDK.
</Aside>

<Code>
  ```javascript theme={null}
  import Spaces from '@ably/spaces';
  import { Realtime } from 'ably';

  // Import your custom logic for handling avatar stack UI updates
  import { renderAvatars } from '/src/own-logic';

  // Create an Ably client
  const client = new Realtime({ authUrl: '<authEndpoint>', clientId: '<clientId>' });

  // Initialize the Spaces SDK using the Ably client
  const spaces = new Spaces(client);

  // Create a new space and set the offlineTimeout period to 3 minutes
  const space = await spaces.get('board-presentation', { offlineTimeout: 180_000});

  // Enter the space to become a member, passing a nickname
  await space.enter({ name: 'Kyle' });

  // Listen to all member changes within the space
  space.members.subscribe('update', async (member) => {
    // Use getOthers() to filter and update the state of all avatars except the member's own
    const otherMembers = await space.members.getOthers();
    renderAvatars(otherMembers);
  });

  // Listen to 'leave' events to update the avatars of those members who have left the space
  space.members.subscribe('leave', (member) => {
    renderAvatars('memberHasLeft', member);
  });

  // Listen to 'remove' events to remove members from the avatar stack after the offlineTimeout has elapsed
  space.members.subscribe('remove', (member) => {
    renderAvatars('removeMember', member);
  });
  ```
</Code>

## Avatar stack foundations

The Spaces SDK is built upon existing Ably functionality available in Ably's Core SDKs. Understanding which core features are used to provide the abstractions in the Spaces SDK enables you to manage space state and build additional functionality into your application.

Avatar stacks build upon the functionality of the Ably Pub/Sub [presence](/docs/presence-occupancy/presence) feature. Members are entered into the presence set when they [enter the space](/docs/spaces/space#enter).

<Aside data-type="usp">
  Reliable avatar presence

  Ably automatically [recovers connections](/docs/platform/architecture/idempotency#connection-recovery-and-exactly-once-delivery) when clients briefly disconnect, so members don't flicker in and out of the avatar stack during temporary network interruptions.
</Aside>
