sanu
sanu

Reputation: 570

Why Angular 2 does not see a dynamically created selector?

This is start-page:

<!DOCTYPE html>
<html>
<head>
    <script>document.write('<base href="/app/" />');</script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>App</title>
</head>
<body>

<my-app>
    Loading...
</my-app>

<!-- 1. Load libraries -->
<!-- IE required polyfills, in this exact order -->
<script src="/cp/node_modules/es6-shim/es6-shim.min.js"></script>
<script src="/cp/node_modules/systemjs/dist/system-polyfills.js"></script>
<script src="/cp/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>

<script src="/cp/node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="/cp/node_modules/systemjs/dist/system.src.js"></script>
<script src="/cp/node_modules/rxjs/bundles/Rx.js"></script>
<script src="/cp/node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="/cp/node_modules/angular2/bundles/router.dev.js"></script>
<script src="/cp/node_modules/angular2/bundles/http.dev.js"></script>

<!-- 2. Configure SystemJS -->
<script>
    System.config({
        packages: {
            app: {
                format: 'register',
                defaultExtension: 'js'
            }
        }
    });
    System.import('app/main')
            .then(null, console.error.bind(console));
</script>
</body>
</html>

This component (startComponent) loads the root page of the application:

@Component({
    selector: 'my-app',
    templateUrl: 'app/mainPage.html',
    directives: [RouterOutlet,RouterLink,ROUTER_DIRECTIVES],
    providers: [
        ROUTER_PROVIDERS,
        HTTP_PROVIDERS
    ]
})

The page mainPage.html contains:

<div id="content">
    <a [routerLink]="['NextPage']">NextPage</a>
    <my-content></my-content>
</div>

When we go to the link NextPage, init this component:

@Component({
    selector: 'my-content',
    templateUrl: 'app/nextPage.html'
})

export class NextPageComponent implements OnInit {
    public str = "";

    constructor() {}

    ngOnInit() {
        this.getStr();
    }

    getStr(){
        this.str = "abc";
    }
}

NextPageComponent should insert a line "abc" to the selector my-content which was created by a component of startComponent. But somehow, he inserts abc line at the end of the selector my-app, not my-content.

That is, in the end we get the following page:

<my-app>
    <div id="content">
        <a [routerLink]="['NextPage']">NextPage</a>
        <my-content></my-content>
    </div>
    <my-content _ngcontent-quv-2=""><div>abc</div></my-content>
</my-app>

But, I want get such a page in result:

<my-app>
    <div id="content">
        <a [routerLink]="['NextPage']">NextPage</a>
        <my-content><div>abc</div></my-content>
    </div>
</my-app>

Why this is happening? What am I doing wrong?

Upvotes: 2

Views: 404

Answers (1)

Eric Martinez
Eric Martinez

Reputation: 31777

Router internally uses DynamicComponentLoader (see Router's source code) to instantiate Components dynamically (the name pretty much says it).

It uses specifically loadNextToLocation and the docs state

loadNextToLocation(...)

Creates an instance of a Component and attaches it to the View Container found at the location specified as ElementRef.

As you see it attaches the Component into the view. It doesn't check the view to see if it matches something inside of it, so whether you have some element that matches the selector it won't work because, again, Router it's not looking for anything on the view, it's attaching the Component in the view, in other words it's pushing it.

Let's try that behavior ourselves. Let's put in some component's view some elements that matches some dynamic component selector

@Component({
  selector : 'dynamic-cmp',
  template : 'Dynamic template'
})
class DynamicCmp {}

@Component({
  selector: 'my-app',
  template : `
    <dynamic-cmp></dynamic-cmp>    <!-- Element matching DynamicCmp selector -->
    <dynamic-cmp></dynamic-cmp>    <!-- Element matching DynamicCmp selector -->
  `
})
export class App {
  constructor(private _dcl: DynamicComponentLoader, private _eRef: ElementRef) {}
  
  ngOnInit() {
    this._dcl.loadNextToLocation(DynamicCmp, this._eRef);
  }
}

Plnkr with the example above

Note : I'm not adding DynamicCmp in directives property

When you run that example and see the DOM you'll see this

<my-app>
    <dynamic-cmp></dynamic-cmp>
    <dynamic-cmp></dynamic-cmp>
</my-app>
<dynamic-cmp _ngcontent-ybo-2="">Dynamic template</dynamic-cmp>

There are two empty dynamic-cmp elements even when they match the dynamic component's selector.

This is exactly what Router does, and that's why you see this behavior. Router doesn't rely on the view (it doesn't look for matching selectors) nor in directives, it loads components dynamically (push them) into the view specified.

I hope this clarifies a little bit what's happening.

Upvotes: 1

Related Questions