簡體   English   中英

Angular 組件動態編譯 - ng-content 與 select 不工作

[英]Angular Component Dynamic Compile - ng-content with select not working

我有一些使用resolveComponentFactory動態編譯的組件。

我這樣做是這樣的......

const embeddedComponentElements = this.hostElement.querySelectorAll( selector );

const element = embeddedComponentElements[ 0 ];

// convert NodeList into an array, since Angular doesn't like having a NodeList passed
const projectableNodes = [
  Array.prototype.slice.call(element.childNodes),
];

const factory = componentFactoryResolver.resolveComponentFactory( Component );

const embeddedComponent = factory.create(
 this.injector,
 projectableNodes,
 element,
);

但是我有一個我正在努力解決的問題......

這按預期工作

<div>
 <ng-content></ng-content>
</div>

這不能按預期工作:

<div>
 <!-- I get all the content here -->
 <ng-content select="h1"></ng-content>
</div>
<div>
 <!-- I don't get any content here -->
 <ng-content></ng-content>
</div>

似乎ng-content select 屬性不適用於動態編譯的內容。

有什么想法我做錯了什么還是這是一個錯誤?

如果有幫助,我正在使用 Angular 8。

更新:我在這里的 stackBlitz 中重現了這個問題: https://stackblitz.com/edit/angular-ivy-givxx6

我還沒有真正想出與ng-select結合使用的 projectable nodes 參數。

但是,如果您查看我的stackblitz ,您會發現它正在工作.. 有點。

我發現會發生什么:

  1. ng-select被顛倒處理(我認為)。 這意味着它首先處理模板中的最后一個 select 並檢查 2d 投影節點數組中的最后一個數組
  2. 當您指定一個 select 查詢時,它在相應的數組中找不到它,它只顯示數組中的所有項目
  3. 第一個ng-select對應於 2d 數組中的第一個數組。
  4. 如果您有一個位於 select 查詢中的節點,並將其添加到下一個數組中,則不會在第一個 select 中找到它,並且由於某種原因被添加到下一個數組中。 反過來也沒什么問題。

因此,如果您將此作為動態 html:

<hello>
  <span class="title">
    Title
  </span>
  <span class="another-title">
    Another title
  </span>
  The rest here of the content......
  <div>Something else here</div>
</hello>

您應該使您的節點如下

const projectableNodes = [
  [ nodes[1] ], // span class="title"
  [ nodes[3] ], // span class="another-title"
  [ nodes[0], nodes[2], nodes[4], nodes[5]]
];

對應這樣的模板:

<h1 class="hello-title">
  <ng-content select=".title"></ng-content>
</h1>
<h2 class="hello-title">
  <ng-content select=".another-title"></ng-content>
</h2>
<div class="hello-content">
  <ng-content></ng-content>
</div>

為了解釋第 4 點(也許還有第 1 點),如果你這樣做:

const projectableNodes = [
  [ nodes[1], nodes[3] ],
  [ nodes[3] ],
  [ nodes[0], nodes[2], nodes[4], nodes[5]]
];

沒有問題。 如果你反過來做:

const projectableNodes = [
  [ nodes[1] ],
  [ nodes[3], nodes[1] ],
  [ nodes[0], nodes[2], nodes[4], nodes[5]]
];

它永遠不會在正確的 position 上投影節點 [1],但它會以某種方式以“另一個標題”附加到第二個<ng-content> 我找到了這個答案,它有一些贊成,但它並沒有讓我很清楚這個機制是如何工作的,或者你可以通過什么方式來實現這個動態。

我希望這能給您一些見解,也許您可以提出一個動態的解決方案並發布您自己的答案。 或者也許這只是你需要的所有信息,我實際上幫助了你:D誰知道


多看一點源代碼,你可以發現你元素中的ng-content的數量。 這是由factory.ngContentSelectors定義的。 在我的堆棧閃電戰中,這是:

0: ".title"
1: ".another-title"
2: "*"

然后,您可以使用Element.matches()方法對 select 該內容,並將 rest 傳遞給通配符。 然后,您可以使用以下邏輯使其動態化:

const nodes = Array.from(element.childNodes);
const selectors = this.factory.ngContentSelectors;
const projectableNodes = selectors.map(() => []);
const wildcardIdx = selectors.findIndex((selector) => selector === '*');

nodes.forEach((node) => {
  if (node instanceof Element) {
    // get element node that matches a select which is not the wildcard
    const projectedToIdx = selectors.findIndex(
      (selector, i) => i !== wildcardIdx && node.matches(selector)
    );

    if (projectedToIdx !== -1) {
      // add it to the corresponding projectableNodes and return
      projectableNodes[projectedToIdx].push(node);
      return;
    }
  } 

  if (wildcardIdx > -1) {
    // when there is a wildcard and it's not an Element or it cannot be,
    // matched to the selection up, add it to the global ng-content
    projectableNodes[wildcardIdx].push(node);
  }
});

console.log('projectableNodes', projectableNodes);

const embeddedComponent = this.factory.create(
  this.injector,
  projectableNodes,
  element
);

我想這可以優化,或者這里可能有一些錯誤,但我希望你能明白。 使用這樣的邏輯或類似的邏輯,您可以擁有具有動態ng-content的相對動態組件

謝謝波爾。 你的回復對我幫助很大。 我寫了下面的內容,我的單元測試確認現在一切都按預期工作。

const projectableNodes = this.getNgContent( element, factory.ngContentSelectors );


private getNgContent( element: Element, ngSelectors: string[] ) {

    // select the content
    const elements = ngSelectors
      .map ( s => {
        if ( s !== '*' ) {
          const els = element.querySelectorAll( s );
          els.forEach( e => e.remove() );
          return Array.from( els );
        }
        return s;
      });

    // where content has not been selected return remaining
    return elements.map( s => {
      if ( s === '*' ) {
        return Array.from( element.childNodes )
      }
      return s;
    });
  }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM