angular: Can't retrieve attributes of the top-level component

http://plnkr.co/edit/5731Z9N6tQ391gQtfZrA?p=preview

I’ve been over the documentation 10 times, I’ve read through the source code, tutorials, the works… and I still can’t wrap my head around what I’m doing wrong.

all I want to do is:

<app myTitle='my awesome title'>  

and access myTitle from inside the constructor.

Is that possible in NG2?

About this issue

  • Original URL
  • State: open
  • Created 9 years ago
  • Reactions: 37
  • Comments: 85 (24 by maintainers)

Commits related to this issue

Most upvoted comments

you can use the ElementRef:

constructor(public elementRef: ElementRef) {
    var native = this.elementRef.nativeElement;
    var myattr = native.getAttribute("myattr");
}

I would like to take some time to comment on the tone of some of the messages. I am sorry that Angular does not do everything you want yet. The issue is still open because we may be able to do it in the future. I get that one is disappointed when the framework does not do X or Y yet and we will be improving as soon as we can. Please form your comments in a constructive way which help the situation. Making negative statements, even if hinting create the same feelings in the reader and that affects the person’s mood. This makes the reader think about the feelings rather than focusing on solving the problem.

Please be courteous to each other. We all our doing our best.

I’m disappointed that Angular2 is restricting itself so blatantly on how it ought to be used. It seems like the whole project is targeting a single use case, which is SPAs and by extension mobile apps, when it could be so much more. Did you know that form submissions are hardcode disabled? I know, I couldn’t believe it either. For such a massive 1.5 MB framework bordering on bloatware, it has so many constraints.

I’ve been using Angular1 since beta, and despite having to deal with this flavor du jour TypeScript nonsense without any documentation for javascript, I’ve picked up Angular2 in plain javascript myself. But with these design decisions I can no longer go through the mental gymnastics to convince myself that Angular2 is somehow better than React (143 kb) or smaller frameworks like Mithril (19 kb) in any way.

It’s also apparent to me now that Angular2 is trying to influence mainstream usage by targeting big players like weather.com, while shunning grassroot developers like me.

There is a nice new GitHub feature to "add reaction"s instead of +1 posts. Please use this instead 👍

+1 for root component inputs. It is extremely important in server-side generated pages that wants to pass data into the Angular2 app. Here’s an example: the server has a list of objects, and generates an HTML block for each object on the same page. I want to convert these blocks into ng.core.Components, but I need a way to pass the object data into each component (as a parameter). I’d like a way to write <my-component some-data="server-injected-data"></my-component>

I want to do all that without having to create a global parent Component, and pass my data via JSON.

a few ideas:

  • you can certainly bootstrap multiple root components on a page, this is an absolutely a supported use case, and possible with a few lines of code. what’s not possible (and likely won’t be) the mixed case where you bootstrap angular “around” existing HTML. Multiple root apps share the same injector and change detection so they place together nicely. See http://plnkr.co/edit/jiZsZLiybmATsC4CGtUL?p=preview for an example of how to do that
  • server side rendering is a huge part of angular2, today with angular/universal and soon with any number of other languages (drupal, .net, etc). that said, our vision is more that you’d write an angular application (that is, not separate client and server application) and bootstrap it server side, and then boot the SPA to take over.
  • to the initial question, if you really want to grab data from the root, that’s possible via https://plnkr.co/edit/nOQuXE8hMkhakDNCNR9u?p=preview - note that it’s not an input (because there’s no angular context outside of it to do the input…) - its a simple string attribute which you’d need to parse yourself.
  • ideally though, you’d do as @robtown suggested and actually pass the data in javascript, rather than passing it as a DOM string and retrieving / parsing it yourself (which has never really been a supported case in angular, despite the explicit-warned-against usage of ng-init in angular1 to accomplish this) - see https://plnkr.co/edit/PoSd07IBvYm1EzeA2yJR?p=preview for a simple, testable example of how to do this.

I’m shocked that this is still an issue. Years ago when I submitted this, this issue caused our entire business to ditch angular 2 and use react for everything. Which has worked fantastic for years, but now we have a project that requires NG2 and here I am literally two years later faced with the same problem.

That being said, I came up with a hokey work around. I have no idea how bad this is as far as hokey factor and I’m sure it’s not very performant, but i wanted to share in case it helps someone.

(this assumes you give your root component the id of “approot” in your index.html file)

app.component.ts:

  ngOnInit() {
     let attrs = document.getElementById("approot").attributes;
     Object.keys(attrs).forEach(key => {
     this[attrs[key].name] = attrs[key].value;
  });

I know it could be optimized, but this is a nice easy copy and paste thing for us to throw into app.component.ts and then not have to worry about changing anything if we later decide we need to add or modify root level attributes.

Hope this helps!

I think what is needed is a big fat bold message on the front page saying “Angular 2 is a framework designed only for SPA and Mobile application development”

I wholly disagree with that philosophy but if that’s what you’re making, you should be up front about it.

+1 for root component projection

In some projects we are using angular in conjunction with static html to provide some dynamic features via simple directives. Like moment.js for some date manipulations, Hyphenator.js, some special markup to include sliders and contact form management etc…

To migrate this successfully to angular2 ng-content for root components is definitely a must have.

+1

+1

It is extremely important in server-side generated pages

@realdope bingo. My initial use case was using angular 2 in drupal and wordpress cms themes where I didn’t want a full scale single page application, but wanted to replace sections of the themes with angular components.

I actually ended up switching to react.js which handles root component projection very easily. I think in the end server side generated pages just arn’t a very good use case for angular. Angular seems to be designed for SPA use only. If you’re looking to do what I did which is basically make several reusable components on a server generated page, react seems a much better fit.

I think the angular devs are missing a big opportunity by not making server side use cases a priority, but in the end that’s not my call.

@realdope - you can change selector: ‘form:not([ngNoForm]):not([ngFormModel]),ngForm,[ngForm]’, to selector: ‘ngForm,[ngForm]’ Form will be working as they were designed and not like some people (angular team) think they should be designed.

@mhevery and @vicb It is A BASE MISTAKE to have problems with easy migration of websites to angular2. Also when only transclusion of root element is needed to get everything work easier. Shame on you. Angular 2 is a piece of shit in compare to Angular 1.x. It looks like modern thing with all of those polyfills, typescripts etc. but you missed the basic functionality. You forget that not only single-page sites exist. I know that angular 1.x was a slow engine, but it was better than unusable library 😛

As this problem was reported in the summer last year and there is still no solution to do this. Only a long discussion why not to do it, I am throwing angular2 off my interests. It’s a library not usable in 90% of the websites. Do not want to ofense you, just saying that I (I am sure that I am not alone) will not recommend angular2 to anybody. If you based your framework on AJAX calls what about to integrate some CSRF tokens service to it as default. This is to make web more safety and not your restrictions…

The reason why this is not working is that your index.html in which you place the <app myTitle='my awesome title'> is not an angular component. Because of this, Angular won’t compile this element. And Angular does not read attribute values during runtime, only during compile time, as otherwise we would get a performance hit.

I.e. this works as intended, please use the code snippet that @Mewel described…

Maybe this is offtopic but… I came here because i wanted to use Angular 2 with Laravel. Here is how i did it.

I have my server side language (php) and i want some pages to use Angular. Also I want to have one js file minified and everything that angular cli does. The routing is done only on the server. To achieve that I have my own conventions. The pages that will use Angular 2 will have a specific path. Lets say [host]/pages/appModule, [host]/pages/testModule etc.

Then I have a functions that checks the url and find the module that I want. Lets name it src/app/findModule.ts

import { AppModule } from './';
import { TestModule } from './test.module';

export default () => {
  let module;
  let windowLocation = window.location.pathname;
  let locationArray = windowLocation.split('/');

  if(locationArray[1]=='pages') {

    if(locationArray[2]){
      switch (locationArray[2]) {

        case 'TestModule':
          module = TestModule;
          break;
        default:
          module = AppModule;
          break;
      }

    }
  } else {
    module = AppModule;
  }
  return module;
}

(Yes this function can be better and more dynamic. lets keep it for now)

Then I import it in main.ts

import findModule from './app/findModule'

and use it to bootstrap my application like this

platformBrowserDynamic().bootstrapModule(findModule());

So every server page that uses Angular has its own root ngModule and its own independent component tree.

Every time that i have a new page i create an new root ngModule and a new component and just update the switch in findModule.ts

Now say that we have the src/test.module.ts

@NgModule({
  declarations: [
    TestComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [TestComponent]
})
export class TestModule { }

and the src/test/test.component.ts

import { Component, OnInit, Input, ElementRef } from '@angular/core';

@Component({
  selector: 'app-test',
  template: `--- {{test | json}}  ---`,
  styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
  test: string;
  constructor(private el: ElementRef) {
    var native = this.el.nativeElement;
    this.test = JSON.parse(native.getAttribute("test"));
  }

  ngOnInit() {
    console.log(this.test);
  }

}

Now in html we can use something like this

<app-test test='[{"name": "blackpr"}]'></app-test>

This is part of a fundamental shift in how Angular 2 works - you can’t really use it to “augment” content in the same way that you could with Angular 1 (by adding something to the page and the directives pick it up) because the compiler isn’t going to be there. It’s stricter and so it becomes a limitation and you need to use a different approach.

What’s particularly confusing is the issue that not all components behave in the same way - a top level component isn’t really running “within” Angular 2 like all the others are, it is the top of the tree so to speak so there is nothing that can set it’s inputs in the same way. That’s probably why it would need extra code to have the same behavior - so more complex code or confused users? Routed components also have their own special behavior but for different reasons which all adds to confusion around how components behave.

Components only work as components when referenced in the template of another component, yet they are expected to be used outside of this which causes confusion

It would probably save a lot of time and frustration if these limitations were documented and highlighted. Make them part of the examples and “why you might think this approach will still work but why it won’t anymore”. “Yes, we make you call this a component but here’s why we don’t really treat it like one in these circumstances”.

+1 for root component Inputs - Very useful for non-SPA apps which typically require multiple root components

FYI, the index.html that hosts your top-level component (typically AppComponent) is not a component template! All of that HTML is invisible to your Angular 2 application.

That means the Angular 2 techniques you know for extracting template information (including attribute assignment) do not work in that context.

You have to switch to old-school DOM querying techniques to extract information that is inside the index.html but outside the scope of your Angular application component.

This is different from Angular 1. Fortunately, once you know about this difference, you should be able to get what you need.

We intend to document this in the not distant future.

I guess it’s not a huge deal as long as you are aware of the limitation. I guess one application of this would be to initialize the root level component with some default data. Currently you would have to define a global variable and just refer to it once you enter the Angular world.

A specific sample would be if you are using server side code in index.html to dynamically create some JavaScript before you kick off your Angular root component.

Something like this (assuming you are using a nodeJS server side template to render dynamic JS in index.html).

<script>
var articleId = '{{article.id}}'; //server side rendering like mustache or similar (Not client side).
</script>

<my-root [myId]="articleId"></my-root>

Not sure this is necessary to implement, but I think this is at least a valid use case.

This issue will have much simpler implementation with Ivy.

bootstrap angular “around” existing HTML is a major use case…

Eg: my immediate use case is adding some angular2 dialog boxes into a middleman4 static content documentation site.

Looks like the upgrade adapter can do this, I hope I won’t be burned going down this route.

It seems to me that we should split this issue into two issues:

  1. Document techniques for passing information from the host web page to the Angular application

  2. Support passing arbitrary HTML content into one-or-more A2 components where such content is treated as a dynamic template, interpreted by A2 within the context of the parent component. Booting multiple components is relatively easy (even arbitrary components discovered at runtime). Doing something Angular-ish with the HTML inside the component element tags is not easy.

Solutions

(1) is straightforward and I’ve opened angular.io issue #1871 to address it.

(2) is much harder. It’s hard to describe (as can be seen in my feeble attempt). It’s harder still to solve because Angular 2 does not have much love for on-the-fly component construction. It really is intended for SPAs, not for manipulating arbitrary HTML as one might do in a CMS or a web site. Angular 1 is good at that. Angular 2 clearly is not.

I do not have an answer for (2).

Hi guys, this is my actual workaround, I hope this can help you all:

platformBrowserDynamic().bootstrapModule(MyApplicationModule).then((module: NgModuleRef<any>) => {
    let rootComponent = module.injector.get(ApplicationRef).components[0];
    let rootElement: ElementRef = rootComponent.injector.get(ElementRef);
    
    if (rootComponent && rootElement) {
        for (let index = 0; index < rootElement.nativeElement.attributes.length; index++) {
            let attr: Attribute = rootElement.nativeElement.attributes[index];
            if (attr.name != 'ng-version' && attr.value) {
                let changes = {};
                if (rootComponent.instance.ngOnChanges)
                    changes[attr.name] = new SimpleChange(rootComponent.instance[attr.name], attr.value, true);
                
                rootComponent.instance[attr.name] = attr.value;
                
                if (rootComponent.instance.ngOnChanges)
                    rootComponent.instance.ngOnChanges(changes);
            }
        }
        rootComponent.changeDetectorRef.detectChanges();
    }
});

Simply get the root component after module bootstrap and then setting component properties using actual html element attributes. This solution requires that the component class implements the OnChanges interface. When a new Angular version will give us a clean solution to this, we’ll simply remove this lines from our code. Easy, right?

@michaill - first and only warning, please conform to the Code of Conduct when participating on GitHub issue, otherwise we’ll simply remove your ability to participate.

@mhevery Thank you for your response. I have a great deal of respect for your team, and I suspect the community at large does as well. Despite the downbeat tone I hope the comments were at least constructive and not personally directed at the team or any of its members.

I understand (1) I’m not entitled to a solution that works perfectly for me, (2) it is still a project in its beta stage, and has room to grow. However the frustration comes from a dissatisfaction stemming from a perceived sense of slight, that in its beta stage we have certain design decisions that to the untrained eyes such as my own seem strange.

Here are a few examples that I can think of off the top of my head right now: (1) form elements cannot submit by default without adding ngNoForm (2) the …-all.umd.min.js files hosted on all cdns are broken for every beta version except beta.0. (3) the inconsistencies with the template syntax, such as [(input_value)]=“my_value”, which only works with input boxes but cannot be extended to custom sub-components without doing some low-level custom codes (4) the issue that this thread represents.

At heart of the discussion is a sense of frustration that I had to express having spent a few weeks playing with the latest version of angular 2.0.0-beta. When you have a community that at least cares to spend their Sunday hours to voice frustration, I hope you can see how much we care about the project.

I had some reservations and quite a few swears when I first ran into this issue… I turns out the workaround was fairly stupid simple.

I simply wrote a “Service” and passed it into my main app. And by “Service” I mean a simple, global, Javascript function (in my razor view) that returns a dynamic variable from my .NET view page.

+1 for root component projection

+1 for root level component projection.

In your example the myTitle property would only be available after the change detection has first runned, ie in the onChange event (you have to register to the onchange lifecycle hook), see the md button for an example.

If you need the value in the ctor, you would have to use @Attribute, the md checkbox for an example.

I am faced this problem too. In my case it is not a top-level component, but @Input property was always undefined. I’ve tried component-interaction demo and it worked. I’ve decreased angular version from 6.0.3 to 6.0.0 hoping this is a version bug and without success. @Input started to work when I changed parent componet template that references child component from <app-albums-view [cat]="recent"></app-albums-view> to <app-albums-view cat="recent"></app-albums-view>

I suppose there is some bug that need to be fixed. I’ve spent about a day trying to make it work and still don’t undestand why first version doesn’t work. I have Windows 10 Home version 1803. npm 5.6.0 Hoping this info will help to fix this bug. Thanks.

@maxime-menard
GitHub issues are for bug reports and feature requests. For support questions please use other channels like the ones listed in CONTRIBUTING - Got a Question or Problem?

@jimmymain I guess i exploded a bit sorry for that im really frustrated now. to only use partials will be to mush of a implamentation as im using a cms already il just have to hack my way and hope that my head wont explode its not much left until it falls appart.

I have managed to use this

import { Component } from "@angular/core";

/**
 * This is a hack taken from http://stackoverflow.com/questions/34993936/angular-2-it-is-possible-to-bind-the-app-component-in-existing-dom-without-era
 * @param tagName
 */
function getExistingContentByTag(tagName) {
    var target = document.getElementsByTagName(tagName)[0];

    if (!target || !target.tagName) return "";

    return target.innerHTML;
}

@Component({
    selector: "body", // this will ensure that angular controlls the whole page with only one root component.
    template: getExistingContentByTag("body") // Currently we have no way of using existing html as per to https://github.com/angular/angular/issues/1858 so we use this hack.
})
export class AppComponent { }

and it seams to work but i now get another error that i just cant figure out i dont know if its related or not but it follows as: metadata_resolver.js:623Uncaught Error: Can't resolve all parameters for HighlightDirective: (?, ?).(…)CompileMetadataResolver._getDependenciesMetadata @ metadata_resolver.js:623CompileMetadataResolver._getTypeMetadata @ metadata_resolver.js:517CompileMetadataResolver.getNonNormalizedDirectiveMetadata @ metadata_resolver.js:255CompileMetadataResolver._loadDirectiveMetadata @ metadata_resolver.js:134(anonymous function) @ metadata_resolver.js:400(anonymous function) @ metadata_resolver.js:315CompileMetadataResolver.loadNgModuleMetadata @ metadata_resolver.js:315RuntimeCompiler._loadModules @ runtime_compiler.js:99RuntimeCompiler._compileModuleAndComponents @ runtime_compiler.js:69RuntimeCompiler.compileModuleAsync @ runtime_compiler.js:59PlatformRef_._bootstrapModuleWithZone @ application_ref.js:302PlatformRef_.bootstrapModule @ application_ref.js:284(anonymous function) @ Wella.ts:5__webpack_require__ @ bootstrap 6ca6f18…:19(anonymous function) @ zone.js:1426__webpack_require__ @ bootstrap 6ca6f18…:19(anonymous function) @ bootstrap 6ca6f18…:63(anonymous function) @ bootstrap 6ca6f18…:63

i followed the angular guide here “https://angular.io/docs/ts/latest/guide/attribute-directives.html#!#write-directive” xD

@robgha01 had no wish to be offensive. I also build angular 1 applications using c# ASP.NET and MVC. I do know what you are referring to. I am simply trying to point out that angular 2 is not built this way. Again terribly sorry if you found my response offensive. I wish I could be of more assistance, but I don’t think you are going to find that angular 2 will suit your particular (albeit common) style. My suspicion is that you will need to use angular 2 differently. I am happy to be corrected or proven wrong, but my suspicion is that you will need to embrace the angular 2 approach - and leverage your razor views as partial views, obtained through angular controller calls and angular directives. ng-content is not supported (as yet) by root components. I am sorry I can’t help further. The reason I responded at all is that I have felt the same frustration attempting to migrate to angular2.

As @coli said, the only way he has managed to achieve this at all is using the angular migration components, which doesn’t give anyone encouragement that this style is around to stay. He’s worried about getting burned going down this route - and I would concur.

@jimmymain Not really helpfull are ya…

Im writing a website application but i need to embed serverside template and that means root component need to support and allow this becuse we can not tell razor to output things after becuse razor is a server language and not used at client side meaning if you have no knowlage of serverside wich i see when you write that nonesence you clearly dont know what im refering to or how a website application work in a real world not in a local or html only site we are not in the 70th this is 2016!

Stick with legecy systems is not recomended… if everyone follows that rule i would have windows 95 installed on this computer…

and if angular2 will not support the users then its garbage. if you have no common sense in what a user write dont answer it… im offended by what you wrote.

@robgha01 ng-content is not supported on a root component, so in short you cannot do it. angular 2 is not designed in the same way as angular1. My experience suggests that if you are not writing a single page app, stick with angular1 or choose a different framework.

+1 for root component projection

Basically this says it all:

“I think the angular devs are missing a big opportunity by not making server side use cases a priority, but in the end that’s not my call.”

To be clear: this only does not work when using a component as the root of your application, I.e. Your application is the component. If you use this component inside of an Angular app, this works fine.

Not reading this attribute is consistent with not interpreting […] or (…) attributes on the root element. On Mon, Oct 26, 2015 at 5:58 PM anakinjay notifications@github.com wrote:

How big of a performance hit are we talking about here? If I’m building a single page app, I can understand why it’s not a big deal…

but if I’m doing something like, building an interactive reusable user profile component to use in a wordpress theme or something, being able to just say is pretty important.

if it’s cycling through all attributes that causes the hit, how about a flag attribute it can check? something like:

If import isn’t set to true then it bypasses reading the properties?

Just a thought.

— Reply to this email directly or view it on GitHub https://github.com/angular/angular/issues/1858#issuecomment-151329445.

Get attributes outside the angular app is useful because it’s so easy to use them. If you won’t provide this feature, just add some good documentation to address this use case.

Btw, reading attributes of the top-level component is easier to understand 😄

Let’s leave this issue open so we can see how many people need this feature…

This is still an issue in Angular 8.2.13. Considering the longevity of this problem, I’d like to suggest adding a note on the Input Docs detailing the issue and linking t o this thread. Thanks!

okay,guys i recently run int the same problem and after a long search i decided to mix my ideas and ended up with the following. 1.declare an array object of the data you want to pass like private sendMe: Array<{title: string}> = [];

2.push the data you want to send to the array like this.sendMe.push({title: this.passMe});

3 and atlast pass the array like <app-download-section [movieTitle]="sendMe"></app-download-section>

  1. recieve it like
import { Component, OnInit, OnDestroy, Input } from '@angular/core';

@Component({
  selector: 'app-download-section',
  templateUrl: './download-section.component.html',
  styleUrls: ['./download-section.component.css']
})
export class DownloadSectionComponent implements OnInit {

  @Input('movieTitle') movieTitle: Array<object> = [];
  constructor() { }

  ngOnInit() {
    console.log(this.movieTitle);
  }
  // tslint:disable-next-line:use-life-cycle-interface
  ngOnDestroy() {

  }

}

@blteblte what about providing and injecting?

@robgha01 no problem, best of luck, and if you manage to get something working, let us know. I used angular 1 to supplement my existing application. angular 2 seems to be a very complete framework designed to be the core of the application, not something that is easily tacked onto an existing application. Let’s hope it changes in such a way that we can still leverage it. Either that, or we have to change our approach.

i was also looking for feature like this as well, on react it seems possible :

React.render(
    <App category={category}/>,
    document.getElementsByClassName('app')[0]
  );

so category can be any js variable than can be passed to the app component

This kind of stuff might be helpful from my point of view specially when working with CMS when you need to pass user defined variable to your angular entry point component

There’s a hack that I used while I was still trying Ng2.

page.MyComponent = ng.core.Component({
  ...
}).Class({
  constructor: [ng.core.ElementRef, function(ref) {
    var property = ref.nativeElement.getAttribute('my_property');
    ...
  }],
  ...
});

This is the plain javascript version (which ironically isn’t found anywhere in the documentation). You can find the typescript version somewhere I’m sure.

last chance before npm uninstall angular2 😃 is there any way how to bootstrap directive without any component? it is unusable by the other way. Do not take this as ofence I like your work. I almost want to cry, when I see what it can do, but I can’t use it because of obstinacy of the authors or whatever prevents you from adding this feature (it can be optional if you want to force some philosophy to other developers).

@realdope

Did you know that form submissions are hardcode disabled?

If you check the selector you’ll see that this is not the case if the form element has the ngNoForm attribute applied:

selector: 'form:not([ngNoForm]):not([ngFormModel]),ngForm,[ngForm]',