Skip to content

Instantly share code, notes, and snippets.

@lentschi
Last active May 1, 2020 10:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lentschi/8600f9b6ea7e7f5178f40a36f3053de7 to your computer and use it in GitHub Desktop.
Save lentschi/8600f9b6ea7e7f5178f40a36f3053de7 to your computer and use it in GitHub Desktop.
Angular 2+ pipe to replace emoji-mart emojis with an html element displaying their image representation
@pradt
Copy link

pradt commented Jan 15, 2019

Hi, thanks for the gist. How do I use this - I created a stackblitz with this code at https://stackblitz.com/edit/pt-emoji?file=src%2Fapp%2Fapp.component.html but wasn't sure how to call upon the various functions within pipe.

@saschabrockel
Copy link

Hey that does not work for me. I always get the error GET http://localhost:4200/assets/images/emojis/apple_32.png 404 (Not Found)

What am I doing wrong?

@lentschi
Copy link
Author

Hi pradt, hi CptDayDreamer!

The pipe references a hardcoded assets path (/assets/images/emojis/${sheetName}_32.png), which requires you to download the emoji sheets you're going to use and put them there (I just got mine from the inspector when viewing the emojis at the sample page: https://missive.github.io/emoji-mart/ - The ngx-emoji-mart itself package doesn't include any emoji images)

If you don't want to host the images yourself though, you could of course just replace the line span.style.backgroundImage = 'url(/assets/images/emojis/${this.sheet}_32.png)'; with any remote URL - e.g.:

span.style.backgroundImage = `url(https://unpkg.com/emoji-datasource-${this.sheet}@4.0.4/img/apple/sheets-256/64.png)`;

I created a fork from pradt's StackBlitz sample doing just that: https://stackblitz.com/edit/pt-emoji-exjc1b

@lentschi
Copy link
Author

lentschi commented Jul 19, 2019

UPDATE: I just updated the gist (There were some bugs - for example when using unicode emojis in html attributes) and forked a new StackBlitz sample, which offers a sheet switcher to clarify that part: https://stackblitz.com/edit/pt-emoji-yrpfgo

@lentschi
Copy link
Author

lentschi commented Jul 19, 2019

And yet another update ;-) - It does the same thing but internally uses ngx-emoji-mart's method to get the image styles (more future proof).
StackBlitz: https://stackblitz.com/edit/pt-emoji-wcoxb9

@saschabrockel
Copy link

@lentschi I implemented a feature that users can react with emojis on messages (just like discord). If I do the same for this span like for the normal messages some functions are buggy. It's not clickable anymore or at least he does not call the function and the tooltip bugs as well.

In the html it looks like this: [innerHTML]="sheet !== '' ? (emojiReaction.emoji | replaceEmojis:sheet:size:sheetSize:backgroundImageFn) : emojiReaction.emoji | emojify"

I can see that the span is refreshing every second in the console using the pipe.

image

That's what it looks like when it's implemented. And the (click) attribute gets ignored.

Any ideas?

@lentschi
Copy link
Author

Hi @CptDayDreamer
Well, without the rest of your component code I can't tell for sure. But some part of your innerHTML statement must be causing the rerender. If you copied directly from my StackBlitz sample, my best guess is backgroundImageFn.
I made a mistake there by making it a getter, which caused a rerender on every change detection cycle because there was always a new instance of that sample function returned. I changed it to a normal property and now it renders only once:

https://stackblitz.com/edit/replaceemojis?file=src/app/app.component.ts (You can add a console.log at the top of the pipe's transform method to check how often it renders)

@saschabrockel
Copy link

I had implemented it with the get as well. That caused the problems. Thank you for your fast help!

@Arjun-Shinojiya
Copy link

Hi @lentschi
This pipe is a very great solution for displaying emoji in div or tag and its working completely fine in it. My issue is , I want to implement the pipe in input field also so that in the input field user can get exact same emoji preview as he selected. Currently, in the input field it showing default emoji and in p tag its showing selected sheets emoji. Is there any solution for it?

@lentschi
Copy link
Author

@Arjun-Shinojiya I'm not sure I understand. My pipe transforms the text to html containing emojis as images. An "input field" (as you write) cannot have an html value.
I guess what you want is a contenteditable?: https://stackblitz.com/edit/replaceemojis-hf6dje?file=src%2Fapp%2Fapp.component.html (You can click into the output and edit it in this sample - though that has no further effects)

@Arjun-Shinojiya
Copy link

@lentschi Thank you for your response, I resolved the issue with div contenteditable :).

@Arjun-Shinojiya
Copy link

@lentschi Is it possible to use this pipe from the ts file? The current scenario is I select the emoji and add text then the whole inner HTML I am saving into a database so in database whole span tags for each emojis are going to save and again when its render , there I am adding pipe. So in some cases when there is already one pipe in div , I am not able to use our pipe at the same time. So I am thinking to send emoji only without span to a database . For that just like pipe does in the Html file ,I need to implement pipe in component file.
I tried to call pipe in ts file and call transform function but it's not working , please let me know if you know how to do this. Thank you.

@lentschi
Copy link
Author

Is it possible to use this pipe from the ts file?

@Arjun-Shinojiya You mean from the component? Yes, that is possible - just add the Pipe to your module's providers array and then inject it - as you would inject any service.

I still don't know, what it is exactly, that you're trying to accomplish - It sounds like you're building some kind of chat where you can view messages containing unicode emojis (using the pipe), edit them (using a contenteditable with the preparsed html content containing spans instead of unicode emojis), and then save them back to the server (transforming the spans back to unicode emojis) - Is that it?

If so, here's a small sample for that:
https://stackblitz.com/edit/replaceemojis-editor?file=src%2Fapp%2Fapp.component.html

Just to be clear about this: If you don't mind saving spans with hardcoded emoji background image styles on your server, then you don't need my pipe at all. (The downside of saving them with hardcoded styles is, that it will be hard to change styles/image paths etc. afterwards - That would require a html migration on the server side.)

@lentschi
Copy link
Author

@Arjun-Shinojiya I forgot to mention, that I had to slightly modify the pipe itself to make that sample work - I had to add the unicode emoji as a text content to the span (see line 120):

span.textContent = unicodeEmoji;
// ...
Object.assign(span.style, styles, {
                      overflow: 'hidden',
                      textIndent: '-10000px'
});

I don't want to change that in the gist as well, as sometimes people just don't need that.

@Arjun-Shinojiya
Copy link

Is it possible to use this pipe from the ts file?

@Arjun-Shinojiya You mean from the component? Yes, that is possible - just add the Pipe to your module's providers array and then inject it - as you would inject any service.

I still don't know, what it is exactly, that you're trying to accomplish - It sounds like you're building some kind of chat where you can view messages containing unicode emojis (using the pipe), edit them (using a contenteditable with the preparsed html content containing spans instead of unicode emojis), and then save them back to the server (transforming the spans back to unicode emojis) - Is that it?

If so, here's a small sample for that:
https://stackblitz.com/edit/replaceemojis-editor?file=src%2Fapp%2Fapp.component.html

Just to be clear about this: If you don't mind saving spans with hardcoded emoji background image styles on your server, then you don't need my pipe at all. (The downside of saving them with hardcoded styles is, that it will be hard to change styles/image paths etc. afterward - That would require a html migration on the server-side.)

@lentchi Thank you for your reply. I will try this solution. Let me explain to you what problem I am currently facing. I have one feed page ,please check below image.
Screenshot from 2019-12-24 10-54-29
So In feed I added our emoji span with text coming from API also with that I have one shorten pipe for Read more and Read less in the feed. Our pipe works with inner HTML in div of p tag. And ngx shorten pipe works between p tag. These both pipe is not working together currently. If It works together still shorten pipe calculates span tags also with characters and that's why read more and read less will come incorrect. Below is my HTML code.
<p class="feeddescription" [innerHTML]="feed.description | replaceEmojis:sheet:size:sheetSize:backgroundImageFn : feed.description"> {{feed.description | shorten: feed.maxLength: '...'}} <a href="javascript:void(0);" *ngIf="feed.description.length > feed.maxLength" (click)="feed.maxLength = feed.description.length">Read more</a> <a href="javascript:void(0);" *ngIf="feed.description.length == feed.maxLength" (click)="feed.maxLength = maxLength">Read less</a> </p>
I wrote like this both pipes but shorten pipe is not working. Thats why I am finding solution to save emoji and text only in database instead of span tag

@lentschi
Copy link
Author

lentschi commented Dec 24, 2019

@Arjun-Shinojiya Like this?: https://stackblitz.com/edit/replaceemojis-runes-truncate?file=src%2Fapp%2Fapp.component.html

If that's what you need, some explanations:

  1. A simple text.substr won't do when trying to truncate emojis UTF-8 characters. In this sample I used runes - see https://stackoverflow.com/questions/52526719/javascript-substring-without-splitting-emoji
  2. This sample doesn't allow for any HTML content in the text you're trying to truncate (It will be escaped as truncating HTML isn't so easy).
    Note: If you do have HTML content, I suggest converting it to text before. (Having any formatting combined with an ellipsis could be considered a design flaw anyway.)

@Arjun-Shinojiya
Copy link

Is it possible to use this pipe from the ts file?

@Arjun-Shinojiya You mean from the component? Yes, that is possible - just add the Pipe to your module's providers array and then inject it - as you would inject any service.

I still don't know, what it is exactly, that you're trying to accomplish - It sounds like you're building some kind of chat where you can view messages containing unicode emojis (using the pipe), edit them (using a contenteditable with the preparsed html content containing spans instead of unicode emojis), and then save them back to the server (transforming the spans back to unicode emojis) - Is that it?

If so, here's a small sample for that:
https://stackblitz.com/edit/replaceemojis-editor?file=src%2Fapp%2Fapp.component.html

Just to be clear about this: If you don't mind saving spans with hardcoded emoji background image styles on your server, then you don't need my pipe at all. (The downside of saving them with hardcoded styles is, that it will be hard to change styles/image paths etc. afterwards - That would require a html migration on the server side.)

@lentschi I think your this example will work for me but it has one issue.
After adding emoji in div ,it wont displaying text after it . I am able to add the text after emoji but it wont get show.

@lentschi
Copy link
Author

@Arjun-Shinojiya Sorry, I'm not sure what you mean to say. In my sample there is text after the last emoji and it is displayed. Could you maybe create a failing StackBlitz sample? (Then I might be able to help.)

@Arjun-Shinojiya
Copy link

@lentschi In your this example https://stackblitz.com/edit/replaceemojis-editor?file=src%2Fapp%2Fapp.component.html . I changed the line number 40 code to
this.editorHtml = this.sanitizer.bypassSecurityTrustHtml(span.outerHTML + this.contenteditable.nativeElement.innerHTML); .I done this because I want to insert emoji after last character of the sentence . Its working fine but now the issue is when I insert the emoji and after that if I type any word or character then It won't get display but after clicking on save button, the entered text gets displayed. Hope you understand the issue.

@lentschi
Copy link
Author

lentschi commented Jan 24, 2020

@Arjun-Shinojiya Ah, that was because you also need to set span.setAttribute('contenteditable', 'false'); for the newly inserted emoji. (Else you type text into the emoji span, which cannot be seen)
I updated the StackBlitz sample and also added a way of inserting the emoji at the current cursor position (if the editor is focused otherwise it will appear at the end).

This is still far from being a perfect editor (It's just a sample on how to use the pipe!). For example the focus on the contenteditable will be lost after inserting an emoji. I think to prevent that you will have to remove the input [innerHTML]="editorHtml" and handle setting the html in the component code. (Otherwise the content will be replaced and the cursor position is lost)
Also note, there's some issue with deleting emojis in current Firefox versions with the backspace key (interestingly the delete key works). I googled a bit and there seems to be a workaround, but I haven't implemented it (out of the scope of this sample too.)

@asdrubalivan
Copy link

asdrubalivan commented Apr 2, 2020

Hello, I'm getting the following

Argument of type '(set: string, sheetSize: number) => string' is not assignable to parameter of type 'number'

Any help? I'm using version 3.0.3 of emoji mart. They added a new param called sheetRows which is a number in here

                    const styles = this.emojiService.emojiSpriteStyles(
                        matchingData.sheet,
                        set,
                        size,
                        sheetSize,
                        backgroundImageFn
                    );

Just between sheetSize and backgroundImageFn

@lentschi
Copy link
Author

lentschi commented Apr 5, 2020

@asdrubalivan I haven't had time to try this out yet, but the new sheetRows parameter is optional - could you not just replace it with undefined and it would still work?

@lentschi
Copy link
Author

lentschi commented Apr 13, 2020

@asdrubalivan I fixed it, but not in the Gist: Due to the ongoing popularity of this Gist, I decided to turn it into a library after all:
npmjs.com: https://www.npmjs.com/package/ng-utf8-emojis-to-images
GitHub: https://github.com/lentschi/ng-utf8-emojis-to-images

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment