# Upgrade to v8

> **Info:** This is release candidate documentation for **Stream Chat Angular SDK v8**. For the latest stable version, see the [latest version (v7)](https://getstream.io/chat/docs/sdk/angular/).

This page lists what changed between v7 and v8, and what to change in your app.

v8 is built on `stream-chat` (the JavaScript client) v10. Most of the SDK's own API (components, services, templates) is unchanged, but some types and behaviors changed because `stream-chat` v10 changed its data model. If your app calls the `stream-chat` client directly (`chatService.chatClient`, `channel.*`), the most common client changes are summarized in [Using the stream-chat client directly](#using-the-stream-chat-client-directly). The complete list is [stream-chat v9 to v10 breaking changes](https://github.com/GetStream/stream-chat-js/blob/release-v10/ai-docs/v10-breaking-changes.md) in the `stream-chat` repository.

## Quick migration checklist

1. **Upgrade both packages:** `npm install stream-chat-angular@rc stream-chat@rc`. See [Installation and requirements](#installation-and-requirements).
2. **Stream API v2 changes:** all response dates are `TimestampNS`, and all custom data is sent in the `custom` wrapper. See [Stream API v2 changes](#stream-api-v2-changes).
3. **Move client options under `client`** in `ChatClientService.init`. See [Client initialization](#client-initialization).
4. **Pass the channel list sort as an array:** `[{ field: "last_message_at", direction: -1 }]`. See [Sort](#sort).
5. **Prefix custom fields in channel filters with `custom.`.** See [Channel list](#channel-list).
6. **Read custom data from `custom`:** a channel's name is `channel.data?.custom?.name`. See [Custom data is under custom](#custom-data-is-under-custom).
7. **Convert message dates with `nsToDate`:** `StreamMessage` dates are `TimestampNS`, not `Date`. See [StreamMessage and other message types](#streammessage-and-other-message-types).
8. **Switch custom channel queries to `queryChannelsAndHydrate`.** See [Custom channel queries](#custom-channel-queries).
9. **Stop reading messages from `channel.state`.** Use the SDK's observables or the channel's message paginator. See [Message list and pagination](#message-list-and-pagination).
10. **Add `custom: {}` to attachments you create, and read attachment metadata from `attachment.custom`.** See [Attachments and voice recordings](#attachments-and-voice-recordings).
11. **Replace `connection.changed` listeners** with `connectionState$`. See [Connection state](#connection-state).
12. **Check bounced and blocked messages with `message.moderation`** instead of `moderation_details`. See [Moderation](#moderation).
13. **Add the new translation key** if you provide your own translations. See [Translations](#translations).
14. **Update error handling for `StreamAPIError`:** it replaces `ErrorFromResponse`, and its `message` no longer has the `StreamChat error code` prefix. See [Error handling](#error-handling).

## Installation and requirements

v8 is a release candidate. Install it, together with `stream-chat` v10, from the `rc` tag:

```bash
npm install stream-chat-angular@rc stream-chat@rc
```

- The `stream-chat` peer dependency changed from `^9.20.0` to `^10.0.0`.
- `stream-chat` v10 requires Node.js 22.18.0 or later for installation and builds.
- The `stream-chat` v10 type definitions work with TypeScript 5.2 or later (the minimum supported by Angular 17).
- Angular 17 to 22 are still supported, see [Angular version support](https://getstream.io/chat/docs/sdk/angular/v8/basics/angular-version-support/).
- The CSS (`@stream-io/stream-chat-css`) and theming didn't change.

## Stream API v2 changes

`stream-chat` v10 uses v2 of the Stream API. Two changes affect almost every part of your app:

- **All response dates are `TimestampNS`.** Date fields in API responses (and in the state built from them) are unix nanoseconds, a branded `number`, instead of `Date` objects or ISO strings. Convert them with the `nsToDate` helper exported by `stream-chat` when you need a `Date`. See [StreamMessage and other message types](#streammessage-and-other-message-types).
- **All custom data is sent in the `custom` wrapper.** Custom fields of channels, messages, users, attachments, reactions, polls and events are no longer top-level fields: they are sent and returned under the entity's `custom` object. This applies to channel filters as well (`custom.` prefix). See [Custom data is under custom](#custom-data-is-under-custom) and [Filters](#filters).

## Client initialization

### Client options

The last parameter of [`ChatClientService.init`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChatClientService/#init) is now the `stream-chat` v10 configuration tree (`DeepPartial<InstanceConfigTree>`). Client options moved under the `client` key, and axios options under `client.axiosRequestConfig`. `trackPendingChannelInvites` stays a top-level option.

In v7:

```ts
chatService.init(apiKey, userId, token, {
  timeout: 10000,
  trackPendingChannelInvites: true,
});
```

In v8:

```ts
chatService.init(apiKey, userId, token, {
  client: { axiosRequestConfig: { timeout: 10000 } },
  trackPendingChannelInvites: true,
});
```

The [StreamChat options mapping](https://github.com/GetStream/stream-chat-js/blob/release-v10/ai-docs/v10-breaking-changes.md#streamchat-options) lists where every option moved.

### User parameter

The type of the `userOrId` parameter changed from `string | OwnUserResponse | UserResponse` to `string | ClientUser`. `ClientUser` is exported by `stream-chat`: it's a partial `OwnUserResponse` with a required `id`. You can pass `{ id, name, image }` objects as before. Custom user fields go under `custom`, see [Custom data is under custom](#custom-data-is-under-custom).

### State recovery after reconnect

The SDK no longer sets `recoverStateOnReconnect` (the option doesn't exist in `stream-chat` v10). Connection recovery is on by default. After a reconnect, the SDK refreshes the channel list as before, and `stream-chat` reloads the active channel and the open thread.

Don't turn connection recovery off (`client.connectionRecovery.enabled: false`). Then `stream-chat` doesn't dispatch the `connection.recovered` event, and the SDK doesn't refresh the channel list after a reconnect.

## Connection state

`stream-chat` v10 no longer emits the `connection.changed` event. [`ChatClientService.connectionState$`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChatClientService/) (`'online' | 'offline'`) and the "Connection failure, reconnecting now..." notification still work, they are now based on the WebSocket's health.

`connectionState$` switches to `'offline'` only if the connection stays unhealthy for 5 seconds. A shorter outage emits nothing.

If you listened to `connection.changed` on `ChatClientService.events$` or with `chatClient.on`, use `connectionState$` instead (or `chatClient.wsConnection.state` if you need the raw WebSocket health):

```ts
chatService.connectionState$.subscribe((state) => {
  console.log(`Chat is ${state}`);
});
```

## Channel list

### Filters

`ChannelFilters` is now the `stream-chat` v10 type. Custom fields need the `custom.` prefix, and each key allows only one operator (combine more with `$and`).

In v7:

```ts
channelService.init({ type: "messaging", color: "green" });
```

In v8:

```ts
channelService.init({ type: "messaging", "custom.color": "green" });
```

### Sort

The `sort` parameter of [`ChannelService.init`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#init) (and the `ChannelQuery` constructor) takes the `stream-chat` v10 sort format: an array of `{ field, direction }` objects (`SortParamRequest[]` from `stream-chat`). The object format (`{ last_message_at: -1 }`) is no longer accepted, and the `ChannelSort` type no longer exists.

In v7:

```ts
channelService.init(filters, { last_message_at: -1 });
```

In v8:

```ts
import { SortParamRequest } from "stream-chat";

const sort: SortParamRequest[] = [{ field: "last_message_at", direction: -1 }];
channelService.init(filters, sort);
```

The order of the array sets the sort priority, the same way the key order of the object format did.

### Custom channel queries

In `stream-chat` v10, `client.queryChannels` returns the raw API response and doesn't create `Channel` instances. If your [`initWithCustomQuery`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#initwithcustomquery) function called `queryChannels`, switch to `queryChannelsAndHydrate`. Filter, sort and options move into one request object.

In v7:

```ts
channelService.initWithCustomQuery(async () => {
  const channels = await client.queryChannels(
    filters,
    { last_message_at: -1 },
    { limit: 25 },
  );
  return { channels, hasMorePage: channels.length === 25 };
});
```

In v8:

```ts
channelService.initWithCustomQuery(async () => {
  const channels = await client.queryChannelsAndHydrate({
    filter_conditions: filters,
    sort: [{ field: "last_message_at", direction: -1 }],
    limit: 25,
  });
  return { channels, hasMorePage: channels.length === 25 };
});
```

The [Channel list state](https://getstream.io/chat/docs/sdk/angular/v8/concepts/channel-list/) page explains how the channel list works.

## Custom data is under custom

In `stream-chat` v10, every custom field lives under the `custom` object of its entity (channel, message, user, attachment, reaction, poll, event). Update the templates and code that read or write custom data.

### Channel name and image

`name` and `image` are custom channel fields, so they moved too.

In v7:

```html
{{ channel.data?.name }} <img [src]="channel.data?.image" />
```

In v8:

```html
{{ channel.data?.custom?.name }} <img [src]="channel.data?.custom?.image" />
```

The SDK's `DefaultChannelData` type now types `channel.data.custom.name` and `channel.data.custom.image`. If you extend `CustomChannelData` (see [Custom types](https://getstream.io/chat/docs/sdk/angular/v8/concepts/custom-types/)), your fields are typed under `channel.data.custom` as well.

### Messages

Custom message fields are read from `message.custom` (for example `message.custom.priority`). The `customData` parameter of `ChannelService.sendMessage` is sent as `custom`, you don't need to change how you call it. The `customData` returned by `beforeSendMessage` works the same way.

### Users, reactions and events

- Custom user fields are under `user.custom`. `name` and `image` stay top-level user fields.
- The `customData` parameter of `ChannelService.addReaction` is sent as `reaction.custom`.
- Custom event fields are under `event.custom`.

## StreamMessage and other message types

[`StreamMessage`](https://getstream.io/chat/docs/sdk/angular/v8/types/stream-message/) is now based on the `stream-chat` v10 `LocalMessage` type instead of `FormatMessageResponse` (removed in v10). The SDK doesn't convert the date fields: like everywhere in `stream-chat` v10, they are `TimestampNS` values (unix nanoseconds, a branded `number`). Convert them with the `nsToDate` helper exported by `stream-chat` when you need a `Date`.

In v7:

```ts
const sentAt = message.created_at.toLocaleString();
```

In v8:

```ts
import { nsToDate } from "stream-chat";

const sentAt = nsToDate(message.created_at).toLocaleString();
```

Angular's `date` pipe treats numbers as milliseconds, so convert the date in your component (or with your own pipe) before passing it to the pipe.

| Change                                                                                                        | v7                                      | v8                                                                            |
| ------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------- |
| Base type                                                                                                     | `FormatMessageResponse`                 | `LocalMessage`                                                                |
| Date fields (`created_at`, `updated_at`, `deleted_at`, `pinned_at`, `pin_expires`, `message_text_updated_at`) | `Date` (or `null`)                      | `TimestampNS` (unix nanoseconds), optional fields are `undefined`, not `null` |
| `quoted_message`                                                                                              | `MessageResponseBase & { translation }` | `MessageResponse & { translation }` (dates are `TimestampNS`)                 |
| Custom fields                                                                                                 | top-level                               | under `custom`                                                                |
| `moderation_details`                                                                                          | moderation result (v1)                  | removed, use `moderation` (see [Moderation](#moderation))                     |
| `__html`                                                                                                      | set on message previews                 | removed                                                                       |

[`DateParserService`](https://getstream.io/chat/docs/sdk/angular/v8/services/DateParserService/) and custom date parsers still receive `Date` objects: the SDK converts the message dates before calling them. The date separator template context (`DateSeparatorContext.date`) is also a `Date`.

If you need the exact object `stream-chat` stores (for example to call a `stream-chat` API), get it from the channel's message paginator: `channel.messagePaginator.getItem(message.id)`.

`stream-chat` no longer exports these message types: `FormatMessageResponse` (use `LocalMessage`), `MessageResponseBase` (use `MessageResponse`), `DraftMessagePayload` (use `MessageRequest`) and `CommandResponse` (use `Command`).

## Message list and pagination

`stream-chat` v10 removed `channel.state.messages`, `latestMessages`, `messageSets`, `threads`, `pinnedMessages` and `loadMessageIntoState`. Messages now live in `channel.messagePaginator`, pinned messages in `channel.pinnedMessagesPaginator` and thread replies in `thread.messagePaginator`. The SDK's observables (`activeChannelMessages$`, `activeThreadMessages$`, `activeChannelPinnedMessages$`) work as before, but code that read the channel state directly has to change:

| v7                                                                   | v8                                                                                                                               |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `channelService.activeChannel.state.messages`                        | `channelService.activeChannelMessages$` or `channel.messagePaginator.items`                                                      |
| `channel.state.latestMessages`                                       | `channel.messagePaginator.anchoredHeadItems` (latest loaded messages), `channel.messagePaginator.latestMessage` (latest message) |
| `channel.state.pinnedMessages`                                       | `channelService.activeChannelPinnedMessages$` or `channel.pinnedMessagesPaginator.items`                                         |
| `channel.state.threads[parentId]`                                    | `channelService.activeThreadMessages$` or `client.threads.get(parentId)?.messagePaginator.items`                                 |
| `channel.state.loadMessageIntoState(id)`                             | `channelService.jumpToMessage(id)`                                                                                               |
| `channel.lastMessage()`                                              | `channel.messagePaginator.latestMessage`                                                                                         |
| `channel.state.messages = []` / `addMessageSorted` / `removeMessage` | `channel.messagePaginator.removeItem({ id })` / `ingestItem(message)` (v10 `LocalMessage`)                                       |

### loadMoreMessages and loadMoreThreadReplies

- The return type of [`loadMoreMessages`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#loadmoremessages) changed from `false | Promise<QueryChannelAPIResponse>` to `false | Promise<void>`. [`loadMoreThreadReplies`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#loadmorethreadreplies) still returns `false | Promise<void>`.
- Both return `false` (no request) if there's nothing more to load in the given direction, a page is already loading, or no message is loaded yet. If the request fails, the returned promise rejects.
- `loadMoreThreadReplies('newer')` now loads newer replies (in v7 it always returned `false`). This happens after jumping to an older reply.

### jumpToMessage

The signature of [`jumpToMessage`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#jumptomessage) is unchanged, but it now uses the message paginator of `stream-chat` v10:

- If the message isn't found or can't be loaded, the promise rejects and the "Message not found" notification is displayed (same as before).
- Jumping to a thread reply also loads the parent message into the channel's message list if needed, then opens the thread and jumps to the reply.

### Channel preview

The type of [`ChannelPreviewComponent`](https://getstream.io/chat/docs/sdk/angular/v8/components/ChannelPreviewComponent/)'s `latestMessage` changed from `FormatMessageResponse` to `LocalMessage`, and the `latestMessage` field of the channel preview info template context (`ChannelPreviewInfoContext`) changed from `StreamMessage` to `LocalMessage`. Its dates are `TimestampNS`, and it doesn't have the `readBy` and `translation` fields. The latest message now comes from `channel.messagePaginator`, so custom channel preview templates no longer need `message.new`, `message.updated` and `message.deleted` listeners to stay up to date.

## Threads

- In `stream-chat` v10, channels don't store thread replies. Replies are stored by `Thread` instances held by `client.threads`. The SDK creates the `Thread` (`client.threads.ensure`) when you call [`setAsActiveParentMessage`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#setasactiveparentmessage).
- `channel.getReplies` was removed in v10, and the SDK no longer calls it. If your code did, use `client.threads.get(parentId)?.messagePaginator`, or the raw `client.getReplies({ parent_id }, options)` request.
- The second parameter of `setAsActiveParentMessage` (`loadMessagesFrom`) is deprecated and ignored: replies are always loaded by the thread.
- The SDK calls `client.threads.registerSubscriptions()` in `ChannelService.init`, threads need it to receive WebSocket events. Don't call `client.threads.unregisterSubscriptions()`, it disposes the reply lists of the threads.

## Custom event handlers and setters

The custom event handlers of [`ChannelService`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/) (`customNewMessageHandler`, `customChannelUpdatedHandler`, `customChannelTruncatedHandler`, `customChannelDeletedHandler`, `customChannelHiddenHandler`, `customChannelVisibleHandler`, `customNewMessageNotificationHandler`, `customAddedToChannelNotificationHandler` and `customRemovedFromChannelNotificationHandler`) keep their signatures, with these changes:

- The `event` parameter (and `ClientEvent.event`) is the v10 `Event` type. It's a union of the event types: a field only exists on the events that have it, so narrow by `event.type` first:

  ```ts
  if (event.type === "notification.message_new") {
    const channel = event.channel;
  }
  ```

  `stream-chat` renamed `EventTypes` to `EventType`.

- `messageListSetter` and `threadListSetter` still work, but the message lists are owned by the message paginators of `stream-chat`: the next paginator update (for example the next message) overwrites the list you set. To permanently change a message list, update the paginator instead (`channel.messagePaginator.removeItem` / `ingestItem`).
- `channel.updated`: the SDK no longer copies `event.channel` into `channel.data`, `stream-chat` v10 does it (keeping `hidden`, `own_capabilities` and `member_count`).
- `channel.truncated`: the SDK no longer clears `channel.state.messages`, `stream-chat` v10 truncates the message list.
- The SDK no longer handles `message.updated`, `message.deleted` and `reaction.*` events itself: the message lists are updated from the message paginators.

## Attachments and voice recordings

In `stream-chat` v10, attachment metadata moved under `attachment.custom`. This includes the fields added by the SDK:

| v7                                | v8                                       |
| --------------------------------- | ---------------------------------------- |
| `attachment.mime_type`            | `attachment.custom.mime_type`            |
| `attachment.file_size`            | `attachment.custom.file_size`            |
| `attachment.duration`             | `attachment.custom.duration`             |
| `attachment.waveform_data`        | `attachment.custom.waveform_data`        |
| `attachment.img_url` (deprecated) | `attachment.custom.img_url` (deprecated) |
| `attachment.isCustomAttachment`   | `attachment.custom.isCustomAttachment`   |

- Every attachment needs a `custom` object (it can be empty). Add `custom: {}` to the attachments you create, for example with [`AttachmentService.addAttachment`](https://getstream.io/chat/docs/sdk/angular/v8/services/AttachmentService/#addattachment):

  ```ts
  attachmentService.addAttachment({
    type: "image",
    image_url: "https://example.com/image.png",
    custom: {},
  });
  ```

- `AttachmentUpload.extraData` is merged into the attachment, so put custom metadata in `extraData.custom`.
- `GalleryAttachment` (the gallery context of [custom attachment](https://getstream.io/chat/docs/sdk/angular/v8/guides/custom-attachments/) templates) has a new, required `custom` property.
- `DefaultAttachmentData` has two new fields: `duration` and `waveform_data`.
- If you have custom attachment templates, read the fields from their new location.
- Custom upload and delete handlers (`customImageUploadRequest`, `customFileUploadRequest`, `customImageDeleteRequest`, `customFileDeleteRequest`) are unchanged. The default upload requests now use `channel.uploadImage` / `uploadFile` (`channel.sendImage` / `sendFile` were removed in v10).
- [`AttachmentListComponent`](https://getstream.io/chat/docs/sdk/angular/v8/components/AttachmentListComponent/) compares attachment URLs without their query string. Attachment URLs are signed, and some message updates (for example adding a reaction) renew the signature, so `message.attachments` can change even if the attachments are the same. `AttachmentListComponent` ignores these changes, so images and videos aren't reloaded. If you display attachments with your own component instead of `stream-attachment-list`, you may want to do the same.

## Message input and drafts

- [`MessageInputComponent.messageDraftChange`](https://getstream.io/chat/docs/sdk/angular/v8/components/MessageInputComponent/#messagedraftchange) emits `MessageRequest | undefined` (it was `DraftMessagePayload | undefined`, removed in v10). The fields are the same (`text`, `attachments`, `mentioned_users`, `poll_id`, `parent_id`, `quoted_message_id`).
- `MessageInputComponent.loadDraft` takes the v10 `DraftResponse`: `draft.message.mentioned_users` is now a list of users (it was a list of ids), and dates are `TimestampNS`. To save a draft with `stream-chat` v10, call `channel.createDraft({ message: draft })`.
- Slash commands come from `channel.config.availableCommands` (it was `channel.getConfig().commands`). `ComandAutocompleteListItem` is now based on `Command` (it was `CommandResponse`).

## Polls

- `stream-chat` v10 renamed the poll types, and the SDK's poll components use the new names. This matters for custom poll templates and if you use the poll components directly:

  | v7                       | v8                       |
  | ------------------------ | ------------------------ |
  | `PollOption`             | `PollOptionResponseData` |
  | `PollVote`, `PollAnswer` | `PollVoteResponseData`   |

  Affected inputs: [`PollVoteComponent.vote`](https://getstream.io/chat/docs/sdk/angular/v8/components/PollVoteComponent/), [`UpsertAnswerComponent.answer`](https://getstream.io/chat/docs/sdk/angular/v8/components/UpsertAnswerComponent/), [`PollVoteResultsListComponent.option`](https://getstream.io/chat/docs/sdk/angular/v8/components/PollVoteResultsListComponent/) and [`PollOptionSelectorComponent.option`](https://getstream.io/chat/docs/sdk/angular/v8/components/PollOptionSelectorComponent/).

- `VotingVisibility` is now a string union type (`'anonymous' | 'public'`), not an enum.
- Poll, option, vote and answer dates are `TimestampNS` (unix nanoseconds), convert them with `nsToDate`.
- Sort options of poll queries use the array format, for example `[{ field: 'created_at', direction: -1 }]`.

## Reactions

- [`MessageReactionsComponent.messageReactionGroups`](https://getstream.io/chat/docs/sdk/angular/v8/components/MessageReactionsComponent/) uses the v10 `ReactionGroupResponse`: `first_reaction_at` and `last_reaction_at` are `TimestampNS`.
- Custom reaction fields are under `reaction.custom`.
- `ChannelService.getMessageReactions` (deprecated) and [`MessageReactionsService.queryReactions`](https://getstream.io/chat/docs/sdk/angular/v8/services/MessageReactionsService/) work as before.

## Moderation

`stream-chat` v10 messages no longer have `moderation_details`, use `message.moderation` instead:

- Bounced messages: `message.type === 'error' && message.moderation?.action === 'bounce'`.
- Blocked messages: `message.type === 'error' && message.moderation?.action === 'remove'`.

If your custom message template or [`MessageBouncePromptComponent`](https://getstream.io/chat/docs/sdk/angular/v8/components/MessageBouncePromptComponent/) customization checked `moderation_details`, switch to `moderation`.

## Other type changes

| Type / API                                                                      | v7                                                              | v8                                                                      |
| ------------------------------------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `ChatClientService.appSettings$` / `AppSettings`                                | `AppSettings` from `stream-chat`                                | `AppSettings` from `stream-chat-angular` (`Partial<AppResponseFields>`) |
| `ChannelService.activeChannelMessages` getter                                   | `(StreamMessage \| MessageResponse \| FormatMessageResponse)[]` | `(StreamMessage \| MessageResponse \| LocalMessage)[]`                  |
| `ChannelService.selectMessageToQuote`                                           | `StreamMessage \| MessageResponse \| undefined`                 | also accepts `LocalMessage`                                             |
| `MessageTextContext.message`                                                    | `StreamMessage \| MessageResponseBase \| undefined`             | `StreamMessage \| MessageResponse \| undefined`                         |
| `ChannelSort`                                                                   | from `stream-chat`                                              | removed, use `SortParamRequest[]` from `stream-chat`                    |
| `getChannelDisplayText(channel, user)`                                          | `user: UserResponse`                                            | `user?: { id?: string }`                                                |
| `listUsers(users)`                                                              | `users: UserResponse[]`                                         | `users: { id: string; name?: string }[]`                                |
| `AvatarComponent.user`, `AvatarPlaceholderComponent.user`, `AvatarContext.user` | `User`                                                          | `UserResponse`                                                          |
| `getReadBy(message, channel)`                                                   | `message: FormatMessageResponse`                                | `message: Pick<MessageResponse, 'created_at' \| 'user'>`                |
| `createMessagePreview(...)`                                                     | returns `MessageResponse`                                       | returns `LocalMessage` (`customData` is set as `custom`)                |
| `MessageListComponent.parseDate`                                                | `parseDate(date: Date)`                                         | `parseDate(timestamp: TimestampNS)`                                     |

## Translations

There is a new translation key: `streamChat.Error loading messages`. It's displayed if the messages of a channel or thread can't be loaded. If you provide your own translations (see [Translation](https://getstream.io/chat/docs/sdk/angular/v8/concepts/translation/)), add this key.

## Error handling

When an API request fails, `stream-chat` v10 throws a `StreamAPIError` (exported by `stream-chat`). It replaces the `ErrorFromResponse` class of v9, which no longer exists: update your imports and `instanceof` checks.

| Field      | v9 (`ErrorFromResponse`)                         | v10 (`StreamAPIError`)                           |
| ---------- | ------------------------------------------------ | ------------------------------------------------ |
| `message`  | `StreamChat error code <code>: <server message>` | the server's error message, without the prefix   |
| `code`     | Stream error code (`null` if there is none)      | Stream error code (`undefined` if there is none) |
| `status`   | HTTP status code                                 | HTTP status code                                 |
| `response` | the HTTP response                                | the HTTP response                                |
| `name`     | `ErrorFromResponse`                              | `StreamAPIError(status: <status>, code: <code>)` |

Read the fields from the error object itself. The error's `toJSON()` only keeps `message`, `stack`, `name`, `code` and `status`, so `JSON.stringify(error)` (or `JSON.parse(JSON.stringify(error))`) drops `response`. If your code serializes errors to read `response.data`, switch to reading the error directly.

Errors that don't come from the Stream API (for example network errors, timeouts or HTTP errors without a Stream error body) are thrown as they are, so check the type before reading Stream specific fields:

```ts
import { StreamAPIError } from "stream-chat";

try {
  await channel.markUnread({ message_id: messageId });
} catch (error) {
  if (error instanceof StreamAPIError) {
    // HTTP status, Stream error code, server error message, raw HTTP response
    console.log(error.status, error.code, error.message, error.response?.data);
  } else {
    // network error, timeout, etc.
  }
}
```

The SDK's own error notifications and the `errorStatusCode` of failed messages (the HTTP status code) work with these errors, no change is needed there.

## Behavior changes

These changes don't require code changes, but your app may behave differently:

- **Reconnect:** after a reconnect, the SDK refreshes the channel list (same as before), and `stream-chat` reloads the active channel and the open thread. The open thread now stays open (in v7 it was closed because its replies weren't reloaded).
- **Message list updates:** messages arriving via WebSocket can be displayed up to 500 ms later, because `stream-chat` v10 batches message list updates. Messages sent by the current user are displayed immediately.
- **Message objects:** when a message changes (for example a reaction is added), only that message gets a new object in `activeChannelMessages$`.
- **Hard-deleted user messages:** `user.messages.deleted` events with `hard_delete` remove the messages instead of displaying a "This message was deleted" placeholder.
- **Read events:** the active channel is marked as read with `channel.markReadViaReporter()`. If read events are disabled for the channel, the SDK doesn't send read requests, and marking a message as unread fails with an error notification.
- **Typing events** are only sent if typing events are enabled for the channel (`channel.config.typingEvents.enabled`).

## New APIs

- [`ChannelService.activeChannelMessagesPaginationState$` and `activeThreadMessagesPaginationState$`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/): the pagination state of the message lists (`MessageListPaginationState`: `hasMoreOlder`, `hasMoreNewer`, `isLoading`, `error`).
- [`ChannelService.jumpToFirstUnreadMessage()`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#jumptofirstunreadmessage): jumps to the first unread message of the active channel. It returns `false` if there is no unread message to jump to. If loading the message fails, it displays the "Message not found" notification and throws the error.
- [`ChannelService.reloadActiveChannelMessages()`](https://getstream.io/chat/docs/sdk/angular/v8/services/ChannelService/#reloadactivechannelmessages): retries loading the messages of the active channel, for example after an error.
- `getTranslationLanguage(channel, user)`: the language messages are translated to (the user's `language`, or the channel's `auto_translation_language`). The SDK updates message translations when either of them changes.
- New exported types: `MessageListPaginationState` and `AppSettings`.

## Using the stream-chat client directly

If you use `chatService.chatClient` or `Channel` objects directly, these are the `stream-chat` v10 changes you're most likely to meet. The [stream-chat v10 breaking changes](https://github.com/GetStream/stream-chat-js/blob/release-v10/ai-docs/v10-breaking-changes.md) list all of them.

| stream-chat v9                                                    | stream-chat v10                                                                                   |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `client.activeChannels[cid]`                                      | `client.channelManager.get(cid)`                                                                  |
| `client.queryChannels(filters, sort, options)`                    | `client.queryChannelsAndHydrate({ filter_conditions, sort: [{ field, direction }], ...options })` |
| `client.queryUsers(filters, sort, options)`                       | `client.queryUsers({ payload: { filter_conditions, sort, ...options } })`                         |
| `client.flagMessage(id)`                                          | `client.moderation.flagMessage({ message_id: id })`                                               |
| `client.updateMessage(message)`                                   | `client.updateMessage({ id }, { message })`                                                       |
| `client.deleteMessage(id, hard)`                                  | `client.deleteMessage({ id }, { hard })`                                                          |
| `client.pinMessage(message)` / `unpinMessage(message)`            | `client.pinMessage({ id })` / `unpinMessage({ id })`                                              |
| `client.queryReactions(id, filter, sort, options)`                | `client.queryReactions({ id }, { filter, sort, ...options })`                                     |
| `client.on('connection.changed', ...)`                            | `client.wsConnection.state.subscribeWithSelector(({ isHealthy }) => ({ isHealthy }), ...)`        |
| `client.logger(...)`                                              | `chatLoggerSystem`                                                                                |
| `channel.sendMessage(message)`                                    | `channel.sendMessage({ message })`                                                                |
| `channel.sendReaction(id, reaction)` / `deleteReaction(id, type)` | `channel.sendReaction({ id }, { reaction })` / `deleteReaction({ id, type })`                     |
| `channel.markRead()`                                              | `channel.markReadViaReporter()` (`markRead()` is now the raw request)                             |
| `channel.getConfig()`                                             | `channel.serverConfig` (server config), `channel.config` (resolved config)                        |
| `channel.sendImage` / `sendFile`                                  | `channel.uploadImage({ file })` / `uploadFile({ file })`                                          |
| `channel.queryMembers(filters, sort, options)`                    | `channel.queryMembers({ payload: { filter_conditions, sort, ...options } })`                      |
| `channel.getReplies(parentId, options)`                           | `client.threads.get(parentId)?.messagePaginator`, or `client.getReplies({ parent_id }, options)`  |
| `channel.state.*` message storage                                 | `channel.messagePaginator` (see [Message list and pagination](#message-list-and-pagination))      |
| Date fields as `string` / `Date`                                  | `TimestampNS` (unix nanoseconds), convert with `nsToDate` / `dateToNs`                            |
| Custom fields at the top level                                    | under `custom`                                                                                    |
| Sort as `{ field: direction }`                                    | `[{ field, direction }]`                                                                          |

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/angular/v8/basics/upgrade-to-v8/](https://getstream.io/chat/docs/sdk/angular/v8/basics/upgrade-to-v8/).