14

I'm trying to learn Angular 2, so I was making some hello world examples. Here is my code:

boot.ts

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'
import {DataService} from './app.dataservice'

bootstrap(AppComponent, [DataService]);

index.html

...
<body>
    <hello-world>Loading...</hello-world>
    <hello-world>Loading...</hello-world>
</body>
...

app.component.ts

import {Component} from 'angular2/core';
import {DataService} from './app.dataservice'

@Component({
    selector: 'hello-world',
    template: '<h1>Hello {{ item }}</h1>'
})

export class AppComponent {
    items: Array<number>;
    item: number;

    constructor(dataService: DataService) {
        this.items = dataService.getItems();
        this.item = this.items[0];
    }
}

app.dataservice.ts

export class DataService {
    items: Array<number>;

    constructor() {
        this.items = [1,2,3];
    }

    getItems() {
        return this.items;
    }
}

The code seems to work fine since the first hello-world custom tag is being correctly showed with the code inside the ts. However, the second hello-world tag is not transformed. Only one custom element is shown.

Can't be more than 1 custom tag? How can I do that?

EDIT

I have added the new import inside app.components.ts

import {ByeWorld} from './app.byeworld';

and in app.byeworld.ts

import {Component} from 'angular2/core';

@Component({
    selector: 'bye-world',
    template: '<h1>Bye World</h1>'
})

export class ByeWorld {
    constructor() {
    }
}
Roham Rafii
  • 2,929
  • 7
  • 35
  • 49
Pablo
  • 9,424
  • 17
  • 55
  • 78
  • Yes, but I want to insert the same custom tag, two times – Pablo Jan 15 '16 at 19:27
  • Is this your main component? – Shaohao Jan 15 '16 at 19:28
  • @ShaohaoLin I'm also getting a same issue with `2.0.0-beta.1` version.. looks wiered. it is bootstrapping application once on the page.. other element tag is overlooked.. – Pankaj Parkar Jan 15 '16 at 19:29
  • Is the main component, but I want to insert many times – Pablo Jan 15 '16 at 19:30
  • @PankajParkar Same. I tested on insert two main components, which doesn't work. But for non-main components it works. – Shaohao Jan 15 '16 at 19:30
  • 5
    I'm guessing that `bootstrap()` only bootstraps the first instance it finds. I.e., you can't have two root components in the same app, which makes sense, since Angular builds a tree of components, and the tree can't have two roots. – Mark Rajcok Jan 15 '16 at 19:33
  • Or, where should be the new non-main component imported? – Pablo Jan 15 '16 at 19:46
  • @Pablo import in the main component. – Shaohao Jan 15 '16 at 19:50
  • @MarkRajcok another interesting thing I found is, If I tried to bootstrap 2 component as main component suppose `app1` & `app2`. both are there on html page as ``. then to initialize component when you do `System.import(app1);System.import(app2);` the latest one imported is created as main component. – Pankaj Parkar Jan 15 '16 at 19:56
  • Please check my edit. I have imported the new file from the main component, and created the component. writing the new tag in html, is now displaying correctly – Pablo Jan 15 '16 at 20:00
  • 2
    @Pablo I think we can have multiple `main-component` in our app, but they should have different name..like Angular1 has ability have multiple application(the rule is root component shouldn't be nested in each other) – Pankaj Parkar Jan 15 '16 at 20:11
  • Sure, that's why I created the new `app.byeworld.ts`, which is not being bootstraped or inserted inside the main component. Why it is not being rendered? – Pablo Jan 15 '16 at 20:13
  • @Pablo the second component should be bootstrap inside the `setTimeout`(i don't know why, if they bootstrapped single time they won't work). `setTimeout(function () { bootstrap(ByeWorld)})`, I guess something related to `zone` – Pankaj Parkar Jan 15 '16 at 20:25
  • This [comment](https://github.com/angular/angular/issues/1858#issuecomment-151326461) from @tbosch is related to this. The *root component* is not an angular component. – Eric Martinez Jan 15 '16 at 20:35

3 Answers3

9

I have tested this. You can not make more than one Angular 2 Main Components with the same name. But you can make as many as you want for non-Main components.

How can main and non-main component are differentiated?

Main component is the one that gets bootstrapped.

Have a look of the screen shot. enter image description here

My main component called: Which I made it twice in the HTML:

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

As you can see, there is a loading at the end of the bottom of the picture.

However, it works for non-main components. As you can see my-hero-detail components can be created as many as I can.

<div class="center-align">
<h1>{{title}}</h1>
</div>
<div class="row" style="margin-bottom: 0;">
    <div class="col s12 m6">
        <div id="my-heroes" class="card">
            <div class="card-header">
                <span>My Heroes</span>
            </div>
            <div class="card-content">
                <ul class="heroes">
                    <li *ngFor="#hero of heroes" 
                        (click)="onSelect(hero)"
                        [class.selected]="hero === selectedHero">
                        <span class="badge">{{hero.id}}</span> {{hero.name}}
                    </li>
                </ul>   
            </div>
        </div>      
    </div>
    <my-hero-detail [hero]="selectedHero"></my-hero-detail>
</div>
<div class="row">
    <my-hero-detail [hero]="selectedHero"></my-hero-detail>
    <my-hero-detail [hero]="selectedHero"></my-hero-detail>
</div>

My Hero Detail Component:

import {Component} from 'angular2/core';
import {Hero} from '../hero';
@Component({
    selector: 'my-hero-detail',
    templateUrl: 'app/hero-detail/hero-detail.html',
    inputs: ['hero'],
})

export class HeroDetailComponent {
    public hero: Hero;
}
Community
  • 1
  • 1
Shaohao
  • 3,471
  • 7
  • 26
  • 45
4

Just as standard HTML page should have one <body> tag for content and one <head> tag for 'metadata', an Angular2 application should have one root tag. To make app work you have to initialize it (tell Angular that it is an app) and you do that by calling bootstrap() function.

If it bothers you that your root tag (for example <app>) is inside the body, you can change selector from custom tag app to standard tag body. If you add different component as root, like this:

import {bootstrap} from 'angular2/platform/browser'
import {Component} from 'angular2/core';
import {AppComponent} from './app.component'
import {DataService} from './app.dataservice'

@Component({
  selector: 'body',
  directives: [AppComponent],
  template: `
    <hello-world>Loading...</hello-world>
    <hello-world>Loading...</hello-world>
  `
})
class RootComponent {}

bootstrap(RootComponent, [DataService]);

...the rest of your code should work.

Of course, if in your HTML you need to have other stuff (non-app content, or other angular apps) you wouldn't select body as root selector for your Angular2 app.

Hope this helps you understand things better...

Sasxa
  • 40,334
  • 16
  • 88
  • 102
  • I underestand that, but inside the root tag, can I add new custom tags?, like can I render the custom tag 'tag' twice inside the root element? How can I do that? – Pablo Jan 16 '16 at 11:30
  • Once app is initialized/bootstraped you can use your components inside it how many times you want. Angular will create instances of your components each time it finds a selector you specified (doesn't have to be a tag - could be class, id or any other selector). In your example AppComponent has `selector: 'hello-world'`, so one instance will be created for each `` tag... – Sasxa Jan 16 '16 at 11:52
  • Yes, but check the last part of my question i have edited, I have created a new custom tag, which is not being bootstraped, how ever it is not showed. – Pablo Jan 16 '16 at 11:54
  • You need to add `` tag in parent component template, import it's class and include as dependency in parent component... Add `directives: [ByeWorld]` below `selector: 'hello-world',` in your AppComponent... – Sasxa Jan 16 '16 at 12:00
0

If you come across this question and really do want two root level app instances, this can be accomplished by manually bootstrapping your root level component(s) in the NgModule ngDoBootstrap method.

(Note that in Angular 5+ this method may no longer be required, see this Angular PR)

We first find all root elements we want to bootstrap and give them a unique ID. Then for each instance, hack the component factory selector with the new ID and trigger the bootstrap.

const entryComponents = [
  RootComponent,
];

@NgModule({
  entryComponents,
  imports: [
    BrowserModule,
  ],
  declarations: [
    RootComponent,
  ],
})
export class MyModule {
  constructor(private resolver: ComponentFactoryResolver) {}

  ngDoBootstrap(appRef: ApplicationRef) {
    entryComponents.forEach((component: any) => {
      const factory = this.resolver.resolveComponentFactory(component);
      let selectorName;
      let elements;

      // if selector is a class
      if (factory.selector.startsWith('.')) {
        selectorName = factory.selector.replace(/^\./, '');
        elements = document.getElementsByClassName(selectorName);

      // else assume selector is an element
      } else {
        selectorName = factory.selector;
        elements = document.getElementsByTagName(selectorName);
      }

      // no elements found, early return
      if (elements.length === 0) {
        return;
      }

      // more than one root level componenet found, bootstrap unique instances
      if (elements.length > 1) {
        const originalSelector = factory.selector;

        for (let i = 0; i < elements.length; i += 1) {
          elements[i].id = selectorName + '_' + i;
          (<any>factory).factory.selector = '#' + elements[i].id;
          appRef.bootstrap(factory);
        }

        (<any>factory).factory.selector = originalSelector;

      // only a single root level component found, bootstrap as usual
      } else {
        appRef.bootstrap(factory);
      }
    });
  }
}

Now, assuming our RootComponent's selector was '.angular-micro-app' this will work as expected:

<body>
    <div class="angular-micro-app"></div>
    ...
    <div class="angular-micro-app"></div>
</body>
Josh
  • 403
  • 7
  • 12
  • I have singleton facades in my angular app, so if I load the root component multiple times, do they share the state? I would like them to not share and stay separate. Thanks. – Code-Strings Jan 30 '20 at 18:09