Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to render asp.net mvc view into angular 2?

I'm trying to integrate asp.net mvc with an angular 2 application. I understand that this is not ideal, but I am being asked to integrate some existing Mvc functionality (think big legacy app) into a brand new Angular 2 spa.

What I would like to be able to do is have a cshtml view that has angular components in it, as well as pure mvc stuff...

<side-bar></side-bar>
<action-bar></action-bar>

@{
    Html.RenderPartial("_SuperLegacyPartialView");   
}

I'm struggling to find any way to do this. This blog post looked promising - http://www.centare.com/tutorial-angular2-mvc-6-asp-net-5/. It used a templateUrl value that pointed to a path rendered by Mvc, as well as AsyncRoute, but none of that works anymore in Angular 2. This post looked promising as well - http://gbataille.github.io/2016/02/16/Angular2-Webpack-AsyncRoute.html, but it uses AsyncRoute too, which is deprecated.

This used to be very easy in Angular 1. We used to either manually bootstrap angular into a Razor View, or render a partial view as the templateUrl of a component/directive. What is the best way to do this in the latest Angular 2 that uses Webpack?

like image 232
Tim Hardy Avatar asked Jan 04 '17 21:01

Tim Hardy


2 Answers

I came up with a solution that satisfied my needs at the time. I'm using angular-cli with WebPack, and this worked for my needs. I don't understand all the examples I've seen that say to use "templateUrl: '/Template/Index'", where the path is a path to an MVC view. That simply doesn't work because the path can't be found inside any of the bundled views that WebPack creates. Maybe those people aren't using angular-cli and WebPack.

This stackoverflow answer - How can I use/create dynamic template to compile dynamic Component with Angular 2.0? was very helpful in creating the following directive. This directive will take the output of an mvc partial view and compile it. It allows for Razor/server logic to take place, and some angular to be compiled as well. Although, actually including other components inside this MVC partial was problematic. If you get that working, please let me know what you did. In my case, I just needed the server rendering to happen and to place that exactly where I wanted it in my Angular 2 spa.

MvcPartialDirective

import {
  Component,
  Directive,
  NgModule,
  Input,
  ViewContainerRef,
  Compiler,
  ComponentFactory,
  ModuleWithComponentFactories,
  ComponentRef,
  ReflectiveInjector, OnInit, OnDestroy
} from '@angular/core';

import { RouterModule }  from '@angular/router';
import { CommonModule } from '@angular/common';
import {Http} from "@angular/http";
import 'rxjs/add/operator/map';

export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
  const cmpClass = class DynamicComponent {};
  const decoratedCmp = Component(metadata)(cmpClass);

  @NgModule({ imports: [CommonModule, RouterModule], declarations: [decoratedCmp] })
  class DynamicHtmlModule { }

  return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
    .then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
      return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
    });
}

@Directive({ selector: 'mvc-partial' })
export class MvcPartialDirective implements OnInit, OnDestroy {
  html: string = '<p></p>';
  @Input() url: string;
  cmpRef: ComponentRef<any>;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler, private http: Http) { }

  ngOnInit() {
    this.http.get(this.url)
      .map(res => res.text())
      .subscribe(
        (html) => {
          this.html = html;
          if (!html) return;

          if(this.cmpRef) {
            this.cmpRef.destroy();
          }

          const compMetadata = new Component({
            selector: 'dynamic-html',
            template: this.html,
          });

          createComponentFactory(this.compiler, compMetadata)
            .then(factory => {
              const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
              this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
            });
        },
        err => console.log(err),
        () => console.log('MvcPartial complete')
      );

  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
  }
}

in some-component.html (assuming your mvc app shares the domain with your spa)

<mvc-partial [url]="'/stuffs/mvcstuff'"></mvc-partial>

MvcStuff.cshtml

@{
    ViewBag.Title = "This is some MVC stuff!!!";
}
<div>
    <h2>MVC Stuff:</h2>
    <h4>@ViewBag.Title</h4>
    <h2>Angular Stuff:</h2>
    <h4>{{1 + 1}}</h4>
</div>

in StuffsController.cs

public PartialViewResult MvcStuff() => PartialView();
like image 66
Tim Hardy Avatar answered Oct 13 '22 21:10

Tim Hardy


I did it like this.

@Component({
    templateUrl: '/Template/Index'
})
export class TemplateComponent {}

"/Template/Index" is the URL in your MVC Controller, and then the method.

public IActionResult Index()
  {
    return PartialView();
  }

My problem is i don't know how the refresh the view to call controller method every time is loaded.

like image 43
Federico Avatar answered Oct 13 '22 19:10

Federico