Piszę funkcję, która może utworzyć szablon wiadomości e-mail z szablonu HTML i podanych informacji. W tym celu korzystam z funkcji Angular z zakresu $compile
.
Jest tylko jeden problem, którego nie mogę rozwiązać. Szablon składa się z szablonu bazowego z nieograniczoną liczbą ng-include
. Kiedy używam "najlepszej praktyki" $timeout
(advised here) Działa po usunięciu wszystkich ng-include
. Więc nie tego chcę.
Przykład $ timeout:
return this.$http.get(templatePath)
.then((response) => {
let template = response.data;
let scope = this.$rootScope.$new();
angular.extend(scope, processScope);
let generatedTemplate = this.$compile(jQuery(template))(scope);
return this.$timeout(() => {
return generatedTemplate[0].innerHTML;
});
})
.catch((exception) => {
this.logger.error(
TemplateParser.getOnderdeel(process),
"Email template creation",
(<Error>exception).message
);
return null;
});
Kiedy zacznę dodawać ng-include
do danego szablonu funkcja ta rozpoczyna powrót szablony, które nie są jeszcze w pełni skompilowane (a workarround jest zagnieżdżanie $timeout
funkcje). Uważam, że dzieje się tak z powodu asynchronicznej natury modelu ng-include
.
kod
Ten kod zwraca szablon HTML, gdy jest dokonywane renderingu (funkcja może być teraz użyte ponownie, see this question for the problem) Praca. Ale to rozwiązanie nie ma sensu, ponieważ używa on kątowego prywatnego $$phase
, aby sprawdzić, czy są jakieś trwające $digest
. Zastanawiam się, czy istnieje jakieś inne rozwiązanie?
return this.$http.get(templatePath)
.then((response) => {
let template = response.data;
let scope = this.$rootScope.$new();
angular.extend(scope, processScope);
let generatedTemplate = this.$compile(jQuery(template))(scope);
let waitForRenderAndPrint =() => {
if (scope.$$phase || this.$http.pendingRequests.length) {
return this.$timeout(waitForRenderAndPrint);
} else {
return generatedTemplate[0].innerHTML;
}
};
return waitForRenderAndPrint();
})
.catch((exception) => {
this.logger.error(
TemplateParser.getOnderdeel(process),
"Email template creation",
(<Error>exception).message
);
return null;
});
Co chcę
chciałbym mieć funkcjonalność, która mogłaby obsługiwać nieograniczoną ilość ng-inlude
„s, a jedynie powrót gdy szablon został pomyślnie utworzony. NIE renderuję tego szablonu i muszę zwrócić w pełni skompilowany szablon.
Rozwiązanie
Po eksperymentach z @estus odpowiedź końcu znalazłem inny sposób na sprawdzenie, kiedy $ kompilacji jest wykonywana. W ten sposób powstał poniższy kod. Przyczyna, dla której używam $q.defer()
, wynika z faktu, że szablon został rozstrzygnięty w przypadku zdarzenia. Z tego powodu nie mogę zwrócić wyniku jak normalna obietnica (nie mogę zrobić return scope.$on()
). Jedynym problemem w tym kodzie jest to, że zależy on w znacznym stopniu od ng-include
. Jeśli podasz tę funkcję, szablon, który nie ma wartości ng-include
, nigdy nie zostanie ponownie przesłany.
/**
* Using the $compile function, this function generates a full HTML page based on the given process and template
* It does this by binding the given process to the template $scope and uses $compile to generate a HTML page
* @param {Process} process - The data that can bind to the template
* @param {string} templatePath - The location of the template that should be used
* @param {boolean} [useCtrlCall=true] - Whether or not the process should be a sub part of a $ctrl object. If the template is used
* for more then only an email template this could be the case (EXAMPLE: $ctrl.<process name>.timestamp)
* @return {IPromise<string>} A full HTML page
*/
public parseHTMLTemplate(process: Process, templatePath: string, useCtrlCall = true): ng.IPromise<string> {
let scope = this.$rootScope.$new(); //Do NOT use angular.extend. This breaks the events
if (useCtrlCall) {
const controller = "$ctrl"; //Create scope object | Most templates are called with $ctrl.<process name>
scope[controller] = {};
scope[controller][process.__className.toLowerCase()] = process;
} else {
scope[process.__className.toLowerCase()] = process;
}
let defer = this.$q.defer(); //use defer since events cannot be returned as promises
this.$http.get(templatePath)
.then((response) => {
let template = response.data;
let includeCounts = {};
let generatedTemplate = this.$compile(jQuery(template))(scope); //Compile the template
scope.$on('$includeContentRequested', (e, currentTemplateUrl) => {
includeCounts[currentTemplateUrl] = includeCounts[currentTemplateUrl] || 0;
includeCounts[currentTemplateUrl]++; //On request add "template is loading" indicator
});
scope.$on('$includeContentLoaded', (e, currentTemplateUrl) => {
includeCounts[currentTemplateUrl]--; //On load remove the "template is loading" indicator
//Wait for the Angular bindings to be resolved
this.$timeout(() => {
let totalCount = Object.keys(includeCounts) //Count the number of templates that are still loading/requested
.map(templateUrl => includeCounts[templateUrl])
.reduce((counts, count) => counts + count);
if (!totalCount) { //If no requests are left the template compiling is done.
defer.resolve(generatedTemplate.html());
}
});
});
})
.catch((exception) => {
defer.reject(exception);
});
return defer.promise;
}
Dziękuję za odpowiedź. Jednak nie mogę znaleźć sposobu na zintegrowanie drugiego rozwiązania z moją funkcją (zobacz moje pytanie na temat). Problem polega na tym, że zdarzenie nigdy nie jest wyzwalane w żadnym ze zdarzeń po ustawieniu obserwatora zdarzeń na utworzonym obiekcie zasięgu. Czy masz przykład, w jaki sposób powinienem zintegrować to z moją funkcją? oh i twój plunkr nie działa. To nie daje mi żadnego wyjścia HTML. –
The plunk działa. Ma instrukcje 'console.log'. Sprawdź konsolę. Nie jestem pewien, co masz na myśli mówiąc o integracji. Musisz ustawić obserwatorów na zasięgu i zadzwonić do $ compile, właśnie to. Kolejność nie powinna mieć znaczenia, ale najpierw spróbuj ustawić obserwatorów. Rozważ dostarczenie pluna, które może odtworzyć problem, jeśli to nie działa. Jakikolwiek sposób, ng-include jest wersją starszą od 1.0 i powinno się jej unikać, jeśli jest to możliwe, ponieważ nie jest zgodne z najlepszymi praktykami kątowymi. – estus
Właśnie dowiedziałem się, że ze względu na fakt, że używam $ rootScope. $ New() (nie mam żadnego zakresu w usłudze) Zdarzenia nie są uruchamiane. Czy wiesz, dlaczego i jeśli $ rootScope to powoduje, czy znasz jakieś rozwiązanie? zobacz http://plnkr.co/edit/ZEVSG7TBpYirR77UDxcF?p=preview –