Sonhja
Sonhja

Reputation: 8448

How to write on a Sanitized input?

I have a small app in which I receive a question with some hidden words to be written down like this:

 The {0} {1} {2} his {3} off

When this string is received, each {x} string has to be substituted with an input that the user will fill with the correct answer. So for that, I created this code:

HTML part

<div *ngFor="let question of questionsArray">
     ---- some stuff ----
    <div [innerHTML]="createQuestion(question)"></div>
     ---- some stuff ----
</div>

Typescript function:

createQuestion(question: string): SafeHtml {
    let innerHtml = '';
    let words = question.split(' ');

    for (let index = 0; index < words.length; index++) {
        const element = words[index];
        if (element.indexOf('{') >= 0) {
            innerHtml += '<input type="text" name="test"></input>';
        } else {
            innerHtml += element;
        }
    }

    return this.sanitizer.bypassSecurityTrustHtml(innerHtml);
}

I also added the DomSanitizer in the constructor like this:

 constructor(private sanitizer: DomSanitizer) {}

It works fine and draws inputs like this:

enter image description here

But I can't write anything on the input. I guess that maybe the byPassSecurityHtml might not be working because I didn't use any Pipe as suggested here. But, as I need it to be created in a dynamic way as it needs to be called foreach question in my DOM, I can't figure out how to use it correctly...

Can anybody give me a hand with this?

Upvotes: 0

Views: 1360

Answers (3)

Sonhja
Sonhja

Reputation: 8448

Based on @Avin Kavish purpose, I found this simplest solution:

Typescript part

createQuestion(question: QuestionDto): Array<string> {
    let words = question.correctAnswer.split(' ');
    return words;
}

Return a string array with all separated elements. It returns this:

 ["The", "{0}", "{1}", "{2}", "his", "{3}", "off"]

HTML part In the UI part I check the content array in order to decide wether to draw an input or the text.

<div *ngFor="let question of questionsArray">
    <div *ngFor="let word of createQuestion(question); index as i">
       <input *ngIf="word.includes('{'); else elseBlock"
           id="word-{{i}}"
           class="form-control"
           type="text" />
           <ng-template #elseBlock>{{ word }}</ng-template>
    </div> 
 </div>

Upvotes: 0

Avin Kavish
Avin Kavish

Reputation: 8947

The problem with DOM strings is that even though they are rendered by the browser, Angular does not see them as part of the template for view binding. The best approach to this problem is to use an array which defines how the template should be rendered like so:

createQuestion(question: string) {
const template = question.match(/[A-Za-z]+|{\d}/g) // <-- [ 'The', '{0}', '{1}', '{2}', 'his', '{3}', 'off' ]
                  .map(match => match[0] === '{' ? { type: 'input', value: ''}
                  : { type: 'string', value: match })

return template;
}

The createQuestion method accepts a template string and uses a regular expression to split it into parts in the form [ 'The', '{0}', '{1}', '{2}', 'his', '{3}', 'off' ] which I then pass into a map method which generates a uniform object for each part. Any part that has the string '{' is considered to be a placeholder for input so it gets turned into the form { type: 'input', value: '' } any text gets turned into the form { type: 'text', value: 'text value here' } so that we can later loop through this array and use *ngIf to conditionally render either text or an input.

This is the template that's generated for the exemplary string you have provided.

template = [
  { type: 'text', value: 'The' },
  { type: 'input', value: '' }
  { type: 'input', value: '' }
  { type: 'input', value: '' }
  { type: 'text', value: 'his' }
  { type: 'input', value: '' }
  { type: 'text', value: 'off' }
]

With this template you can create a value bound angular template like so,

<div *ngFor="let template of questionsArray.map(q => createQuestion(q))">
     ---- some stuff ----
    <div *ngFor="let section of template">
    <input *ngIf="section.type === 'input'" ([ngModel])="section.value" />
    <span *ngIf="section.type === 'text'">{{ section.value }}</span>
    </div>
     ---- some stuff ----
</div>

The outer *ngFor directive loops through all the different questions that have been transformed into templates with .map(q => createQuestion(q)). The inner *ngFor directive loops through each section of the template and generates either a span or an input based on the type property of each section. If the type is text, a span is displayed. If the type is input and input is displayed with ngModel binding to the value property.

Upvotes: 1

user4676340
user4676340

Reputation:

This is not how you're supposed to use Angular.

In Angular, you don't manipulate the DOM yourself. You're supposed to let the framework handle that for you.

For your issue, I think the best solution would be to use a pipe on your original string, as to not modify it.

Here is some stackblitz so that you can see it in action

Upvotes: 0

Related Questions