7

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

Gary
  • 2,293
  • 2
  • 25
  • 47
  • 1
    i didn't really got what you mean. Can you explain better ? – ashfaq.p Apr 22 '18 at 08:30
  • @ashfaq.p the code logic is a little intermingled and wanted to avoid details. What I need to understand is can I add structure directives to a DOM template dynamically to a template during some automated event based runtime or from the component class functions manually? – Gary Apr 22 '18 at 10:09

2 Answers2

2

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.

Phil
  • 7,065
  • 8
  • 49
  • 91
  • Ok great. This is nearer to logic but partially. I understand this. but still have issues my use case. Let me explain (updated in question). 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` the decision is made whether to show the user provided template or a predecided set of switch templates. (only part of user provided template needs the *ngIf directive. The user template is parsed in JS to get the needed part). – Gary May 05 '18 at 02:08
  • The array `fetchArraywithHttpFromuserProvidedValue` fetched from the request is not warranted and is a dynamic set of options from a different place. That was the only reason I asked if I can allow a dynamic definition of directives or something like that. A pipe might not be doing good here. Ng uses compiler on the fly using JIT option. Since Ng allows this API for use can I create a viewcontainerref & then compile the template instead of directly assigning the template to DOM. Possible? Else I will have to split templates& assign static placeholders which is not optimal & will be restrictive – Gary May 05 '18 at 02:22
  • I am definitely lacking some understanding of how this is supposed to be achieved. whether using a dynamic directive definition or any other way. May be this information will help. Thanks for responding ;- – Gary May 05 '18 at 02:26
  • @Gary Angular-CLI is not being served as you cans see from it being in the devDependencies and not main dependencies. You do not distribute any compiler to the user, be it JIT or AOT (which in fact is the same compiler, just with different options). If you want to parse dynamically generated user-code, Angular is not the right tool for you. – Phil May 05 '18 at 08:22
  • I am unsure of that. I remember reading it somewhere. Checking though. Is there an alternate way of achieving this without adding a directive like function to the code for the mentioned logic? I am open to exploring that. Right now I really can't think a way out. I am okay with a dirty hack for the moment. – Gary May 05 '18 at 11:06
  • Perhaps you read on using Schematics? https://medium.com/@tomastrajan/%EF%B8%8F-how-to-create-your-first-custom-angular-schematics-with-ease-%EF%B8%8F-bca859f3055d But they are just help for generating typescript classes for the developer, not for runtime code parsing – Phil May 05 '18 at 11:09
  • No no not schematics. I am not touching cli at all. I am definitely not aware of extending cli functionality and its a wrong tool for this use case. I was talking about compiler api. https://angular.io/api/core/Compiler actually checking the source. Never bothered to check the source docs so much before. :-( – Gary May 05 '18 at 11:15
  • The compiler-cli is for things like making your own linter or annotation for aspect-oriented-programming, but I am afraid not for what I think you are trying to do. – Phil May 05 '18 at 11:39
  • Not cli. Its of no use in this use case. Creating an own decorator may be an overdo. I have seen extending components functionality in SO questions but not much on this issue. Can we get on a quick chat? – Gary May 05 '18 at 12:41
  • Ah finally, found it. Let me try this before giving up. https://stackoverflow.com/questions/38888008/how-can-i-use-create-dynamic-template-to-compile-dynamic-component-with-angular https://stackoverflow.com/questions/34784778/equivalent-of-compile-in-angular-2 – Gary May 06 '18 at 05:29
-1

No you can't add structure directives dynamically, you need to approach it by thinking what is your app expecting later on.

For example, if you plan on looping through an array which I'm guessing is what you intend to do having looked at your code, you could do the following:

hotels: any;

<div *ngIf="hotels.length > 0">
  <div *ngFor="let item of hotels">{{item.name}}</div>
</div>
Andrew Howard
  • 2,818
  • 5
  • 33
  • 59
  • Hm. I had thought of that. But he template is dynamically assigned. Also I have been thinking through this and I dont know whether it was in some blog there was a creation of template using view(templateref and viewcontainerref) and one blog that talked about compiler api. I am looking at the angular source and inline docs but trying to figure out if I can assign a template and assign a directive and then manually compile. Just thinking out loud.. Though it might sound stupid. Any way like this possible? I have not seen a lot of doc on this. Its trying to find a needle in haystack.Possible? – Gary May 04 '18 at 00:51
  • No I think you’re approaching this in the wrong way. Every page should expect a certain data object structure that you want to adhere too. You just can’t dynamically assign directives without knowing how the data is structured. Can you show me the JSON you’re working with? – Andrew Howard May 04 '18 at 08:27
  • There is no JSON I am working on. Its going to be a user input. The JSON has nothing to do with adding directives. ``. Can you check the HTML code, I have actually put the way the logic is working. May be you will be able to help me out – Gary May 04 '18 at 09:53