The pile
@for paints every bubble type.| File | TS | HTML | SCSS |
|---|---|---|---|
message-container | 4,746 | 3,180 | 1,228 |
new-message-model | 580 | 427 | 371 |
image-audo-message | 526 | 384 | 153 |
automated-message-modal | 398 | 22 | 11 |
edit-participants-modal | 243 | 249 | 212 |
message-price-model | 64 | 67 | 32 |
message-price-model-v2 | 59 | 33 | 14 |
Already out, and still not enough
The seven siblings handle new-message, mailing-list participants, two price modals, automated-message media, and the automated-message form. The container still owns about 95% of the behavior. message-price-model and message-price-model-v2 are the same smell at modal scale. Pick one later. Do not add a third.
No messages.module.ts. The lazy route already points at a standalone MessageContainerComponent with canDeactivate and a route-scoped AudioRecordingService. New files follow that shape.
What I would cut
Visible tree
app-message-container route, selected id
app-inbox-panel search, tabs, list
app-inbox-row one DM or mailing list
app-conversation-header avatar, online, block
app-message-thread history, scroll up
app-message-bubble later, not first
app-message-composer text, emoji, tip, send
app-paid-attachment-strip CDK paid / free lists
app-composer-audio record + waveform
app-automated-messages-tab table + empty state
Children receive data and emit intents. They do not call get-chatGroups-and-users. Share modals stay lazy. They already live at the bottom of the template. Keep opening them with await import(...) on click.
Services, only when a method cluster has one job
File under messages/services/ | Owns |
|---|---|
messages-inbox.service.ts | Cursor, search debounce, badge counts, loadInboxPage |
messages-thread.service.ts | History cursor, merge live + fetched, dmValidations |
messages-send.service.ts | Queue, S3, sendMessageApiCall, hasPendingSend$ |
messages-realtime.facade.ts | One ChatService.receiveMessage subscription |
messages-payment-flow.service.ts | Tip, pay-per-DM, guaranteed reply, make-payment |
messages-wls.service.ts | Owner chat target, allow-DM flag. Last. |
ChatService stays the socket pipe. A MessagesStore PR that copies fields off the class and leaves the methods behind is a second pile.
What stays in the container
- The lazy route target and
userProfileData()boot. - Which conversation is open. Inbox emits. Thread and composer read it.
canDeactivate, readinghasPendingSend$from the send service once that exists. Until then it still readsmesssageQueue.- Header and footer visibility through
HeaderFooterStateService. - White-label layout classes on the shell, until the WLS service exists.
Leave these alone in the first cuts
NgRx is already thin. The container only selects the profile. Do not spread the store into leaf components.
Do not convert the leftover container to signals in the same PR as an extract. New files may use input() and output(). The old class keeps working.
Order of work
loadInboxPage, search, scroll- Container still opens the thread
- Table, empty state, CRUD hooks
- Modal stays where it is
- Avatar, online, mailing actions
- Block and report still open existing dialogs
@Input messageList, load-older output- No bubble split yet
- Connected paid and free lists
- Mobile bridge file input
- Queue, S3, tip popup, emoji
- Audio recorder moves with it
- Delete the extra
receiveMessagesub - Inbox and thread both listen here
- Tip and pay-per-DM next
- White-label last. It branches every pane.
Inbox first because it has no socket and no upload. Automated next because it is already a second tab that the DM pane does not need. Thread as a dumb list, then composer. A bubble component folder is polish after the thread file exists. MESSAGES_ROUTES does not change path.
God methods I would not copy
else if.
- Smell
ngAfterViewCheckedat 2718 callsdetectChanges()on the whole tree, every cycle.- Cause
- The template is too large for Angular to notice what changed, so the class forces a second pass.
- Fix
- Delete it after inbox, thread, and composer own their own change detection. Do not delete it as the first commit. The giant template still needs the lie until the children exist.
- Smell
ChatService.receiveMessageis subscribed inngAfterViewInitand again insidesliceMessageFromMessageQueue.- Cause
- A send-path patch that did not trust the view-init subscription.
- Fix
- One facade. The second subscribe is PR 7, after composer moves, so you can see both call sites without the send method still living in the container.
Coupling that will bite
| Risk | Where | What to do |
|---|---|---|
| Send queue | messsageQueue, canDeactivate at 4254 |
Expose hasPendingSend$ from the send service. The guard stays on the route. |
| CDK drag-drop | Paid and free lists, todoList / doneList |
Keep both lists in one component. Splitting them across templates breaks the connected lists. |
| Infinite scroll | Inbox down, history up, shared CHAT_SCROLL |
Pass the constant in. Do not share one handler. |
| Audio | Route providers: [AudioRecordingService] |
Move the provider onto the composer or the audio child when that file exists. |
| Direct DOM | document.getElementById in setMessageQueue, plus declare var $ |
Composer uses viewChild for #editable. Drop jQuery when that method moves. |
Seven Injector.get calls |
Constructor 453-457, then more | Constructor DI on the service that actually needs the dependency. Stop parking services on public fields. |
| Payment redirect | dashboard/make-payment plus CommonService.subscriptionPaymentData |
Payment-flow service. Composer emits "payment required". |
| White-label | showUsersList, auto-open owner chat, exclusive empty state |
Last. Every earlier PR should keep the current WLS branches compiling, even if they still live on the shell. |
After the first two PRs
src/app/modules/messages/- components/message-container/*.ts 4746+ components/message-container/*.ts ~3900+ components/inbox-panel/*+ components/automated-messages-tab/*+ services/messages-inbox.service.ts components/new-message-model/* unchanged messages.routes.ts still MessageContainerComponent
What "working" means after each cut
- Inbox still pages, searches, and shows Priority / General / Bulk badges.
- Opening a row still fills the thread. That handler stays on the container until the thread PR.
- Automated tab still lists, creates, edits, and deletes. The DM pane does not import
app-table. - Leaving mid-send still blocks navigation.
- White-label owner and fan layouts still compile. Visual check on both.
Done when
- Container TypeScript under 1,500 lines. Template under 400.
- One
receiveMessagesubscription. - No
ngAfterViewCheckeddetectChanges(). canDeactivatestill blocks on a non-empty send queue.- Inbox, thread, composer, and automated tab each have a folder under
messages/components/.
I would not call it done if
- A
MessagesStoreexists and the container still hassendMessageApiCall. - Bubble components landed before the thread parent.
- Signals or zoneless leaked into the leftover container in the same PR as an extract.
- White-label was "cleaned up" in the inbox PR and fan layout broke.
Open
| Question | Default if nobody answers |
|---|---|
Keep ngx-bootstrap tabset for Messages vs Automated, or give automated its own route? |
Keep the tabset on the shell. A second route is a product change. |
AudioRecordingService on the route or on the audio child? |
Move it with the composer in PR 6. |
| Which price modal survives? | The one the composer still opens. Delete the unused file in a later cleanup, not in PR 1. |
Do we rename image-audo-message while we are here? |
No. Typo rename is its own PR so git blame stays readable. |
Folder names above are the ones I would type. If a later extract wants app-inbox instead of app-inbox-panel, that is fine. The order is the decision.