AngularJS ng-app หลายอันภายในหนึ่งหน้า


180

ฉันเพิ่งเริ่มเรียนรู้ Angular JS และสร้างตัวอย่างพื้นฐานบางอย่าง แต่ฉันติดอยู่กับปัญหาต่อไปนี้

ฉันได้สร้าง 2 โมดูลและ 2 ตัวควบคุม

shoppingCart -> ShoppingCartController
namesList -> NamesController

มีมุมมองที่เกี่ยวข้องสำหรับแต่ละคอนโทรลเลอร์ มุมมองแรกแสดงผลได้ดี แต่อันดับที่สองไม่แสดงผล ไม่มีข้อผิดพลาด

http://jsfiddle.net/ep2sQ/

โปรดช่วยฉันแก้ปัญหานี้

นอกจากนี้ยังมีความเป็นไปได้ที่จะเพิ่มคอนโซลในมุมมองเพื่อตรวจสอบว่าค่าใดถูกส่งผ่านจากตัวควบคุม

เช่นใน div ต่อไปนี้เราสามารถเพิ่ม console.log และส่งออกค่าควบคุม

<div ng-app="shoppingCart" ng-controller="ShoppingCartController">
</div>

10
อาจเป็นสิ่งนี้จะช่วย: stackoverflow.com/questions/12860595/ …
Cherniv

ขอบคุณ Cherniv สิ่งนี้มีประโยชน์มากและฉันแก้ไขปัญหาโดยใช้ลิงก์ที่คุณให้ไว้ โปรดคุณสามารถให้ข้อมูลเกี่ยวกับวิธีการใช้ console.log เพื่อถ่ายโอนตัวควบคุมภายในมุมมอง / แม่แบบ {{console.log}} ไม่ทำงาน
Nitin Mukesh

ยินดีต้อนรับคุณ โปรดทราบว่าคุณได้ทำ "คอนโซล" ในมุมมองนี้{{item.product_name}}แล้ว: "พิมพ์" ค่าจากแบบจำลองของคุณ
Cherniv

1
มีอะไรผิดปกติหรือไม่ในการสร้างหลายแอพสำหรับเว็บแอพ ฉันมีโครงการนี้ที่แต่ละหน้า html มีแอปเป็นของตัวเองฉันต้องการทราบว่าประสิทธิภาพจะได้รับผลกระทบหรือไม่
ต. เร็กซ์

แม้ว่าจะสามารถบูตแอปพลิเคชันมากกว่าหนึ่ง AngularJS ต่อหน้า แต่เราไม่ได้ทดสอบสถานการณ์นี้อย่างแข็งขัน เป็นไปได้ว่าคุณจะพบปัญหาโดยเฉพาะกับแอปที่ซับซ้อนดังนั้นจึงควรใช้ความระมัดระวัง ดูAngularJS ผู้พัฒนาคู่มือ - เงินทุน
georgeawg

คำตอบ:


190

ดังนั้นตามที่กล่าวไว้โดย Cherniv เราจำเป็นต้อง bootstrap โมดูลเพื่อให้มี ng-app หลายตัวในหน้าเดียวกัน ขอบคุณมากสำหรับอินพุตทั้งหมด

var shoppingCartModule = angular.module("shoppingCart", [])
shoppingCartModule.controller("ShoppingCartController",
  function($scope) {
    $scope.items = [{
      product_name: "Product 1",
      price: 50
    }, {
      product_name: "Product 2",
      price: 20
    }, {
      product_name: "Product 3",
      price: 180
    }];
    $scope.remove = function(index) {
      $scope.items.splice(index, 1);
    }
  }
);
var namesModule = angular.module("namesList", [])
namesModule.controller("NamesController",
  function($scope) {
    $scope.names = [{
      username: "Nitin"
    }, {
      username: "Mukesh"
    }];
  }
);
angular.bootstrap(document.getElementById("App2"), ['namesList']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>

<div id="App1" ng-app="shoppingCart" ng-controller="ShoppingCartController">
  <h1>Your order</h1>
  <div ng-repeat="item in items">
    <span>{{item.product_name}}</span>
    <span>{{item.price | currency}}</span>
    <button ng-click="remove($index);">Remove</button>
  </div>
</div>

<div id="App2" ng-app="namesList" ng-controller="NamesController">
  <h1>List of Names</h1>
  <div ng-repeat="_name in names">
    <p>{{_name.username}}</p>
  </div>
</div>


2
คุณสามารถสร้างคำสั่งเพื่อทำสิ่งนี้แทนstackoverflow.com/a/22898036/984780
Luis Perez

33
เอกสารเชิงมุมบอกว่าเมื่อทำการบู๊ตแอปด้วยตนเองห้ามใช้คำสั่ง ngApp ดังนั้น ng-app = "namesList" (สามารถ / ควร) ได้รับการรักอีกครั้ง docs.angularjs.org/guide/bootstrap
Mike_K

4
สำหรับผู้ที่มี ng-app ในไฟล์ js แยกสองไฟล์รหัสด้านล่างอาจช่วยให้ angular.element (เอกสาร). ready (function () {angular.bootstrap (document.getElementById ("App2"), ['namesList'] ;});
Siva Kannan

3
หมายเหตุ: ในแอพของฉันฉันต้องใส่บรรทัดนี้ "angular.bootstrap (document.getElementById (" App2 "), ['namesList']);" ใน $ (document) .ready function
La masse

มันไม่ทำงานสำหรับฉันเฉพาะ ng-app ตัวแรกที่ทำงานได้อย่างถูกต้อง
SIVAKUMAR.J

120

ในการรันแอปพลิเคชั่นหลายตัวในเอกสาร HTML คุณจะต้องบูตด้วยตนเองโดยใช้angular.bootstrap ()

HTML

<!-- Automatic Initialization -->
<div ng-app="myFirstModule">
    ...
</div>
<!-- Need To Manually Bootstrap All Other Modules -->
<div id="module2">
    ...
</div>

JS

angular.
  bootstrap(document.getElementById("module2"), ['mySecondModule']);

เหตุผลนี้คือแอปพลิเคชัน AngularJS เดียวเท่านั้นที่สามารถบูตโดยอัตโนมัติต่อเอกสาร HTML สิ่งแรกที่ng-appพบในเอกสารจะถูกใช้เพื่อกำหนดองค์ประกอบหลักให้กับ auto-bootstrap เป็นแอปพลิเคชัน

กล่าวอีกนัยหนึ่งถึงแม้ว่าในทางเทคนิคจะมีแอปพลิเคชั่นหลายตัวต่อหน้าเพียงคำสั่ง ng-app เดียวเท่านั้นที่จะถูกทำให้เป็นอินสแตนซ์และเริ่มต้นโดยอัตโนมัติโดย Angular framework


20
มีเพียงngAppคำสั่งเดียวเท่านั้นที่สามารถโหลดอัตโนมัติต่อเอกสาร HTML แต่คุณสามารถมีหลายแอพได้ตราบใดที่คุณบู๊ตไฟล์ต่อไปด้วยตนเอง
JaredMcAteer

@CodeHater namesListโมดูลอยู่ที่ไหนแล้ว? คุณช่วยอัพเดทคำตอบของคุณเพื่อให้ชัดเจนขึ้นได้ไหม
Eugene

นี่เป็นสิ่งที่ผิด คุณสามารถมีหลายแอปได้ ดูstackoverflow.com/a/24867989/753632
AndroidDev

3
@AndroidDev ฉันไม่ได้ติดตาม ลิงก์ที่คุณอ้างอิงไม่แสดงแอตทริบิวต์ ng-app หลายรายการ
Jpnh

42

คุณสามารถใช้angular.bootstrap()โดยตรง ...ปัญหาคือคุณเสียประโยชน์จากคำสั่ง

ก่อนอื่นคุณต้องได้รับการอ้างอิงถึงองค์ประกอบ HTML เพื่อที่จะบูตมันซึ่งหมายความว่าตอนนี้โค้ดของคุณจะถูกผนวกเข้ากับ HTML ของคุณ

ประการที่สองความสัมพันธ์ระหว่างสองไม่ชัดเจน ด้วยngAppคุณสามารถเห็นได้อย่างชัดเจนว่า HTML เกี่ยวข้องกับโมดูลใดและคุณรู้ว่าจะหาข้อมูลนั้นได้จากที่ใด แต่angular.bootstrap()อาจถูกเรียกใช้จากที่ใดก็ได้ในรหัสของคุณ

หากคุณกำลังจะทำมันด้วยวิธีที่ดีที่สุดก็คือการใช้คำสั่ง สิ่งที่ฉันทำ ngModuleมันเรียกว่า นี่คือลักษณะที่โค้ดของคุณจะดูเหมือนใช้:

<!DOCTYPE html>
<html>
    <head>
        <script src="angular.js"></script>
        <script src="angular.ng-modules.js"></script>
        <script>
          var moduleA = angular.module("MyModuleA", []);
          moduleA.controller("MyControllerA", function($scope) {
              $scope.name = "Bob A";
          });

          var moduleB = angular.module("MyModuleB", []);
          moduleB.controller("MyControllerB", function($scope) {
              $scope.name = "Steve B";
          });
        </script>
    </head>
    <body>
        <div ng-modules="MyModuleA, MyModuleB">
            <h1>Module A, B</h1>
            <div ng-controller="MyControllerA">
                {{name}}
            </div>
            <div ng-controller="MyControllerB">
                {{name}}
            </div>
        </div>

        <div ng-module="MyModuleB">
            <h1>Just Module B</h1>
            <div ng-controller="MyControllerB">
                {{name}}
            </div>
        </div>
    </body>
</html>

คุณสามารถรับซอร์สโค้ดได้ที่:

http://www.simplygoodcode.com/2014/04/angularjs-getting-around-ngapp-limitations-with-ngmodule/

ngAppก็ดำเนินการในลักษณะเดียวกับที่ มันเรียกเพียงแค่angular.bootstrap()เบื้องหลัง


9

ในกรณีของฉันฉันต้องปิด bootstrapping ของแอพที่angular.element(document).readyสองเพื่อให้ทำงานได้:

angular.element(document).ready(function() {
  angular.bootstrap(document.getElementById("app2"), ["app2"]);
});   

V1.6 เลิกใช้: ใช้แทนangular.element(callback) angular.element(document).ready(callback))ดูAngularJS อ้างอิง นอกจากนี้github.com/angular/angular.js/commit/…
georgeawg

7

นี่คือตัวอย่างของสองแอปพลิเคชันในหน้า html เดียวและสอง conrollers ในแอปพลิเคชันเดียว:

    <div ng-app = "myapp">
      <div  ng-controller = "C1" id="D1">
         <h2>controller 1 in app 1 <span id="titre">{{s1.title}}</span> !</h2>
      </div>

      <div  ng-controller = "C2" id="D2">
         <h2>controller 2 in app 1 <span id="titre">{{s2.valeur}}</span> !</h2>
      </div>
    </div>
    <script>
        var A1 = angular.module("myapp", [])

        A1.controller("C1", function($scope) {
            $scope.s1 = {};
            $scope.s1.title = "Titre 1";
         });

        A1.controller("C2", function($scope) {
            $scope.s2 = {};
            $scope.s2.valeur = "Valeur 2";
         });
    </script>

    <div ng-app="toapp" ng-controller="C1" id="App2">
        <br>controller 1 in app 2
        <br>First Name: <input type = "text" ng-model = "student.firstName">
        <br>Last Name : <input type="text" ng-model="student.lastName">
        <br>Hello : {{student.fullName()}}
        <br>
    </div>

    <script>
        var A2 = angular.module("toapp", []);
        A2.controller("C1", function($scope) {
            $scope.student={
                firstName:"M",
                lastName:"E",
                fullName:function(){
                    var so=$scope.student;
                    return so.firstName+" "+so.lastName;
                }
            };
        });
        angular.bootstrap(document.getElementById("App2"), ['toapp']);
    </script>
<style>
    #titre{color:red;}
    #D1{ background-color:gray; width:50%; height:20%;}
    #D2{ background-color:yellow; width:50%; height:20%;}
    input{ font-weight: bold; }
</style>


6

คุณสามารถผสานหลายโมดูลในหนึ่ง rootModule และกำหนดโมดูลนั้นเป็น ng-app ให้กับองค์ประกอบที่เหนือกว่าเช่นแท็ก body

รหัสอดีต:

    <!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="namesController.js"></script>
<script src="myController.js"></script>
<script>var rootApp = angular.module('rootApp', ['myApp1','myApp2'])</script>
<body ng-app="rootApp">

<div ng-app="myApp1" ng-controller="myCtrl" >
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{firstName + " " + lastName}}
</div>

<div ng-app="myApp2" ng-controller="namesCtrl">
<ul>
  <li ng-bind="first">{{first}}
  </li>
</ul>
</div>

</body>
</html>

4
คุณกำลังซ้อนแอปที่แตกต่างกันสองแอพไว้ใน rootApp แองกูลาร์ไม่อนุญาตการซ้อนแอป
codin

4

         var shoppingCartModule = angular.module("shoppingCart", [])
          shoppingCartModule.controller("ShoppingCartController",
           function($scope) {
             $scope.items = [{
               product_name: "Product 1",
               price: 50
             }, {
               product_name: "Product 2",
               price: 20
             }, {
               product_name: "Product 3",
               price: 180
             }];
             $scope.remove = function(index) {
               $scope.items.splice(index, 1);
             }
           }
         );
         var namesModule = angular.module("namesList", [])
          namesModule.controller("NamesController",
           function($scope) {
             $scope.names = [{
               username: "Nitin"
             }, {
               username: "Mukesh"
             }];
           }
         );


         var namesModule = angular.module("namesList2", [])
          namesModule.controller("NamesController",
           function($scope) {
             $scope.names = [{
               username: "Nitin"
             }, {
               username: "Mukesh"
             }];
           }
         );


         angular.element(document).ready(function() {
           angular.bootstrap(document.getElementById("App2"), ['namesList']);
           angular.bootstrap(document.getElementById("App3"), ['namesList2']);
         });
<!DOCTYPE html>
<html>

<head>
  <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>

</head>

<body>

  <div id="App1" ng-app="shoppingCart" ng-controller="ShoppingCartController">
    <h1>Your order</h1>
    <div ng-repeat="item in items">
      <span>{{item.product_name}}</span>
      <span>{{item.price | currency}}</span>
      <button ng-click="remove($index);">Remove</button>
    </div>
  </div>

  <div id="App2" ng-app="namesList" ng-controller="NamesController">
    <h1>List of Names</h1>
    <div ng-repeat="_name in names">
      <p>{{_name.username}}</p>
    </div>
  </div>
  <div id="App3" ng-app="namesList2" ng-controller="NamesController">
    <h1>List of Names</h1>
    <div ng-repeat="_name in names">
      <p>{{_name.username}}</p>
    </div>
  </div>


</body>

</html>


เพียงแค่ส่วนขยายที่มีแอปจำนวนมากในหน้าเดียวฉันต้องรวมทั้งรหัส saeb-amini และ @Nithin Mukesh - ขอบคุณทั้งคู่
Praneeth

แนวคิดนี้ใช้ได้สำหรับฉัน angular.element (เอกสาร). ready (function () {angular.bootstrap (document.getElementById ("App2"), ['namesList']); angular.bootstrap (document.getElementById ("App3"), ['names3' ]);});
SIVAKUMAR.J

2

แอปเดียวเท่านั้นที่เริ่มต้นโดยอัตโนมัติ คนอื่น ๆ จะต้องเริ่มต้นด้วยตนเองดังนี้:

ไวยากรณ์:

angular.bootstrap(element, [modules]);

ตัวอย่าง:

<!DOCTYPE html>
<html>

<head>
  <script src="https://code.angularjs.org/1.5.8/angular.js" data-semver="1.5.8" data-require="angular.js@1.5.8"></script>
  <script data-require="ui-router@0.2.18" data-semver="0.2.18" src="//cdn.rawgit.com/angular-ui/ui-router/0.2.18/release/angular-ui-router.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script>
    var parentApp = angular.module('parentApp', [])
  .controller('MainParentCtrl', function($scope) {
    $scope.name = 'universe';
  });



var childApp = angular.module('childApp', ['parentApp'])
  .controller('MainChildCtrl', function($scope) {
    $scope.name = 'world';
  });


angular.element(document).ready(function() {
  angular.bootstrap(document.getElementById('childApp'), ['childApp']);
});
    
  </script>
</head>

<body>
  <div id="childApp">
    <div ng-controller="MainParentCtrl">
      Hello {{name}} !
      <div>
        <div ng-controller="MainChildCtrl">
          Hello {{name}} !
        </div>
      </div>
    </div>
  </div>
</body>

</html>

AngularJS API


1

คุณสามารถกำหนด Root ng-App และใน ng-App นี้คุณสามารถกำหนดหลาย nd-Controler แบบนี้

    <!DOCTYPE html>
    <html>
    <script src = "https://ajax.googleapis.com/ajax/libs/angularjs/1.3.3/angular.min.js"></script>

<style>
         table, th , td {
            border: 1px solid grey;
            border-collapse: collapse;
            padding: 5px;
         }

         table tr:nth-child(odd) {
            background-color: #f2f2f2;
         }

         table tr:nth-child(even) {
            background-color: #ffffff;
         }
      </style>

     <script>
      var mainApp = angular.module("mainApp", []);

      mainApp.controller('studentController1', function ($scope) {
      $scope.student = {
      firstName: "MUKESH",
      lastName: "Paswan",

      fullName: function () {
         var studentObject;
         studentObject = $scope.student;
         return studentObject.firstName + " " + studentObject.lastName;
                     }
                 };
             });

             mainApp.controller('studentController2', function ($scope) {
                 $scope.student = {
                     firstName: "Mahesh",
                     lastName: "Parashar",
                     fees: 500,

                     subjects: [
                        { name: 'Physics', marks: 70 },
                        { name: 'Chemistry', marks: 80 },
                        { name: 'Math', marks: 65 },
                        { name: 'English', marks: 75 },
                        { name: 'Hindi', marks: 67 }
                     ],

                     fullName: function () {
                         var studentObject;
                         studentObject = $scope.student;
                         return studentObject.firstName + " " + studentObject.lastName;
                     }
                 };
             });
          </script>

    <body>
    <div ng-app = "mainApp">
    <div id="dv1"  ng-controller = "studentController1">
    Enter first name: <input type = "text" ng-model = "student.firstName"><br/><br/> Enter last name: <input type = "text" ng-model = "student.lastName"><br/>
    <br/>
     You are entering: {{student.fullName()}}
    </div>

    <div id="dv2" ng-controller = "studentController2">
     <table border = "0">
                <tr>
                   <td>Enter first name:</td>
                   <td><input type = "text" ng-model = "student.firstName"></td>
                </tr>

                <tr>
                   <td>Enter last name: </td>
                   <td>
                      <input type = "text" ng-model = "student.lastName">
                   </td>
                </tr>

                <tr>
                   <td>Name: </td>
                   <td>{{student.fullName()}}</td>
                </tr>

                <tr>
                   <td>Subject:</td>

                   <td>
                      <table>
                         <tr>
                            <th>Name</th>.
                            <th>Marks</th>
                         </tr>

                         <tr ng-repeat = "subject in student.subjects">
                            <td>{{ subject.name }}</td>
                            <td>{{ subject.marks }}</td>
                         </tr>

                      </table>
                   </td>

                </tr>
             </table>

          </div>
    </div>

    </body>
    </html>

1

// root-app
const rootApp = angular.module('root-app', ['app1', 'app2E']);

// app1
const app11aa = angular.module('app1', []);
app11aa.controller('main', function($scope) {
  $scope.msg = 'App 1';
});

// app2
const app2 = angular.module('app2E', []);
app2.controller('mainB', function($scope) {
  $scope.msg = 'App 2';
});

// bootstrap
angular.bootstrap(document.querySelector('#app1a'), ['app1']);
angular.bootstrap(document.querySelector('#app2b'), ['app2E']);
<!-- angularjs@1.7.0 -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.0/angular.min.js"></script>

<!-- root-app -->
<div ng-app="root-app">

  <!-- app1 -->
  <div id="app1a">
    <div ng-controller="main">
      {{msg}}
    </div>
  </div>

  <!-- app2 -->
  <div id="app2b">
    <div ng-controller="mainB">
      {{msg}}
    </div>
  </div>

</div>


0

ฉันได้ปรับเปลี่ยน jsfiddle ของคุณสามารถทำให้โมดูลส่วนใหญ่เป็น rootModule สำหรับโมดูลที่เหลือ ด้านล่างการดัดแปลงแก้ไขอัพเดทบน jsfiddle ของคุณ

  1. โมดูลที่สองสามารถฉีดใน RootModule
  2. ใน Html ที่สองที่กำหนดไว้ ng-app ที่อยู่ภายใน Root ng-app

Updated JsFiddle: http://jsfiddle.net/ep2sQ/1011/


ความคิดใดที่ว่าทำไมสิ่งนี้ถึงประเมินผลครั้งแรกเท่านั้นng-app? jsfiddle.net/vwcbtzdg
Si8

ครั้งแรกเท่านั้นเริ่มต้นโดยอัตโนมัติอื่น ๆ จะต้องเริ่มต้นด้วยตนเอง
Mano

0

ใช้angular.bootstrap(element, [modules], [config])เพื่อเริ่มแอปพลิเคชัน AngularJS ด้วยตนเอง (สำหรับข้อมูลเพิ่มเติมให้ดูที่คู่มือ Bootstrap )

ดูตัวอย่างต่อไปนี้:

// root-app
const rootApp = angular.module('root-app', ['app1', 'app2']);

// app1
const app1 = angular.module('app1', []);
app1.controller('main', function($scope) {
  $scope.msg = 'App 1';
});

// app2
const app2 = angular.module('app2', []);
app2.controller('main', function($scope) {
  $scope.msg = 'App 2';
});

// bootstrap
angular.bootstrap(document.querySelector('#app1'), ['app1']);
angular.bootstrap(document.querySelector('#app2'), ['app2']);
<!-- angularjs@1.7.0 -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.0/angular.min.js"></script>

<!-- root-app -->
<div ng-app="root-app">

  <!-- app1 -->
  <div id="app1">
    <div ng-controller="main">
      {{msg}}
    </div>
  </div>

  <!-- app2 -->
  <div id="app2">
    <div ng-controller="main">
      {{msg}}
    </div>
  </div>

</div>


-5
<html>
<head>
    <script src="angular.min.js"></script>
</head>
<body>
<div ng-app="shoppingCartParentModule" >
     <div ng-controller="ShoppingCartController">
        <h1>Your order</h1>
        <div ng-repeat="item in items">
            <span>{{item.product_name}}</span>
            <span>{{item.price | currency}}</span>
            <button ng-click="remove($index);">Remove</button>
        </div>
    </div>

    <div ng-controller="NamesController">
        <h1>List of Names</h1>
        <div ng-repeat="name in names">
            <p>{{name.username}}</p>
        </div>
    </div>
</div>
</body>
<script>
var shoppingCartModule = angular.module("shoppingCart", [])
            shoppingCartModule.controller("ShoppingCartController",
                function($scope) {
                    $scope.items = [
                        {product_name: "Product 1", price: 50},
                        {product_name: "Product 2", price: 20},
                        {product_name: "Product 3", price: 180}
                    ];
                    $scope.remove = function(index) {
                        $scope.items.splice(index, 1);
                    }
                }
            );
            var namesModule = angular.module("namesList", [])
            namesModule.controller("NamesController",
                function($scope) {
                    $scope.names = [
                        {username: "Nitin"},
                        {username: "Mukesh"}
                    ];
                }
            );
   angular.module("shoppingCartParentModule",["shoppingCart","namesList"])
</script>
</html>

1
เพิ่มคำอธิบายให้กับคำตอบของคุณสิ่งที่คุณโพสต์ไว้
Abhinav Singh Maurya

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