Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning Angular structural directives during runtime

I am trying to assign *ngIf directive from angular code to the template during runtime. Not been able to figure out a way to do it. Is view/templateref an option to do it or is there a different way and an easier one. Is it possible in the first place?

Update:

The code is a little messy and jumbled, so avoided it. But here is the DOM code how it approximately looks and why I need to add inbuilt structural directives dynamically.

<div>
  <input type="text" [value]="userProvidedValue">
  <textarea [value]="someDynamicDOMCodefromWYSIWYG">
    <!-- user provided provided code or dynamic code -->
  </textarea>
</div>
<div>
  <select *ngIf="fetchArraywithHttpFromuserProvidedValue">
    <option *ngFor="let val of fetchArraywithHttpFrom-userProvidedValue" value=""></option>
  </select>
</div>
<div>
  <ng-template>
    <!-- Some User provided code or dynamic code which might need to use *ngIf OR *ngFor -->
    <!-- The *ngIf OR *ngFor will be added dynamically based on a manipulator function which is decided from the value of fetchArraywithHttpFromuserProvidedValue -->
  </ng-template>
</div>

Update

I am doing a fetch request based on userProvidedValue value and the result of the fetch request decides the fetchArraywithHttpFromuserProvidedValue array. Second, based on the value of fetchArraywithHttpFromuserProvidedValue derived from fetch request the decision is made whether to show the user provided template or a predecided set of templates in switch option. (only part of user provided template needs the *ngIf directive. The user template is parsed in JS to get the needed part). The use case is similar to a tool that creates a HTML design/page which fetches structure from a user. This is exactly for a similar tool, just that I am not making a tool that creates a user defined HTML page.

Please help me out with this. If this is not possible, then please recommend an alternative that will allow me to assign functionality similarly or get me a workaround in this situation.

Update 2

Like pointed out in one of the answers below, all of the following templates failed to be added with proper parsing/compilation with elementref or using ViewContainerRef + TemplateRef:

<input [value]="someVariableFromClass"/>

<input value="{{someVariableFromClass}}"/>

<div *ngFor="let item of items">{{item}}</div>

The following works though, if the template is in the DOM before the application is being built and loaded (not dynamic addition):

<ng-template #tpl>
  <!-- Add the template to #vcr using ViewContainerRef from the class -->
    <div *ngFor="let item of items">{{item}}</div>
</ng-template>
<div #vcr>
    <!-- Add the template from #tpl using ViewContainerRef from the class -->
</div>

Currently, I am trying out the compiler API in Angular and checking if compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>): Promise<ModuleWithComponentFactories<T>> can help me in this use case. The issue seems like I will have a create a completely new component by assigning the html as a template to the component, then create a dynamic module, and then compile the whole before inserting into the view (Logic I am trying out currently - not working yet). After this (if I succeed), I will try adding the component template with a directive and see if that compiles right. Any help is welcome. It seems like I might end up by adding static directives to manual placeholders and adding [innerHTML]= / safeHTML / Sanitize API, if I dont succeed. Not ideal though. Please help with alternatives if you can.

I am using this example, though it's plunkr currently not working.

How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

http://plnkr.co/edit/wh4VJG?p=preview

like image 311
Gary Avatar asked Apr 22 '18 07:04

Gary


1 Answers

You don't call a fetch method inside an *ngIf. The ... inside *ngIf="..." gets executed every time angular decides to do change-detection and that might be dozens of times per second. You don't want to deploy a DDOS for your own backend.

This is why you should put a field like isUserProvidedValueValid there and update that field in the subscription of your HttpClient-call:

userProvidedValue: any;
isUserProvidedValueValid: boolean = false;

constructor(private httpClient: HttpClient) {}

doFetch() { // called by a button-click for example
    this.isUserProvidedValueValid = false;
    this.httpClient
      .get<any>(SOME_URL)
      .subscribe(res => {
          if (res) { // you might have a complex check here, not just not-undefined
               this.isUserProvidedValueValid = true;
          }
          // you might consider putting this in the if-clause and in the *ngIf only check for userProvidedValue being not null
          this.userProvidedValue = res;
      });
}

Now for the code that your user provides: first of all, you need to sanitize it. You can do it with a pipe inside a directive (you don't need to use ng-template, you use innerHtml of a normal tag for it) like in this example: https://stackoverflow.com/a/39858064/4125622

  template: `<div [innerHtml]="html | safeHtml"></div>`,

Or before the .subscribe() in the code above, you can do

// domSanitizer needs to be injected in constructor as well
.map(res => this.domSanitizer.bypassSecurityTrustHtml(res)); 

If you need to transform this code, you can add another .map()-RXJS-mapper or another custom pipe - it's up to you which kind of transformer you prefer. In the transformer you can use VanillaJS for manipulation of the user-code. Perhaps an HTML-parser-plugin or a JSON-toHTML-parser-plugin or something similar might be useful to you - depends on the data type your user provides.

like image 67
Phil Avatar answered Oct 24 '22 09:10

Phil