การรวบรวมสตริง HTML แบบไดนามิกจากฐานข้อมูล


132

สถานการณ์

ซ้อนอยู่ภายในแอป Angular ของเราเป็นคำสั่งที่เรียกว่า Page ซึ่งได้รับการสนับสนุนโดยคอนโทรลเลอร์ซึ่งมี div ที่มีแอตทริบิวต์ ng-bind-html-unsafe สิ่งนี้ถูกกำหนดให้กับ $ scope var ที่เรียกว่า 'pageContent' ตัวแปรนี้ได้รับการกำหนด HTML ที่สร้างขึ้นแบบไดนามิกจากฐานข้อมูล เมื่อผู้ใช้พลิกไปที่หน้าถัดไปจะมีการเรียกไปยังฐานข้อมูลและ pageContent var ถูกตั้งค่าเป็น HTML ใหม่นี้ซึ่งจะแสดงผลบนหน้าจอผ่าน ng-bind-html-unsafe นี่คือรหัส:

คำสั่งเพจ

angular.module('myApp.directives')
    .directive('myPage', function ($compile) {

        return {
            templateUrl: 'page.html',
            restrict: 'E',
            compile: function compile(element, attrs, transclude) {
                // does nothing currently
                return {
                    pre: function preLink(scope, element, attrs, controller) {
                        // does nothing currently
                    },
                    post: function postLink(scope, element, attrs, controller) {
                        // does nothing currently
                    }
                }
            }
        };
    });

เทมเพลตของคำสั่งเพจ ("page.html" จากคุณสมบัติ templateUrl ด้านบน)

<div ng-controller="PageCtrl" >
   ...
   <!-- dynamic page content written into the div below -->
   <div ng-bind-html-unsafe="pageContent" >
   ...
</div>

ตัวควบคุมหน้า

angular.module('myApp')
  .controller('PageCtrl', function ($scope) {

        $scope.pageContent = '';

        $scope.$on( "receivedPageContent", function(event, args) {
            console.log( 'new page content received after DB call' );
            $scope.pageContent = args.htmlStrFromDB;
        });

});

ที่ได้ผล เราเห็น HTML ของหน้าจาก DB ที่แสดงผลอย่างสวยงามในเบราว์เซอร์ เมื่อผู้ใช้พลิกไปที่หน้าถัดไปเราจะเห็นเนื้อหาของหน้าถัดไปและอื่น ๆ จนถึงตอนนี้ดีมาก

ปัญหา

ปัญหาคือเราต้องการให้มีเนื้อหาเชิงโต้ตอบภายในเนื้อหาของเพจ ตัวอย่างเช่น HTML อาจมีภาพขนาดย่อที่เมื่อผู้ใช้คลิกที่ Angular ควรทำสิ่งที่ยอดเยี่ยมเช่นการแสดงหน้าต่างโมดอลป๊อปอัป ฉันได้วางการเรียกเมธอด Angular (ng-click) ไว้ในสตริง HTML ในฐานข้อมูลของเรา แต่แน่นอนว่า Angular จะไม่รู้จักการเรียกใช้วิธีการหรือคำสั่งเว้นแต่จะแยกวิเคราะห์สตริง HTML จดจำและรวบรวมข้อมูลเหล่านี้

ในฐานข้อมูลของเรา

เนื้อหาสำหรับหน้า 1:

<p>Here's a cool pic of a lion. <img src="lion.png" ng-click="doSomethingAwesone('lion', 'showImage')" > Click on him to see a large image.</p>

เนื้อหาสำหรับหน้า 2:

<p>Here's a snake. <img src="snake.png" ng-click="doSomethingAwesone('snake', 'playSound')" >Click to make him hiss.</p>

กลับไปที่ตัวควบคุมหน้าจากนั้นเราจะเพิ่มฟังก์ชัน $ scope ที่เกี่ยวข้อง:

ตัวควบคุมหน้า

$scope.doSomethingAwesome = function( id, action ) {
    console.log( "Going to do " + action + " with "+ id );
}

ฉันคิดไม่ออกว่าจะเรียกเมธอดนั้น 'doSomethingAwesome' ได้อย่างไรจากในสตริง HTML จาก DB ฉันรู้ว่า Angular ต้องแยกวิเคราะห์สตริง HTML แต่อย่างไร ฉันได้อ่านคำพูดที่คลุมเครือเกี่ยวกับบริการคอมไพล์ $ และคัดลอกและวางตัวอย่างบางส่วน แต่ไม่มีอะไรได้ผล นอกจากนี้ตัวอย่างส่วนใหญ่จะแสดงเนื้อหาแบบไดนามิกเฉพาะที่ได้รับการตั้งค่าในช่วงการเชื่อมโยงของคำสั่ง เราต้องการให้เพจมีชีวิตอยู่ตลอดชีวิตของแอป มันรับรวบรวมและแสดงเนื้อหาใหม่อย่างต่อเนื่องเมื่อผู้ใช้พลิกดูหน้าต่างๆ

ในแง่นามธรรมฉันเดาว่าคุณอาจพูดได้ว่าเรากำลังพยายามซ้อนชิ้นส่วนของ Angular ภายในแอป Angular แบบไดนามิกและจำเป็นต้องสามารถสลับเข้าและออกได้

ฉันได้อ่านเอกสาร Angular หลาย ๆ ครั้งรวมถึงบล็อกโพสต์ทุกประเภทและ JS Fiddled with people code ฉันไม่รู้ว่าฉันเข้าใจ Angular ผิดไปหมดหรือขาดอะไรง่ายๆไปหรือบางทีฉันก็ทำงานช้า ไม่ว่าในกรณีใดฉันสามารถใช้คำแนะนำได้


2
$ compile และบล็อกเอกสารที่อยู่รอบ ๆ มันทำให้ฉันรู้สึกว่าตัวเองทำงานช้าเช่นกันแม้ว่าฉันจะรู้สึกว่า js ของฉันค่อนข้างแข็งแกร่ง - ฉันคิดว่าถ้าฉันจับได้ด้วยสิ่งนี้ฉันจะสร้างบล็อกสไตล์งี่เง่า - นั่นคือความพิเศษของฉัน!
ลงจอด

คำตอบ:


249

ng-bind-html-unsafeแสดงผลเนื้อหาเป็น HTML เท่านั้น ไม่ผูกขอบเขตเชิงมุมกับ DOM ที่เป็นผลลัพธ์ คุณต้องใช้$compileบริการเพื่อจุดประสงค์นั้น ฉันสร้างplunker นี้เพื่อสาธิตวิธีการใช้$compileสร้างคำสั่งการแสดงผลไดนามิก HTML ที่ผู้ใช้ป้อนและเชื่อมโยงกับขอบเขตของคอนโทรลเลอร์ แหล่งที่มาโพสต์ด้านล่าง

demo.html

<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script data-require="angular.js@1.0.7" data-semver="1.0.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Compile dynamic HTML</h1>
    <div ng-controller="MyController">
      <textarea ng-model="html"></textarea>
      <div dynamic="html"></div>
    </div>
  </body>

</html>

script.js

var app = angular.module('app', []);

app.directive('dynamic', function ($compile) {
  return {
    restrict: 'A',
    replace: true,
    link: function (scope, ele, attrs) {
      scope.$watch(attrs.dynamic, function(html) {
        ele.html(html);
        $compile(ele.contents())(scope);
      });
    }
  };
});

function MyController($scope) {
  $scope.click = function(arg) {
    alert('Clicked ' + arg);
  }
  $scope.html = '<a ng-click="click(1)" href="#">Click me</a>';
}

6
ขอบคุณมาก Buu! การสร้างคำสั่งแอตทริบิวต์และการเพิ่มฟังก์ชันการเฝ้าดูขอบเขตเป็นสองสิ่งที่ฉันขาดหายไป ตอนนี้ใช้งานได้แล้วเดาว่าฉันจะอ่านคำสั่งและ $ compile อีกครั้งเพื่อทำความเข้าใจสิ่งที่เกิดขึ้นภายใต้ประทุน
giraffe_sense

11
ฉันก็เช่นกันทีม Angular สามารถปรับปรุงเอกสารในเรื่องนี้ได้จริงๆ
Craig Morgan

$compile(ele.contents())(scope);- บรรทัดนี้แก้ไขปัญหาของฉันในการไม่รวบรวมส่วนประกอบเชิงมุมซึ่งเพิ่มแบบไดนามิก ขอบคุณ.
Mital Pritmani

@BuuNguyen ภายใน teplateURL สมมติว่าคุณรวมหน้า htmnl แบบไดนามิกโดยใช้ ng-bind-html การใช้งานคอมไพล์ไม่ได้ให้ข้อผิดพลาดจากเนื้อหาที่ไม่ปลอดภัยบางด้านโดยใช้ trustAsHTml เพียงลบข้อผิดพลาดที่ไม่ปลอดภัยไม่รวบรวมข้อเสนอแนะใด ๆ
anam

1
ฉันชอบตัวอย่างนี้ แต่มันไม่ได้ผลของฉัน ฉันมีคำสั่งสวิตช์ที่เกิดขึ้นเนื่องจากการเลือกของผู้ใช้ดังนั้นมันจึงเป็นแบบไดนามิก ขึ้นอยู่กับว่าฉันต้องการแทรก html ที่มีคำสั่ง คำสั่งใช้งานได้ถ้าฉันวางไว้ในขั้นตอนการบูตตามธรรมชาติ อย่างไรก็ตามฉันมีสิ่งนี้ที่ไม่ได้เริ่มทำงาน --- case 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>'); หยุดพัก; --- เมื่อฉันต้องการทำสิ่งต่างๆเช่น --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>')); ความคิดใด ๆ เกี่ยวกับวิธีแก้ปัญหา ฯลฯ ...
ถึง

19

ในเชิงมุม 1.2.10 บรรทัดscope.$watch(attrs.dynamic, function(html) {แสดงข้อผิดพลาดของอักขระที่ไม่ถูกต้องเนื่องจากพยายามดูค่าattrs.dynamicซึ่งเป็นข้อความ html

ฉันแก้ไขโดยดึงแอตทริบิวต์จากคุณสมบัติขอบเขต

 scope: { dynamic: '=dynamic'}, 

ตัวอย่างของฉัน

angular.module('app')
  .directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      scope: { dynamic: '=dynamic'},
      link: function postLink(scope, element, attrs) {
        scope.$watch( 'dynamic' , function(html){
          element.html(html);
          $compile(element.contents())(scope);
        });
      }
    };
  });

สวัสดีถ้าฉันใช้element.htmlมันส่งคืน TypeError ให้ฉัน: ไม่สามารถเรียกเมธอด 'insertBefore' ของ null ได้ หลังจาก googling เกี่ยวกับเรื่องนี้ฉันพบว่าฉันต้องใช้element.appendแต่ถ้าฉันใช้คำสั่งนั้นในหลาย ๆ ที่มันจะสร้าง HTML แบบทวีคูณ คำสั่ง 2 คำสั่งจึงสร้างโค้ด HTML เดียวกัน 4 รายการ ขอบคุณสำหรับคำตอบ.
DzeryCZ

ฉันจะไม่ใช้ต่อท้ายแทนคุณฉันจะดูคืนนี้แล้วฉันจะติดต่อกลับ พูดตามตรงฉันใช้คำสั่งนี้ในหลาย ๆ ที่ในเพจโดยไม่มีปัญหาใด ๆ ฉันจะพยายามทำให้เกิดปัญหาขึ้นและจะติดต่อกลับไป
Alexandros Spyropoulos

1
@AlexandrosSpyropoulos ฉันเพิ่งทดสอบและดูว่ารหัสของฉันทำงานได้ดีแม้จะมี 1.2.12 ฉันคิดว่าคุณอาจพลาดการประกาศ <div dynamic = "html"> ใน HTML? (ด้วยการประกาศดังกล่าว $ watch จะเฝ้าดูคุณสมบัติ 'html' ในขอบเขตไม่ใช่ HTML จริงตามที่คุณกล่าวไว้ดังนั้นจึงไม่ควรมีข้อผิดพลาดถ่านที่ไม่ถูกต้อง) หากไม่เป็นเช่นนั้นโปรดส่ง plunkr ที่แสดงว่าไม่ทำงานให้ฉัน จะเห็นว่ามีอะไรผิดปกติ
Buu Nguyen

คุณคงพูดถูก ฉันคาดหวังว่าในตอนนั้น html เป็นตัวแปรที่มี html: P เป็นความคิดที่ดีที่จะกำหนดขอบเขตของคำสั่งของคุณ umur.io/…
Alexandros Spyropoulos

$compile(ele.contents())(scope);- บรรทัดนี้แก้ไขปัญหาของฉันในการไม่รวบรวมส่วนประกอบเชิงมุมซึ่งเพิ่มแบบไดนามิก ขอบคุณ.
Mital Pritmani

5

พบในกลุ่มสนทนาของ Google ใช้ได้ผลสำหรับฉัน

var $injector = angular.injector(['ng', 'myApp']);
$injector.invoke(function($rootScope, $compile) {
  $compile(element)($rootScope);
});

3

คุณสามารถใช้ได้

ng-bind-html https://docs.angularjs.org/api/ng/service/$sce

คำสั่งในการผูก html แบบไดนามิก อย่างไรก็ตามคุณต้องรับข้อมูลผ่านบริการ $ sce

โปรดดูการสาธิตสดได้ที่http://plnkr.co/edit/k4s3Bx

var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$sce) {
    $scope.getHtml=function(){
   return $sce.trustAsHtml("<b>Hi Rupesh hi <u>dfdfdfdf</u>!</b>sdafsdfsdf<button>dfdfasdf</button>");
   }
});

  <body ng-controller="MainCtrl">
<span ng-bind-html="getHtml()"></span>
  </body>

ขอบคุณ! สิ่งนี้ช่วยฉันได้ อย่างไรก็ตามคุณต้องรวม ngSanitize และ angular-sanitize.js:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft

ที่ใช้งานได้สำหรับฉันเช่นกันในระหว่างการผูกไอคอน bootstrap กับองค์ประกอบ md-list span ของวัสดุ
changtung

1

ลองใช้โค้ดด้านล่างนี้เพื่อผูก html ผ่าน attr

.directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      scope: { dynamic: '=dynamic'},
      link: function postLink(scope, element, attrs) {
        scope.$watch( 'attrs.dynamic' , function(html){
          element.html(scope.dynamic);
          $compile(element.contents())(scope);
        });
      }
    };
  });

ลองใช้ element.html นี้ (scope.dynamic); กว่า element.html (attr.dynamic);

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.