20

What is the best way to pass data from an ASP.NET MVC controller to an Angular 2.0 component? For example, we use the ASP.NET MVC Model and would like to send a JSON version of it to Angular to use it in Angular.

When the controller is serving the view, we can already push some data to Angular2 (the model). So additional AJAX call to fetch that data is not required.

However, I am struggling to "inject" it into the Angular component. How do you do this? Any good references for this? As you may have noticed, I'm quite new to Angular2.

My index.cshtml looks like this.

<div class="container">
<div>
    <h1>Welcome to the Angular 2 components!</h1>
</div>
<div class="row">
    <MyAngular2Component>
        <div class="alert alert-info" role="alert">
            <h3>Loading...</h3>
        </div>
    </MyAngular2Component>
</div>
</div>

Kind regards,

Rob

sajadre
  • 1,141
  • 2
  • 15
  • 30
Rob Van Pamel
  • 734
  • 1
  • 8
  • 23
  • What the controller actually renders? I mean Angular2 applications consist of static files. So you generate some of these files? – Thierry Templier Apr 25 '16 at 12:46
  • 1
    You need to build a REST-API that provides JSON data. This will perhaps help you: http://www.asp.net/web-api/overview/older-versions/build-restful-apis-with-aspnet-web-api – Dinistro Apr 25 '16 at 12:52
  • For clarity: I've added the view i was creating. which is a .cshtml file. – Rob Van Pamel Apr 25 '16 at 13:09
  • 1
    I usually let my mvc controllers provide a mixed role: they can either provide the angular view (as you already have), or they can be data sources which instead of returning html, return json. Can be done in a "cleaner" way with a separate rest api, but this way works. – Nathan Apr 25 '16 at 13:10
  • @Nathan: Do you have an example how to do this in Angular 2 ? – Rob Van Pamel Apr 25 '16 at 13:21
  • First you should provide JSON data from your MVC controller, then inside your Angular component you can use http class for getting the data. – Sirwan Afifi Apr 25 '16 at 13:35
  • @SirwanAfifi : That's just what i want to avoid, we are already serving some static html files (the view) from the server to the client. Why do I need to create another request for fetching the data? – Rob Van Pamel Apr 25 '16 at 13:38
  • @RobVanPamel because you usually load the view and the data separately in order to allow for re-use of the view (it's just a template for the data) - so you load one view and (potentially) multiple pieces of data. If you're only ever going to load a single piece of data, then angular is very over-kill. If you're going to load more than one, then the approach above is more efficient. – Nathan Apr 25 '16 at 13:57
  • @Nathan: for some views it is normal that data from multiple data sources is being fetched. For that i totally agree, but some pages, eg a detail view of a small entity can be served with data immediately. Is there then no way to resolve this? – Rob Van Pamel Apr 25 '16 at 14:04
  • Yea, why can't you just have the server plop the data you need in a script tag in the header as a JSON object and have the controller use that? – Pytth Apr 25 '16 at 14:53
  • this is an option, but i think it is a dirty way to do the trick. Aren't there other options? – Rob Van Pamel Apr 25 '16 at 17:25

7 Answers7

16

The best way that I have found to pass data in from MVC (or any hosting/startup page) is to specify the data as an attribute on the bootstrapped component, and use ElementRef to retrieve the value directly.

Below is an example for MVC derived from the this answer, which states that it is not possible to use @Input for root-level components.

Example:

//index.cshtml
<my-app username="@ViewBag.UserName">
    <i class="fa fa-circle-o-notch fa-spin"></i>Loading...
</my-app>


//app.component.ts
import {Component, Input, ElementRef} from '@angular/core';

@Component({
    selector: 'my-app',
    template: '<div> username: <span>{{username}}</span> </div>'
})
export class AppComponent {
    constructor(private elementRef: ElementRef) {}

    username: string = this.elementRef.nativeElement.getAttribute('username')
}

If you want to retrieve more complex data that is not suitable for an attribute, you can use the same technique, just put the data in a HTML <input type='hidden'/> element or a hidden <code> element, and use plain JS to retrieve the value.

myJson: string = document.getElementById("myJson").value

Warning: Access the DOM directly from a data-bound application such as Angular, breaks the data-binding pattern, and should be used with caution.

Community
  • 1
  • 1
Joseph Gabriel
  • 8,339
  • 3
  • 39
  • 53
2

You might want to look for similar questions related to AngularJS, not Angular 2 specific, as the main gist of the thing remains the same:

  • you want your server-side Razor engine to render some kind of view (i.e. HTML or JS directly)
  • this view contains a JS template where part of the content is filled from a server model instance or anyway server data (e.g. a resource, dictionary, etc.)
  • in order to properly fill a JS variable from Razor, C# server-side data has to be properly serialized into a JSON format

In this post by Marius Schulz you can see as he serializes the data and uses that to fill a template AngularJS value component:

<script>
  angular.module("hobbitModule").value("companionship", @Html.Raw(Model));
</script>

Something similar could be made to inject some data e.g. into window.myNamespace.myServerData, and then have Angular2 bootstrap that value among other providers.

In this post by Roel van Lisdonk, a similar approach is used, again, to fill an AngularJS-based template, with that ng-init directive:

<div ng-controller="main"
     ng-init="resources={ textFromResource: '@WebApplication1.Properties.Resources.TextFromResource'}">
  <h1>{{ title }}</h1>
  {{ resources.textFromResource }}
</div>

As the first post points out, there's something to think about (pasting here):

A word of caution: The method I'm about to use is probably not a good fit for large amounts of data. Since the JavaScript data is inlined into the HTML response, it's sent over the wire every single time you request that page.

Also, if the data is specific to the authenticated user, the response can't be cached and delivered to different users anymore. Please keep that in mind when considering to bootstrap your Angular Apps with .NET data this way.

The second part may be less of an issue if your served page is already dynamic server-side, i.e. if it already has bits filled in out of server-side data.

HTH

Community
  • 1
  • 1
superjos
  • 12,189
  • 6
  • 89
  • 134
1

You need to first bundle your services and controllers in separate module files and load services before controllers. For example:

dist
|--services.js
|--controllers.js

Then you need to load the JavaScript code of the Services via ASP.NET MVC JavaScript result, here you need to inject your startup data.

public class ScriptController: Controller
{
  public ActionResult GetServices(){
   string file= File.ReadAllText(Server.MapPath("~dist/services.js"));
   //modify the file to inject data or 
   var result = new JavaScriptResult();         
   result.Script = file;
   return result;     
}

Then in the index.html load the scripts as follows

<script src="/script/getservices"></script>
<script src="/dist/controller.js"></script>

This way you can inject data into angular code while loading. However, even this has a performance impact due to time spent on fetching the view, compiling the view, and binding data to the view. For an initial load performance can still be improved if you use Server Side Rendering.

Jigar
  • 544
  • 1
  • 8
  • 28
1

You can use the Input function exposed by @angular/core, I have for example an Angular 2 component to display information messages to the user of the application

My HTML template take an Angular 2 Message property

<div class="alert alert-info col-lg-10 col-lg-offset-1 col-md-10 col-md-offset-1 col-sm-10 col-sm-offset-1 col-xs-10 col-xs-offset-1">
    <i class="fa fa-info-circle"></i> {{ Message }}
</div>

The Message property is passed as an input to my Angular 2 component named informationmessage.component.ts, for example

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

@Component({
    selector: 'informationmessage',
    templateUrl: '../Templates/informationmessage.component.html'
})
export class InformationMessageComponent {
    @Input() Message: string;
}

I then pass the data to my InformationMessageComponent using property binding in the HTML page, for example.

<informationmessage [Message]="InformationMessage"></informationmessage>

You can replace InformationMessage in the above example with the data that you get from your MVC controller for example

<informationmessage [Message]="@Model.InformationMessage"></informationmessage>

Please note: I did not test this scenario, but there is no technical reason for it not working, at the end of the day you are just binding a value to an Angular 2 property.

Andre Lombaard
  • 6,985
  • 13
  • 55
  • 96
1

src/ApplicationConfiguration.ts

export class ApplicationConfiguration {
    public setting1: string;
    public setting2: string;
}

src/main.ts

declare var config : ApplicationConfiguration;
var providers = [{ provide: ApplicationConfiguration, useValue: config }];
platformBrowserDynamic(providers).bootstrapModule(AppModule)
.catch(err => console.log(err));

src/index.html

  <script type="text/javascript">
    var config = {setting1: "value1", setting2: "value2"};
  </script>

src/app/app.component.ts

export class AppComponent {

  private _config : ApplicationConfiguration;

  constructor(config: ApplicationConfiguration) {

    this._config = config;

  }

}
  • Could you please add some text what you are doing and why? It's better to not just put code, but also explanations. – Nico Albers May 12 '18 at 19:16
0

I found a much simpler solution. Don't attempt to get the attribute from in the constructor! Use the ngOnInit() hook instead. The property will be accessible as long as it has been decorated with @Input(). It just appears that it is not available by the time the constructor is called.

Component HTML:

<MyComponent [CustomAttribute]="hello"></MyComponent>

Component TS:

export class MyComponentComponent {
    @Input()
    public CustomAttribute: string;

    constructor() {}

    ngOnInit() {
        console.log("Got it! " + this.CustomAttribute);
    }
}
Eraph
  • 1,019
  • 1
  • 10
  • 21
0

Further to @Joseph Gabriel's approach, if you want a complex object to be passed via an attribute, in MVC you should serialize it to a string, and then in the angular side de-serialize it using JSON.Parse.

DekuDesu
  • 2,224
  • 1
  • 5
  • 19
JPB
  • 1
  • 1