Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obtaining references to all directives of a certain type up the component tree

Tags:

angular

I have a complex scenario which I need help with.

I have a directive (called TagDirective) that is placed on multiple elements all over my app. I have another directive (QueryDirective) which needs to reference all instances of TagDirective that exist on its host element, as well as on all the elements above it in the hierarchy.

Example:

<div appTag="a">
  <div appTag="b">
    <div appTag="c">
      <div appTag="d">
        <div appQuery>
          <!-- In here I want to have a reference to TagDirectives instances
              d,c,b,a -->
        </div>
      </div>
    </div>
    <div appTag="e">
      <div appTag="f">
        <div appTag="g">
          <div appTag="h">
            <div appQuery>
              <!-- In here I want to have a reference to TagDirectives instances
                  h,g,f,e,b,a -->
            </div>
          </div>
        </div>
        <div appQuery>
          <!-- In here I want to have a reference to TagDirectives instances
              f,e,b,a -->
        </div>
      </div>
    </div>
  </div>
</div>

I know I can obtain a reference to the TagDirective on the host element alone by having the injector provide it in the constructor of QueryDirective, I also know I can get the next higher instance by injecting ViewContainerRef and using its parentInjector member to request an instance of type TagDirective.

However, I haven't found a way to go further up the tree, and collect all the instances all the way up to the root.

How would I achieve that? Thanks!

like image 750
Aviad P. Avatar asked Oct 18 '17 07:10

Aviad P.


1 Answers

Since each of elements has its own injector we can't just use multi: true it will only work if we provide the same token on the same element.

Possible workaround could be as follows:

export const TAG_DIRECTIVES_TOKEN = new InjectionToken('tags directives');

export function tagDirectiveFactory(dir: TagDirective, token: TagDirective[]) {
  return token ? [dir, ...token] : [dir];
}

@Directive({
  selector: '[appTag]',
  providers: [{
    provide: TAG_DIRECTIVES_TOKEN,
    useFactory: tagDirectiveFactory,
    deps: [TagDirective, [ new SkipSelf(), new Optional(), TAG_DIRECTIVES_TOKEN ]]
  }]
})
export class TagDirective  {}

@Directive({ 
  selector: '[appQuery]'
})
export class AppQueryDirective  {
  constructor(@Inject(TAG_DIRECTIVES_TOKEN) private directives: TagDirective[]){
      console.log(directives);
  }
}

Stackblitz Example

In the code above i am providing TAG_DIRECTIVES_TOKEN on each of div[appTag] elements. I use factory with the following dependencies:

deps: [TagDirective, [ new SkipSelf(), new Optional(), TAG_DIRECTIVES_TOKEN ]]
           ^^^^^                                            ^^^^^
current instance of TagDirective        parent optional TAG_DIRECTIVES_TOKEN  

where:

  • SkipSelf tells angular compiler to skip current token and use token that we provided in the parent div[appTag]

  • Optional is used here because root div[appTag] element can't recognize parent TAG_DIRECTIVES_TOKEN so angular compiler won't raise the error:

No provider for InjectionToken tags directives!

like image 63
yurzui Avatar answered Oct 13 '22 20:10

yurzui