Übergeben von vom Backend gerenderten Parametern an die Angular2-Bootstrap-Methode

72

Gibt es eine Möglichkeit, im Backend gerenderte Argumente an die Angular2-Bootstrap-Methode zu übergeben? Ich möchte den http-Header für alle Anforderungen mithilfe von BaseRequestOptions mit dem vom Backend bereitgestellten Wert festlegen . Meine main.tsDatei sieht folgendermaßen aus:

import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from "./app.component.ts";

bootstrap(AppComponent);

Ich habe herausgefunden, wie diese Argumente an die Root-Komponente übergeben werden können ( https://stackoverflow.com/a/35553650/3455681 ), aber ich brauche sie, wenn ich die bootstrapMethode feuere ... Irgendwelche Ideen?

bearbeiten:

Inhalt von webpack.config.js:

module.exports = {
  entry: {
    app: "./Scripts/app/main.ts"
  },

  output: {
    filename: "./Scripts/build/[name].js"
  },

  resolve: {
    extensions: ["", ".ts", ".js"]
  },

  module: {
    loaders: [
      {
        test: /\.ts$/,
        loader: 'ts-loader'
      }
    ]
  }
};
Bodzio
quelle

Antworten:

95

update2

Plunker Beispiel

AoT aktualisieren

Um mit AoT arbeiten zu können, muss die Werksschließung ausgezogen werden

function loadContext(context: ContextService) {
  return () => context.load();
}

@NgModule({
  ...
  providers: [ ..., ContextService, { provide: APP_INITIALIZER, useFactory: loadContext, deps: [ContextService], multi: true } ],

Siehe auch https://github.com/angular/angular/issues/11262

Aktualisieren Sie ein letztes Beispiel für RC.6 und 2.0.0

function configServiceFactory (config: ConfigService) {
  return () => config.load();
}

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule,
        routes,
        FormsModule,
        HttpModule],
    providers: [AuthService,
        Title,
        appRoutingProviders,
        ConfigService,
        { provide: APP_INITIALIZER,
          useFactory: configServiceFactory
          deps: [ConfigService], 
          multi: true }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Wenn Sie nicht warten müssen, bis die Initialisierung abgeschlossen ist, kann auch der Konstruktor der Klasse AppModule {} verwendet werden:

class AppModule {
  constructor(/*inject required dependencies */) {...} 
}

Hinweis (zyklische Abhängigkeit)

Beispielsweise kann das Injizieren des Routers zyklische Abhängigkeiten verursachen. Um dies zu umgehen, injizieren Sie die Injectorund erhalten Sie die Abhängigkeit von

this.myDep = injector.get(MyDependency);

anstatt MyDependencydirekt zu injizieren wie:

@Injectable()
export class ConfigService {
  private router:Router;
  constructor(/*private router:Router*/ injector:Injector) {
    setTimeout(() => this.router = injector.get(Router));
  }
}

aktualisieren

Dies sollte in RC.5 genauso funktionieren, aber stattdessen den Provider providers: [...]des Root-Moduls hinzufügenbootstrap(...)

(habe mich noch nicht getestet).

aktualisieren

Ein interessanter Ansatz, um dies vollständig in Angular zu tun, wird hier erklärt: https://github.com/angular/angular/issues/9047#issuecomment-224075188

Sie können verwenden, APP_INITIALIZERwelche Funktion ausgeführt wird, wenn die App initialisiert wird, und die Bereitstellung verzögern, wenn die Funktion ein Versprechen zurückgibt. Dies bedeutet, dass die App ohne große Latenz initialisiert werden kann und Sie auch die vorhandenen Dienste und Framework-Funktionen verwenden können.

Angenommen, Sie haben eine Lösung mit mehreren Mandanten, bei der die Site-Informationen vom Domain-Namen abhängen, von dem aus sie bereitgestellt werden. Dies kann [Name] .letterpress.com oder eine benutzerdefinierte Domain sein, die mit dem vollständigen Hostnamen übereinstimmt. Wir können die Tatsache verbergen, dass dies hinter einem Versprechen steht, indem wir verwenden APP_INITIALIZER.

Im Bootstrap:

{provide: APP_INITIALIZER, useFactory: (sites:SitesService) => () => sites.load(), deps:[SitesService, HTTP_PROVIDERS], multi: true}),

sites.service.ts:

@Injectable()
export class SitesService {
  public current:Site;

  constructor(private http:Http, private config:Config) { }

  load():Promise<Site> {
    var url:string;
    var pos = location.hostname.lastIndexOf(this.config.rootDomain);
    var url = (pos === -1)
      ? this.config.apiEndpoint + '/sites?host=' + location.hostname
      : this.config.apiEndpoint + '/sites/' + location.hostname.substr(0, pos);
    var promise = this.http.get(url).map(res => res.json()).toPromise();
    promise.then(site => this.current = site);
    return promise;
  }

HINWEIS: configist nur eine benutzerdefinierte Konfigurationsklasse. rootDomainwäre '.letterpress.com'für dieses Beispiel und würde Dinge wie erlauben aptaincodeman.letterpress.com.

Alle Komponenten und anderen Dienste können jetzt Sitein sie eingefügt werden und die .currentEigenschaft verwenden, bei der es sich um ein konkretes Objekt handelt, ohne dass auf ein Versprechen in der App gewartet werden muss.

Dieser Ansatz schien die Startlatenz zu verringern, was ansonsten ziemlich auffiel, wenn Sie darauf warteten, dass das große Angular-Bundle geladen wurde, und dann eine weitere http-Anforderung, bevor der Bootstrap überhaupt begann.

Original

Sie können es mit Angulars-Abhängigkeitsinjektion übergeben:

var headers = ... // get the headers from the server

bootstrap(AppComponent, [{provide: 'headers', useValue: headers})]);
class SomeComponentOrService {
   constructor(@Inject('headers') private headers) {}
}

oder BaseRequestOptionsdirekt wie vorbereitet bereitstellen

class MyRequestOptions extends BaseRequestOptions {
  constructor (private headers) {
    super();
  }
} 

var values = ... // get the headers from the server
var headers = new MyRequestOptions(values);

bootstrap(AppComponent, [{provide: BaseRequestOptions, useValue: headers})]);
Günter Zöchbauer
quelle
2
Sie möchten also das aus dem HTML lesen. Sie können dem Server ein Skript-Tag hinzufügen, das sie einer globalen Variablen zuweist <script> function() { window.headers = someJson; }()</script>. Ich bin mir nicht sicher über die Syntax, ich benutze JS selbst nicht viel. Auf diese Weise müssten Sie überhaupt nicht analysieren.
Günter Zöchbauer
4
Ausgezeichnete Lösung, nur ein paar Notizen für zukünftige Googler wie mich: 1) Beachten Sie, dass a zurückgegeben werden load()muss Promise, nicht an Observable. Verwenden Sie die .toPromise()Funktion hier, wenn Sie Observables wie ich in Ihrem Dienst verwenden. 2) Sie fragen sich möglicherweise, wie Sie den Wert sites.load()in Ihre Dienste, Komponenten usw. abrufen können. Beachten Sie, dass er SitesServicezugewiesen wird this.current. Sie müssen also nur SitesServicein Ihre Komponente injizieren und ihre currentEigenschaft abrufen
Jared Phelps
2
Gute Lösung, aber wenn ich mein Projekt neu erstelle, wird folgende Fehlermeldung angezeigt: "FEHLER im Fehler beim statischen Auflösen von Symbolwerten. Funktionsaufrufe werden nicht unterstützt. Ersetzen Sie die Funktion oder das Lambda durch einen Verweis auf eine exportierte Funktion (Position 24:46 in der.) ursprüngliche .ts-Datei), Auflösung des Symbols AppModule in ... / src / app / app.module.ts ". Ich glaube, es zeigt auf den Lambda-Ausdruck in useFactory. Wie würden Sie das obige Lambda in eine exportierte Funktion übersetzen? Wirkt die Funktion einfach als Wrapper?
HGPB
2
Mit AoT müssen Sie () => sites.load()zu einer Funktion (außerhalb der Klasse und des Dekorateurs) wechseln und diese dann im Anbieter durch diesen Funktionsnamen ersetzen
Günter Zöchbauer
2
@ GünterZöchbauer danke, ich habe es versucht wie du vorgeschlagen hast aber bekomme den gleichen Fehler. Aber vielleicht folge ich auch nicht. Können Sie einen Blick auf meine Frage werfen
HGPB
30

In der endgültigen Version von Angular2 kann der APP_INITIALIZER-Anbieter verwendet werden, um das zu erreichen, was Sie möchten.

Ich habe eine Übersicht mit einem vollständigen Beispiel geschrieben: https://gist.github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9

Das Hauptbeispiel ist das Lesen aus JSON-Dateien, kann jedoch leicht geändert werden, um von einem REST-Endpunkt zu lesen.

Was Sie brauchen, ist im Grunde:

a) Richten Sie APP_INITIALIZER in Ihrer vorhandenen Moduldatei ein:

import { APP_INITIALIZER } from '@angular/core';
import { BackendRequestClass } from './backend.request';
import { HttpModule } from '@angular/http';

...

@NgModule({
    imports: [
        ...
        HttpModule
    ],
    ...
    providers: [
        ...
        ...
        BackendRequestClass,
        { provide: APP_INITIALIZER, useFactory: (config: BackendRequestClass) => () => config.load(), deps: [BackendRequestClass], multi: true }
    ],
    ...
});

Diese Zeilen rufen die load () -Methode aus der BackendRequestClass-Klasse auf, bevor Ihre Anwendung gestartet wird.

Stellen Sie sicher, dass Sie "HttpModule" im Abschnitt "Importe" festlegen, wenn Sie mithilfe der integrierten Angular2-Bibliothek http-Aufrufe an das Backend senden möchten.

b) Erstellen Sie eine Klasse und nennen Sie die Datei "backend.request.ts":

import { Inject, Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class BackendRequestClass {

    private result: Object = null;

    constructor(private http: Http) {

    }

    public getResult() {
        return this.result;
    }

    public load() {
        return new Promise((resolve, reject) => {
            this.http.get('http://address/of/your/backend/endpoint').map( res => res.json() ).catch((error: any):any => {
                reject(false);
                return Observable.throw(error.json().error || 'Server error');
            }).subscribe( (callResult) => {
                this.result = callResult;
                resolve(true);
            });

        });
    }
}

c) Um den Inhalt des Backend-Aufrufs zu lesen, müssen Sie nur die BackendRequestClass in eine Klasse Ihrer Wahl einfügen und getResult () aufrufen. Beispiel:

import { BackendRequestClass } from './backend.request';

export class AnyClass {
    constructor(private backendRequest: BackendRequestClass) {
        // note that BackendRequestClass is injected into a private property of AnyClass
    }

    anyMethod() {
        this.backendRequest.getResult(); // This should return the data you want
    }
}

Lassen Sie mich wissen, ob dies Ihr Problem löst.

computeiro
quelle
2
In Angular 2.3.0 wird folgende Fehlermeldung angezeigt: "Nicht behandelte Ablehnung von Versprechungen: appInits [i] ist keine Funktion; Zone: <wurzel>; Aufgabe: Promise.then; Wert: TypeError: appInits [i] ist keine function (…) TypeError: appInits [i] ist keine Funktion bei new ApplicationInitStatus (eval bei <anonymous> ( localhost: 8080 / js / vendor.js: 89: 2 ), <anonymous>: 3751: 49) bei MyModuleInjector. createInternal (/MyModule/module.ngfactory.js:454:36) - Es scheint, dass das vom Laden zurückgegebene Versprechen nicht auch von der useFactory-Funktion zurückgegeben werden kann.
IanT8
@ IanT8 Beachten Sie, dass die Factory-Funktion keine Funktion zurückgibt, sondern eine Funktion, die ein Versprechen zurückgibt. Dies war die Ursache für diesen AppInits [i] -Fehler für mich.
Jared Phelps
Ich habe genau den gleichen Fehler. Gelöst durch Hinzufügen des Extra () =>für useFactory
Stephen Paul
Der Versuch, die gleichen Schritte in angle4 zu befolgen, aber es funktioniert nicht, kein Fehler, da keine Daten angezeigt werden
Shubham Tiwari
8

Anstatt dass Ihr Einstiegspunkt Bootstrap selbst aufruft, können Sie eine Funktion erstellen und exportieren, die die Arbeit erledigt:

export function doBootstrap(data: any) {
    platformBrowserDynamic([{provide: Params, useValue: new Params(data)}])
        .bootstrapModule(AppModule)
        .catch(err => console.error(err));
}

Sie können diese Funktion abhängig von Ihrem Setup (Webpack / SystemJS) auch auf dem globalen Objekt platzieren. Es ist auch AOT-kompatibel.

Dies hat den zusätzlichen Vorteil, dass der Bootstrap verzögert wird, wenn dies sinnvoll ist. Wenn Sie diese Benutzerdaten beispielsweise als AJAX-Aufruf abrufen, nachdem der Benutzer ein Formular ausgefüllt hat. Rufen Sie einfach die exportierte Bootstrap-Funktion mit diesen Daten auf.

André Werlang
quelle
Wie kann auf diese übergebenen "Daten" im AppModule zugegriffen werden?
Ajey
@ Ajey injizieren Parameter auf jedes injizierbare Element
André Werlang
In meinem Fall war dies die bessere Option. Ich wollte das Laden der App manuell durch ein anderes Ereignis auf der Seite
starten
1

Die einzige Möglichkeit, dies zu tun, besteht darin, diese Werte bei der Definition Ihrer Anbieter anzugeben:

bootstrap(AppComponent, [
  provide(RequestOptions, { useFactory: () => {
    return new CustomRequestOptions(/* parameters here */);
  });
]);

Dann können Sie diese Parameter in Ihrer CustomRequestOptionsKlasse verwenden:

export class AppRequestOptions extends BaseRequestOptions {
  constructor(parameters) {
    this.parameters = parameters;
  }
}

Wenn Sie diese Parameter aus einer AJAX-Anforderung erhalten, müssen Sie asynchron wie folgt booten:

var appProviders = [ HTTP_PROVIDERS ]

var app = platform(BROWSER_PROVIDERS)
  .application([BROWSER_APP_PROVIDERS, appProviders]);

var http = app.injector.get(Http);
http.get('http://.../some path').flatMap((parameters) => {
  return app.bootstrap(appComponentType, [
    provide(RequestOptions, { useFactory: () => {
      return new CustomRequestOptions(/* parameters here */);
    }})
  ]);
}).toPromise();

Siehe diese Frage:

Bearbeiten

Da Sie Ihre Daten im HTML haben, können Sie Folgendes verwenden.

Sie können eine Funktion importieren und mit Parametern aufrufen.

Hier ist ein Beispiel des Hauptmoduls, mit dem Ihre Anwendung gebootet wird:

import {bootstrap} from '...';
import {provide} from '...';
import {AppComponent} from '...';

export function main(params) {
  bootstrap(AppComponent, [
    provide(RequestOptions, { useFactory: () => {
      return new CustomRequestOptions(params);
    });
  ]);
}

Dann können Sie es wie folgt von Ihrer HTML-Hauptseite importieren:

<script>
  var params = {"token": "@User.Token", "xxx": "@User.Yyy"};
  System.import('app/main').then((module) => {
    module.main(params);
  });
</script>

Siehe diese Frage: Übergeben Sie konstante Werte von _layout.cshtml an Angular .

Thierry Templier
quelle
Aber wie werden diese Parameter in eine Typoskriptdatei gerendert? Oder sollte ich diese Bootstrap-Methode in einem Inline-Skript auf Seite ausführen? Aber wie geht das, wenn es6-Importe verwendet werden?
Bodzio
Was genau meinst du mit rendern? Generieren Sie Ihre Haupt-HTML-Datei / JS-Dateien vom Server? Führen Sie eine AJAX-Anforderung aus, um diese Parameter abzurufen?
Thierry Templier
Ich generiere meine Ansichten vom Server. Ich dachte, ich werde alle notwendigen Parameter auf der Backend-Seite {"token": "@User.Token", "xxx": "@User.Yyy"}so rendern: also in gerendertem HTML werde ich haben {"token": "123abc456def", "xxx": "yyy"}. Und ich möchte diesen gerenderten JSON irgendwie an die Bootstrap-Methode übergeben, die ich in der .js-Datei habe.
Bodzio
Gibt es eine Möglichkeit, dies ohne SystemJS auszuführen (ich verwende Webpack und der Einstiegspunkt ist in der Datei
webpack.config
Ich bin kein Webpack-Experte, aber ich könnte versuchen ... Können Sie den Inhalt Ihrer Datei webpack.config hinzufügen? Vielen Dank!
Thierry Templier