Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the text content of an element be read in Angular without reading from the DOM?

I need to be able to use translated strings in code in my Angular application, but with the i18n tools not yet up to the task, I implemented a slightly hacky version that takes advantage of Angular's existing i18n capabilities, with the intention of phasing out my solution for native Angular as its i18n capabilities catch up to my needs (should be a 5.x release, fingers crossed).

Basically, I have a TranslationDirective that reads text from the DOM, and emits events when the text changes:

@Directive({
  selector: '[myAppTranslation]'
})
export class TranslationDirective implements AfterViewChecked, OnChanges {
  /**
   * dependencies takes an array of the values needed to calculate the output translation
   * we use this for change detection, to minimize the DOM interaction to only when it is necessary
   */
  @Input() dependencies: any[];
  isDirty = true;
  @Input() messageKey: string;
  message: string;
  @Output() messageUpdated = new EventEmitter<TranslationEvent>();

  constructor(public el: ElementRef) {}

  /**
   * sets the translated message and triggers the TranslationEvent
   */
  setMessage() {
    const message = (this.el.nativeElement.textContent || '').trim();
    const oldMessage = (this.message || '');
    if (oldMessage !== message) {
      this.message = message;
      this.isDirty = false;
      this.triggerTranslationEvent();
    }
  }

  ngOnChanges() {
    this.isDirty = true;
  }

  ngAfterViewChecked() {
    if (this.isDirty) {
      this.setMessage();
    }
  }

  /**
   * triggers the messageUpdated EventEmitter with the TranslationEvent
   */
  triggerTranslationEvent() {
    // need to delay a tick so Angular doesn't throw an ExpressionChangedAfterItHasBeenCheckedError
    setTimeout(() => {
      const event = new TranslationEvent(this.messageKey, this.message);
      this.messageUpdated.emit(event);
    });
  }
}

export class TranslationEvent {
  constructor(public messageKey: string, public message: string) {}
}

which gets used like this:

<span
  myAppTranslation
  i18n
  [dependencies]="[today]"
  [messageKey]="todaysDateKey"
  (messageUpdated)="setTodaysDateTranslation($event)"
>
  Today is {{today | date:'short'}}
</span>

Since the strings to be translated all reside in the templates, Angular's xi18n tool reads them just fine, and the Angular compiler will replace them with the translated strings.

This is functional, but its not great. I suspect that there is a timing bug just waiting to bite me that just hasn't manifested yet. There is an inefficient and slow write-to-DOM-read-from-DOM cycle that would be really nice to eliminate.

I'd like to be able to eliminate one source of problems by bypassing the DOM if I can at all avoid it. Does Angular keep an in-memory copy of the contents of an element that is accessible without going through the DOM? If that's possible, can I avoid writing the translation element to the DOM entirely?

like image 587
asgallant Avatar asked Dec 19 '17 21:12

asgallant


People also ask

What is text content in HTML?

The textContent property in HTML is used to set or return the text content of the specified node and all its descendants. This property is very similar to nodeValue property but this property returns the text of all child nodes. Syntax: It is used to set the text of node.

What is textContent in Javascript?

textContents is all text contained by an element and all its children that are for formatting purposes only. innerText returns all text contained by an element and all its child elements. innerHtml returns all text, including html tags, that is contained by an element.


1 Answers

It looks like a lot of your complexity is because you want to support dynamic text - that the text might change at run-time. I don't think you need to do that, because i18n text needs to be static so that:

  1. It can be extracted at compile-time.

  2. It can be translated in advance, and you have all the translations stored at a later compile-time.

Your example "Today is ..." is a perfect example of untranslatable text. :-) You should change it to just have "Today is" be a text chunk by itself, and then have the date displayed with the current locale outside of that span.

like image 142
christian.simms Avatar answered Sep 18 '22 18:09

christian.simms