Chỉ thị góc - khi nào và làm thế nào để sử dụng biên dịch, bộ điều khiển, liên kết trước và liên kết sau [đóng]


451

Khi viết một lệnh Angular, người ta có thể sử dụng bất kỳ hàm nào sau đây để thao tác hành vi, nội dung và giao diện của phần tử mà lệnh được khai báo:

  • biên dịch
  • bộ điều khiển
  • liên kết trước
  • liên kết

Dường như có một số nhầm lẫn về việc nên sử dụng chức năng nào. Câu hỏi này bao gồm:

Chỉ thị cơ bản

Bản chất chức năng, làm và không

Câu hỏi liên quan:


27
Những gì những gì ?
haimlit

2
@Ian Xem: Toán tử quá tải . Về cơ bản, điều này là dành cho cộng đồng wiki. Quá nhiều câu trả lời cho các câu hỏi liên quan là một phần, không cung cấp hình ảnh đầy đủ.
Izhaki

8
Đây là nội dung tuyệt vời, nhưng chúng tôi yêu cầu mọi thứ ở đây phải được giữ ở định dạng Hỏi & Đáp. Có lẽ bạn muốn chia câu hỏi này thành nhiều câu hỏi riêng biệt và sau đó liên kết với chúng từ thẻ wiki?
Flexo

57
Mặc dù bài đăng này không có chủ đề và ở dạng blog, nhưng nó hữu ích nhất trong việc cung cấp một lời giải thích sâu sắc về các chỉ thị của Angular. Xin đừng xóa bài đăng này, quản trị viên!
Exegesis

12
Thành thật mà nói, tôi thậm chí không bận tâm đến các tài liệu gốc. Một bài đăng stackoverflow hoặc một blog thường khiến tôi đi trong vòng vài giây, so với 15-30 phút xé tóc cố gắng hiểu các tài liệu gốc.
David

Câu trả lời:


168

Theo thứ tự các chức năng chỉ thị được thực hiện?

Đối với một chỉ thị duy nhất

Dựa trên plunk sau , hãy xem xét đánh dấu HTML sau:

<body>
    <div log='some-div'></div>
</body>

Với tuyên bố chỉ thị sau:

myApp.directive('log', function() {

    return {
        controller: function( $scope, $element, $attrs, $transclude ) {
            console.log( $attrs.log + ' (controller)' );
        },
        compile: function compile( tElement, tAttributes ) {
            console.log( tAttributes.log + ' (compile)'  );
            return {
                pre: function preLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (pre-link)'  );
                },
                post: function postLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (post-link)'  );
                }
            };
         }
     };  

});

Đầu ra giao diện điều khiển sẽ là:

some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)

Chúng ta có thể thấy rằng compileđược thực hiện đầu tiên, sau đó controller, sau đó pre-linkvà cuối cùng là post-link.

Đối với chỉ thị lồng nhau

Lưu ý: Những điều sau đây không áp dụng cho các chỉ thị khiến con cái họ trong chức năng liên kết của chúng. Khá nhiều chỉ thị Angular làm như vậy (như ng If, ngRepeat, hoặc bất kỳ chỉ thị nào với transclude). Những chỉ thị này thực chất sẽ có linkchức năng được gọi trước khi chỉ thị con của chúng compileđược gọi.

Đánh dấu HTML gốc thường được tạo từ các phần tử lồng nhau, mỗi phần tử có chỉ thị riêng. Giống như trong đánh dấu sau (xem plunk ):

<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>

Đầu ra giao diện điều khiển sẽ trông như thế này:

// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)

// The link phase   
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)

Chúng ta có thể phân biệt hai giai đoạn ở đây - giai đoạn biên dịch và giai đoạn liên kết .

Giai đoạn biên dịch

Khi DOM được tải, Angular bắt đầu giai đoạn biên dịch, tại đó nó đi qua đánh dấu từ trên xuống và gọi compiletất cả các lệnh. Về mặt đồ họa, chúng ta có thể diễn đạt nó như vậy:

Một hình ảnh minh họa vòng lặp biên dịch cho trẻ em

Có lẽ điều quan trọng cần đề cập là ở giai đoạn này, các mẫu mà hàm biên dịch nhận được là các mẫu nguồn (không phải mẫu mẫu).

Giai đoạn liên kết

Các phiên bản DOM thường đơn giản là kết quả của một mẫu nguồn được hiển thị cho DOM, nhưng chúng có thể được tạo bởi ng-repeathoặc được giới thiệu một cách nhanh chóng.

Bất cứ khi nào một phiên bản mới của một phần tử có lệnh được hiển thị cho DOM, pha liên kết sẽ bắt đầu.

Trong giai đoạn này, các cuộc gọi kiễu góc controller, pre-link, lặp trẻ em, và cuộc gọi post-linktrên tất cả các chỉ thị, như vậy:

Một minh họa cho thấy các bước giai đoạn liên kết


5
@lzhaki Lưu đồ trông đẹp. Tâm trí để chia sẻ tên của công cụ biểu đồ? :)
merlin

1
@merlin Tôi đã sử dụng OmniGraffle (nhưng có thể đã sử dụng họa sĩ minh họa hoặc inkscape - ngoài tốc độ, không có gì OmniGraffle làm tốt hơn các công cụ biểu đồ khác như liên quan đến hình minh họa này).
Izhaki

2
Plunker của @ Anant đã biến mất, vì vậy đây là một cái mới: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Mở bảng điều khiển JS để xem các báo cáo nhật ký

TẠI SAO điều này không đúng khi ng-repeat được sử dụng cho chỉ thị trẻ em ??? Xem plunk: plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview
Luckylooke

@Luckylooke Plunk của bạn không có con nào có lệnh dưới ng-repeat (nghĩa là, cái được lặp lại là một mẫu có chỉ thị. Nếu có, bạn sẽ thấy rằng trình biên dịch của chúng chỉ được gọi sau liên kết của ng-repeat.
Izhaki

90

Điều gì khác xảy ra giữa các cuộc gọi chức năng này?

Các chức năng chỉ thị khác nhau được thực hiện từ bên trong hai chức năng khác góc gọi $compile(nơi chỉ thị của compileđược thực hiện) và một chức năng nội bộ gọi nodeLinkFn(nơi của chỉ thị controller, preLinkpostLinkđược thực hiện). Những điều khác nhau xảy ra trong chức năng góc trước và sau khi các chức năng chỉ thị được gọi. Có lẽ đáng chú ý nhất là đệ quy con. Hình minh họa đơn giản sau đây cho thấy các bước chính trong giai đoạn biên dịch và liên kết:

Một minh họa cho thấy biên dịch Angular và các giai đoạn liên kết

Để trình bày các bước này, hãy sử dụng đánh dấu HTML sau:

<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>

Với chỉ thị sau:

myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});

Biên dịch

Các compilehình API thích như vậy:

compile: function compile( tElement, tAttributes ) { ... }

Thông thường các tham số được thêm tiền tố tđể biểu thị các thành phần và thuộc tính được cung cấp là các tham số của mẫu nguồn, chứ không phải là của thể hiện.

Trước khi gọi đến compilenội dung được nhúng (nếu có) sẽ bị xóa và mẫu được áp dụng cho đánh dấu. Do đó, phần tử được cung cấp cho compilehàm sẽ trông như vậy:

<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>

Lưu ý rằng nội dung được nhúng không được chèn lại vào thời điểm này.

Theo lệnh gọi của chỉ thị .compile, Angular sẽ duyệt qua tất cả các phần tử con, bao gồm cả các phần tử có thể vừa được giới thiệu bởi lệnh (ví dụ: các phần tử mẫu).

Sáng tạo sơ thẩm

Trong trường hợp của chúng tôi, ba phiên bản của mẫu nguồn ở trên sẽ được tạo (bởi ng-repeat). Do đó, trình tự sau sẽ thực hiện ba lần, mỗi lần một lần.

Bộ điều khiển

Các controllerAPI bao gồm:

controller: function( $scope, $element, $attrs, $transclude ) { ... }

Bước vào giai đoạn liên kết, chức năng liên kết được trả lại qua $compilehiện được cung cấp với một phạm vi.

Đầu tiên, hàm liên kết tạo một phạm vi con ( scope: true) hoặc phạm vi tách biệt ( scope: {...}) nếu được yêu cầu.

Bộ điều khiển sau đó được thực thi, được cung cấp với phạm vi của phần tử thể hiện.

Liên kết trước

Các pre-linkhình API thích như vậy:

function preLink( scope, element, attributes, controller ) { ... }

Hầu như không có gì xảy ra giữa lệnh gọi đến lệnh .controller.preLinkhàm. Angular vẫn cung cấp khuyến nghị về cách mỗi nên được sử dụng.

Sau .preLinkcuộc gọi, hàm liên kết sẽ duyệt qua từng phần tử con - gọi hàm liên kết chính xác và gắn vào nó phạm vi hiện tại (đóng vai trò là phạm vi cha cho các phần tử con).

Liên kết bài

Các post-linkAPI là tương tự như của các pre-linkchức năng:

function postLink( scope, element, attributes, controller ) { ... }

Có lẽ đáng chú ý rằng một khi .postLinkchức năng của một lệnh được gọi, quá trình liên kết của tất cả các yếu tố con của nó đã hoàn thành, bao gồm tất cả các .postLinkchức năng của trẻ em .

Điều này có nghĩa là theo thời gian .postLinkđược gọi, những đứa trẻ 'sống' đã sẵn sàng. Điêu nay bao gôm:

  • ràng buộc dữ liệu
  • áp dụng loại trừ
  • phạm vi đính kèm

Do đó, mẫu ở giai đoạn này sẽ trông như vậy:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>

3
Làm thế nào bạn tạo ra bản vẽ này?
Royi Namir

6
@RoyiNamir Omnigraffle.
Izhaki

43

Làm thế nào để khai báo các chức năng khác nhau?

Biên dịch, điều khiển, liên kết trước và sau liên kết

Nếu một là sử dụng cả bốn chức năng, lệnh sẽ theo mẫu này:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return {
                pre: function preLink( scope, element, attributes, controller, transcludeFn ) {
                    // Pre-link code goes here
                },
                post: function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here
                }
            };
        }
    };  
});

Lưu ý rằng biên dịch trả về một đối tượng có chứa cả hàm pre-link và post-link; trong biệt ngữ Angular chúng ta nói hàm biên dịch trả về một hàm mẫu .

Biên dịch, điều khiển và liên kết bài

Nếu pre-linkkhông cần thiết, hàm biên dịch có thể chỉ cần trả về hàm post-link thay vì một đối tượng định nghĩa, như vậy:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here                 
            };
        }
    };  
});

Đôi khi, một người muốn thêm một compilephương thức, sau khi phương thức (bài) linkđược xác định. Đối với điều này, người ta có thể sử dụng:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.

            return this.link;
        },
        link: function( scope, element, attributes, controller, transcludeFn ) {
            // Post-link code goes here
        }

    };  
});

Bộ điều khiển và liên kết bài

Nếu không có chức năng biên dịch là cần thiết, người ta có thể bỏ qua khai báo của nó hoàn toàn và cung cấp chức năng hậu liên kết dưới thuộc linktính của đối tượng cấu hình của lệnh:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

Không có bộ điều khiển

Trong bất kỳ ví dụ nào ở trên, người ta có thể chỉ cần loại bỏ controllerhàm nếu không cần thiết. Vì vậy, ví dụ, nếu chỉ cần post-linkchức năng, người ta có thể sử dụng:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

31

Sự khác biệt giữa mẫu nguồnmẫu cá thể là gì?

Thực tế là Angular cho phép thao tác DOM có nghĩa là đánh dấu đầu vào vào quá trình biên dịch đôi khi khác với đầu ra. Đặc biệt, một số đánh dấu đầu vào có thể được sao chép một vài lần (như với ng-repeat) trước khi được kết xuất vào DOM.

Thuật ngữ góc là một chút không nhất quán, nhưng nó vẫn phân biệt giữa hai loại đánh dấu:

  • Mẫu nguồn - đánh dấu sẽ được nhân bản, nếu cần. Nếu được nhân bản, đánh dấu này sẽ không được hiển thị cho DOM.
  • Mẫu sơ thẩm - đánh dấu thực tế sẽ được hiển thị cho DOM. Nếu nhân bản có liên quan, mỗi trường hợp sẽ là một bản sao.

Đánh dấu sau đây chứng minh điều này:

<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>

Mã nguồn html định nghĩa

    <my-directive>{{i}}</my-directive>

phục vụ như là mẫu nguồn.

Nhưng vì nó được gói trong một lệnh ng-repeat, mẫu nguồn này sẽ được sao chép (3 lần trong trường hợp của chúng tôi). Các bản sao này là mẫu cá thể, mỗi mẫu sẽ xuất hiện trong DOM và được liên kết với phạm vi có liên quan.


23

Hàm biên dịch

Mỗi compilechức năng của chỉ thị chỉ được gọi một lần, khi Angstra bootstraps.

Chính thức, đây là nơi để thực hiện các thao tác mẫu (nguồn) không liên quan đến phạm vi hoặc ràng buộc dữ liệu.

Chủ yếu, điều này được thực hiện cho mục đích tối ưu hóa; xem xét các đánh dấu sau:

<tr ng-repeat="raw in raws">
    <my-raw></my-raw>
</tr>

Lệnh <my-raw>này sẽ hiển thị một bộ đánh dấu DOM cụ thể. Vì vậy, chúng ta có thể:

  • Cho phép ng-repeatnhân đôi mẫu nguồn ( <my-raw>), sau đó sửa đổi đánh dấu của từng mẫu cá thể (bên ngoài compilehàm).
  • Sửa đổi mẫu nguồn để liên quan đến đánh dấu mong muốn (trong compilehàm), và sau đó cho phép ng-repeatsao chép nó.

Nếu có 1000 mục trong rawsbộ sưu tập, tùy chọn sau có thể nhanh hơn tùy chọn trước.

Làm:

  • Thao tác đánh dấu để nó phục vụ như một khuôn mẫu cho các thể hiện (bản sao).

Đừng

  • Đính kèm xử lý sự kiện.
  • Kiểm tra các yếu tố con.
  • Thiết lập các quan sát trên các thuộc tính.
  • Thiết lập đồng hồ trên phạm vi.

20

Chức năng điều khiển

Mỗi controllerhàm của lệnh được gọi bất cứ khi nào một phần tử liên quan mới được khởi tạo.

Chính thức, controllerchức năng là nơi một:

  • Xác định logic bộ điều khiển (phương thức) có thể được chia sẻ giữa các bộ điều khiển.
  • Bắt đầu các biến phạm vi.

Một lần nữa, điều quan trọng cần nhớ là nếu chỉ thị liên quan đến một phạm vi bị cô lập, thì bất kỳ thuộc tính nào trong phạm vi kế thừa từ phạm vi cha mẹ vẫn chưa có sẵn.

Làm:

  • Xác định logic điều khiển
  • Bắt đầu các biến phạm vi

Đừng:

  • Kiểm tra các phần tử con (chúng có thể chưa được hiển thị, bị ràng buộc theo phạm vi, v.v.).

Rất vui khi bạn đề cập đến Bộ điều khiển trong chỉ thị là một nơi tuyệt vời để khởi tạo phạm vi. Tôi đã có thời gian khó khăn để khám phá điều đó.
jsbisht

1
Trình điều khiển KHÔNG "Khởi tạo phạm vi", nó chỉ truy cập phạm vi đã được khởi tạo độc lập với nó.
Dmitri Zaitsev

@DmitriZaitsev chú ý tốt đến các chi tiết. Tôi đã sửa đổi văn bản.
Izhaki

19

Chức năng hậu liên kết

Khi post-linkchức năng được gọi, tất cả các bước trước đó đã diễn ra - ràng buộc, loại trừ, v.v.

Đây thường là nơi để thao tác thêm DOM được kết xuất.

Làm:

  • Thao tác các phần tử DOM (được hiển thị và do đó được khởi tạo).
  • Đính kèm xử lý sự kiện.
  • Kiểm tra các yếu tố con.
  • Thiết lập các quan sát trên các thuộc tính.
  • Thiết lập đồng hồ trên phạm vi.

9
Trong trường hợp bất cứ ai đang sử dụng chức năng liên kết (không có liên kết trước hoặc liên kết sau), thật tốt khi biết rằng nó tương đương với liên kết sau.
Asaf David

15

Chức năng liên kết trước

Mỗi pre-linkhàm của lệnh được gọi bất cứ khi nào một phần tử liên quan mới được khởi tạo.

Như đã thấy trước đây trong phần thứ tự biên dịch, các pre-linkhàm được gọi là cha-con-con, trong khi các post-linkhàm được gọi child-then-parent.

Các pre-linkchức năng hiếm khi được sử dụng, nhưng có thể hữu ích trong các tình huống đặc biệt; ví dụ, khi bộ điều khiển con tự đăng ký với bộ điều khiển chính, nhưng việc đăng ký phải theo parent-then-childkiểu ( ngModelControllerthực hiện theo cách này).

Đừng:

  • Kiểm tra các phần tử con (chúng có thể chưa được hiển thị, bị ràng buộc theo phạm vi, v.v.).
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.