如何将变量从父组件中声明的ng-template传递给子组件/指令?

时间:2018-10-11 00:29:30

标签: javascript angularjs angular angular-directive ng-template

所以我想知道是否有一种方法可以传递ng-template并生成其所有内容以包括插值中使用的变量?

另外,我对angular还是很陌生,所以除了删除html元素之外,我还需要担心删除其他内容吗?

在这末尾,将有一个指向stackblitz.com仓库的链接,该仓库将具有下面显示的所有代码。

以下是实现我的指令的src / app / app.component.html代码:

<hello name="{{ name }}"></hello>
<p>
  Start editing to see some magic happen :)
</p>
<!-- popup/popup.directive.ts contains the code i used in button tag -->
<button PopupDir="" body="this is a hardcoded message that is passed to popup box"> simple 
</button>

<ng-template #Complicated="">
  <div style="background-color: red;">
    a little more complicated but simple and still doable
  </div>
</ng-template>
<button PopupDir="" [body]="Complicated">
  complicated
</button>

<ng-template #EvenMoreComplicated="">
  <!-- name and data isn't being passed i need help here--> 
  <div style="background-color: green; min-height: 100px; min-width:100px;">
    {{name}} {{data}}
  </div>
</ng-template>
<button PopupDir="" [body]="EvenMoreComplicated">
  more complicated
</button>

以下是我的src / app / popup / popup.directive.ts

import { Directive, Input, TemplateRef, ViewContainerRef, HostListener } from '@angular/core'

@Directive({
  selector: 'PopupDir, [PopupDir]'
})
export class Popup {
  @Input() body: string | TemplateRef<any>;
  viewContainer: ViewContainerRef;
  popupElement: HTMLElement;

  //i dont know if i need this
  constructor (viewContainer: ViewContainerRef) {
    this.viewContainer = viewContainer;
  }

  //adds onlick rule to parent tag
  @HostListener('click')
  onclick () {
    this.openPopup();
  }

  openPopup() {
    //Pcreate pupup html programatically
    this.popupElement = this.createPopup();

    //insert it in the dom
    const lastChild = document.body.lastElementChild;
    lastChild.insertAdjacentElement('afterend', this.popupElement);
  }

  createPopup(): HTMLElement {
    const popup = document.createElement('div');
    popup.classList.add('popupbox');

    //if you click anywhere on popup it will close/remove itself
    popup.addEventListener('click', (e: Event) => this.removePopup());
    //if statement to determine what type of "body" it is
    if (typeof this.body === 'string')
    {
      popup.innerText = this.body;
    } else if (typeof this.body === 'object')
    {
      const appendElementToPopup = (element: any) => popup.appendChild(element);
      //this is where i get stuck on how to include the context and then display the context/data that is passed by interpolation in ng-template
      this.body.createEmbeddedView(this.viewContainer._view.context).rootNodes.forEach(appendElementToPopup);
    }
    return popup;
  }

  removePopup() {
    this.popupElement.remove();
  }
}

这是显示我的问题的回购链接: https://stackblitz.com/edit/popupproblem

1 个答案:

答案 0 :(得分:2)

首先让我们考虑一下如何将上下文传递给嵌入式视图。您写道:

public class XMLMaker {

    private boolean docTypeDeclared = false;

    public DocumentBuilderFactory docDriverSetup;

    public DocumentBuilder driverSetup;

    public int connectedDevices = 0;

    Document doc;

    TransformerFactory transformerFactory;

    Transformer transformer;

    public void setupDriverXMLFile(List <AppiumDriver<MobileElement>> driverList) {

        try {

            docDriverSetup = DocumentBuilderFactory.newInstance();

            driverSetup = docDriverSetup.newDocumentBuilder();

            doc = driverSetup.newDocument();


            transformerFactory = TransformerFactory.newInstance();
            transformer = transformerFactory.newTransformer();

            if(docTypeDeclared == false) {
                DOMImplementation domImpl = doc.getImplementation();
                DocumentType docType = domImpl.createDocumentType("suite", "","http://testng.org/testng-1.0.dtd");
                doc.appendChild(docType);
                docTypeDeclared = true;
            }

            Element suiteElement = doc.createElement("suite");


            for(AppiumDriver<MobileElement> driver: driverList) {

                Element rootElement = doc.createElement("test");
                suiteElement.appendChild(rootElement);
                rootElement.setAttribute("name", (String) driver.getCapabilities().getCapability("deviceName"));

                Element deviceNameEle = doc.createElement("parameter");
                deviceNameEle.setAttribute("name", "deviceName");
                deviceNameEle.setAttribute("value", (String) driver.getCapabilities().getCapability("deviceName"));
                rootElement.appendChild(deviceNameEle);

                Element platformEle = doc.createElement("parameter");
                platformEle.setAttribute("name", "platform");
                platformEle.setAttribute("value", driver.getPlatformName()+"");
                rootElement.appendChild(platformEle);

                Element udidEle = doc.createElement("parameter");
                udidEle.setAttribute("name", "udid");
                udidEle.setAttribute("value", (String)driver.getCapabilities().getCapability("udid"));
                rootElement.appendChild(udidEle);

                Element urlPort = doc.createElement("parameter");
                urlPort.setAttribute("name", "URL");
                urlPort.setAttribute("value", (String)driver.getCapabilities().getCapability("appiumURL"));
                rootElement.appendChild(urlPort);

                Element devicePort = doc.createElement("parameter");

                if((driver.getPlatformName()+"").
                        toLowerCase().contains("android")) {
                        devicePort.setAttribute("name", "port");
                        devicePort.setAttribute("value", (String)driver.getCapabilities().getCapability("systemPort"));

                }
                if ((driver.getPlatformName()+"").
                        toLowerCase().contains("ios")) {
                        devicePort.setAttribute("name", "port");
                        devicePort.setAttribute("value", (String)driver.getCapabilities().getCapability("wdaLocalPort"));
                }

                rootElement.appendChild(devicePort);

                Element packageName = doc.createElement("package");
                packageName.setAttribute("name", "BaseTest");
                devicePort.appendChild(packageName);

                connectedDevices++;
            }   
            suiteElement.setAttribute("parallel", "tests");
            suiteElement.setAttribute("thread-count", connectedDevices+"");
            doc.appendChild(suiteElement);

        } catch(ParserConfigurationException pce) {
            pce.printStackTrace();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } 

    }


    public void createDriverFile() throws TransformerConfigurationException {
        transformerFactory = TransformerFactory.newInstance();
        transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(new File("./drivers.xml"));

        try {
            transformer.transform(source, result);

            System.out.println("File Updated");
        } catch (TransformerException e) {
            e.printStackTrace();
        }

    }

}

您的this.body.createEmbeddedView(this.viewContainer._view.context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 组件托管在Popup视图中,因此AppComponent将是this.viewContainer._view.context实例。但是我想让你说:

1)嵌入式视图已经可以访问定义AppComponent的模板的范围。

2)如果我们传递上下文,则应仅通过模板引用变量使用它。

ng-template

因此,在这种情况下,您不需要传递上下文,因为它已经存在。

this.body.createEmbeddedView(this.viewContainer._view.context)
             ||
             \/

this.body.createEmbeddedView({
  name = 'Angular';
  data = 'this should be passed too'
})
             ||
             \/

<ng-template #EvenMoreComplicated let-name="name" let-data="data">
    {{name}} {{data}}

为什么UI不更新?

角度变化检测机制依赖于视图树。

this.body.createEmbeddedView({})
             ||
             \/
<ng-template #EvenMoreComplicated>
        {{name}} {{data}}

我们看到有两种视图:组件视图和嵌入式视图。 AppComponent_View / \ ChildComponent_View EmbeddedView | SubChildComponent_View (ng-template)表示嵌入式视图。

当Angular要更新UI时,它只是通过该视图执行两个检查绑定。

现在让我们提醒一下如何通过低级API创建嵌入式视图:

  • TemplateRef

  • TemplateRef.createEmbeddedView

它们之间的主要区别在于,前者仅创建EmbeddedView,而后者创建EmbeddedView,并且将其添加到Angular变化检测树。这样,嵌入式视图便成为变更检测树的一部分,我们可以看到更新的绑定。

是时候查看您的代码了:

ViewContainerRef.createEmbeddedView

很明显,您正在使用第一种方法。这意味着您必须自己进行更改检测:手动调用this.body.createEmbeddedView(this.viewContainer._view.context).rootNodes.forEach(appendElementToPopup); 或附加到树上。

简单的解决方案可能是:

viewRef.detectChanges()

Stackblitz Example

但是它将只检测一次更改。我们可以在每个const view = this.body.createEmbeddedView({}); view.detectChanges(); view.rootNodes.forEach(appendElementToPopup); 钩子上调用detectChanges方法,但是Angular本身有一种更简单的方法。

Popup.ngDoCheck()

我们使用了第二种方法来创建嵌入式视图,以便Angular自身会自动检查模板。

  

我仍然对角度不熟悉,所以除了删除html元素外,我还   需要担心删除其他内容吗?

我认为我们也应该在关闭弹出窗口时破坏嵌入式视图。

const view = this.viewContainer.createEmbeddedView(this.body);
view.rootNodes.forEach(appendElementToPopup);

Final Stackblitz Example